diff --git a/README.md b/README.md index 6c33d99..5649748 100644 --- a/README.md +++ b/README.md @@ -1,87 +1,97 @@ INSTALLATION PROCEDURE ====================== -This package contains required PHP libraries as well as the Roundcube Framework -placed in lib/ext directory. In case you're using a package version -with lib/ext directory empty make sure all dependencies are installed -on your system. These are Roundcube Framework and its dependencies plus -PEAR::HTTP_Request2 and PEAR::Net_URL2 packages. Additionally Smarty v3 need -to be installed. Webdav driver requires SabreDAV library. +This package uses [Composer][1] to install and maintain required PHP libraries +as well as the Roundcube framework. The requirements are basically the same as +for Roundcube so please read the INSTALLATION section in the Roundcube +framework's [README][2] file. -1. Create local config +1. Install Composer + +Execute this in the project root directory: + +$ curl -s http://getcomposer.org/installer | php + +This will create a file named composer.phar in the project directory. + +2. Install Dependencies + +$ cp composer.json-dist composer.json +$ php composer.phar install + +3. Import the Roundcube Framework (1.2) and Kolab plugins + +3.1. Either copy or symlink the Roundcube framework package into lib/ext/Roundcube +3.2. Either copy or symlink the roundcubemail-plugins-kolab into lib/drivers/kolab/plugins + +4. Create local config The configuration for this service inherits basic options from the Roundcube config. To make that available, symlink the Roundcube config file (config.inc.php) into the local config/ directory. -2. Give write access for the webserver user to the logs, cache and temp folders: +5. Give write access for the webserver user to the logs, cache and temp folders: $ chown logs $ chown cache $ chown temp -3. Execute database initialization scripts from doc/SQL/ on Roundcube database. +6. Execute database initialization scripts from doc/SQL/ on Roundcube database. -4. Optionally, configure your webserver to point to the 'public_html' directory of this +7. Optionally, configure your webserver to point to the 'public_html' directory of this package as document root. -SabreDAV INSTALLATION -===================== - -cd lib/ext -wget https://github.com/fruux/sabre-dav/releases/download/2.1.6/sabredav-2.1.6.zip -unzip sabredav-2.1.6.zip -rm -f sabredav-2.1.6.zip - - CREATING BACKEND-DRIVER ======================= Chwala API supports creation of different storage backends. It is possible to create a driver class that will store files on any storage e.g. local filesystem. As for now it is possible to use only one storage driver at a time. There are currently two drivers available for Chwala: Kolab and Seafile. The Kolab driver is considered the reference driver. Both can be found in the lib/drivers directory. The Kolab driver is based on Roundcube Framework and implements storage the "Kolab way", which is to store files in IMAP. The main file is lib/drivers/kolab/kolab_file_storage.php. To create a new driver for a different storage system you need to: 1. Create driver directory as lib/drivers/. This directory will be added to PHP's include path. 2. Create lib/drivers//_file_storage.php file. This file should define a class _file_storage which implements the file_storage interface as defined in lib/file_storage.php. 3. To change the driver set 'fileapi_backend' option to the driver name in main configuration file. The default is 'kolab'. Driver initialization --------------------- Driver object is initialized in file_api::api_init() method. After the object instance is created we call configure() method. Driver methods -------------- 1. configure - Is used to configure the driver. 2. authenticate - Is used to authenticate a user in authenticate request. 3. capabilities - Is supposed to return capabilities and limitations (like max. upload size) supported by the driver. Other methods are self explanatory and well documented in interface class file. API documentation can be generated using phpDocumentor (http://phpdoc.org). + +[1]: http://getcomposer.org +[2]: https://github.com/roundcube/roundcubemail/blob/master/program/lib/Roundcube/README.md) diff --git a/composer.json-dist b/composer.json-dist new file mode 100644 index 0000000..1ba0af6 --- /dev/null +++ b/composer.json-dist @@ -0,0 +1,25 @@ +{ + "name": "kolab/chwala", + "description": "The Kolab Storage service", + "license": "AGPL-3.0+", + "repositories": [ + { + "type": "vcs", + "url": "https://git.kolab.org/diffusion/PNL/php-net_ldap.git" + } + ], + "require": { + "php": ">=5.4.0", + "pear/pear-core-minimal": "~1.10.1", + "pear/net_url2": "~2.2.1", + "pear/http_request2": "~2.3.0", + "pear/net_socket": "~1.2.1", + "pear/auth_sasl": "~1.1.0", + "pear/net_idna2": "~0.2.0", + "pear/mail_mime": "~1.10.0", + "pear/net_smtp": "~1.7.3", + "pear/net_ldap2": "~2.2.0", + "kolab/net_ldap3": "dev-master", + "sabre/dav" : "~2.1.11" + } +} diff --git a/lib/drivers/kolab/plugins/kolab_auth/LICENSE b/lib/drivers/kolab/plugins/kolab_auth/LICENSE deleted file mode 100644 index dba13ed..0000000 --- a/lib/drivers/kolab/plugins/kolab_auth/LICENSE +++ /dev/null @@ -1,661 +0,0 @@ - GNU AFFERO GENERAL PUBLIC LICENSE - Version 3, 19 November 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU Affero General Public License is a free, copyleft license for -software and other kinds of works, specifically designed to ensure -cooperation with the community in the case of network server software. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -our General Public Licenses are 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. - - 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. - - Developers that use our General Public Licenses protect your rights -with two steps: (1) assert copyright on the software, and (2) offer -you this License which gives you legal permission to copy, distribute -and/or modify the software. - - A secondary benefit of defending all users' freedom is that -improvements made in alternate versions of the program, if they -receive widespread use, become available for other developers to -incorporate. Many developers of free software are heartened and -encouraged by the resulting cooperation. However, in the case of -software used on network servers, this result may fail to come about. -The GNU General Public License permits making a modified version and -letting the public access it on a server without ever releasing its -source code to the public. - - The GNU Affero General Public License is designed specifically to -ensure that, in such cases, the modified source code becomes available -to the community. It requires the operator of a network server to -provide the source code of the modified version running there to the -users of that server. Therefore, public use of a modified version, on -a publicly accessible server, gives the public access to the source -code of the modified version. - - An older license, called the Affero General Public License and -published by Affero, was designed to accomplish similar goals. This is -a different license, not a version of the Affero GPL, but Affero has -released a new version of the Affero GPL which permits relicensing under -this license. - - 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 Affero 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. Remote Network Interaction; Use with the GNU General Public License. - - Notwithstanding any other provision of this License, if you modify the -Program, your modified version must prominently offer all users -interacting with it remotely through a computer network (if your version -supports such interaction) an opportunity to receive the Corresponding -Source of your version by providing access to the Corresponding Source -from a network server at no charge, through some standard or customary -means of facilitating copying of software. This Corresponding Source -shall include the Corresponding Source for any work covered by version 3 -of the GNU General Public License that is incorporated pursuant to the -following paragraph. - - 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 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 work with which it is combined will remain governed by version -3 of the GNU General Public License. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU Affero 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 Affero 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 Affero 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 Affero 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. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If your software can interact with users remotely through a computer -network, you should also make sure that it provides a way for users to -get its source. For example, if your program is a web application, its -interface could display a "Source" link that leads users to an archive -of the code. There are many ways you could offer source, and different -solutions will be better for different programs; see section 13 for the -specific requirements. - - 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 AGPL, see -. diff --git a/lib/drivers/kolab/plugins/kolab_auth/composer.json b/lib/drivers/kolab/plugins/kolab_auth/composer.json deleted file mode 100644 index 5fec104..0000000 --- a/lib/drivers/kolab/plugins/kolab_auth/composer.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "kolab/kolab_auth", - "type": "roundcube-plugin", - "description": "Kolab authentication", - "homepage": "https://git.kolab.org/diffusion/RPK/", - "license": "AGPLv3", - "version": "3.2.8", - "authors": [ - { - "name": "Thomas Bruederli", - "email": "bruederli@kolabsys.com", - "role": "Lead" - }, - { - "name": "Aleksander Machniak", - "email": "machniak@kolabsys.com", - "role": "Lead" - } - ], - "repositories": [ - { - "type": "composer", - "url": "http://plugins.roundcube.net" - } - ], - "require": { - "php": ">=5.3.0", - "roundcube/plugin-installer": ">=0.1.3" - } -} diff --git a/lib/drivers/kolab/plugins/kolab_auth/config.inc.php.dist b/lib/drivers/kolab/plugins/kolab_auth/config.inc.php.dist deleted file mode 100644 index 8c01d56..0000000 --- a/lib/drivers/kolab/plugins/kolab_auth/config.inc.php.dist +++ /dev/null @@ -1,91 +0,0 @@ - 'cn=kolab,cn=config', -// 'domain_filter' => '(&(objectclass=domainrelatedobject)(associateddomain=%s))', -// 'domain_name_attr' => 'associateddomain', -// -// With this %dc variable in base_dn and groups/base_dn will be -// replaced with DN string of resolved domain -//--------------------------------------------------------------------- -$config['kolab_auth_addressbook'] = ''; - -// This will overwrite defined filter -$config['kolab_auth_filter'] = '(&(objectClass=kolabInetOrgPerson)(|(uid=%u)(mail=%fu)(alias=%fu)))'; - -// Use this field (from fieldmap configuration) to get authentication ID. Don't use an array here! -$config['kolab_auth_login'] = 'email'; - -// Use these fields (from fieldmap configuration) for default identity. -// If the value array contains more than one field, first non-empty will be used -// Note: These aren't LDAP attributes, but field names in config -// Note: If there's more than one email address, as many identities will be created -$config['kolab_auth_name'] = array('name', 'cn'); -$config['kolab_auth_email'] = array('email'); -$config['kolab_auth_organization'] = array('organization'); - -// Role field (from fieldmap configuration) -$config['kolab_auth_role'] = 'role'; - -// Template for user names displayed in the UI. -// You can use all attributes from the 'fieldmap' property of the 'kolab_auth_addressbook' configuration -$config['kolab_auth_user_displayname'] = '{name} ({ou})'; - -// Login and password of the admin user. Enables "Login As" feature. -$config['kolab_auth_admin_login'] = ''; -$config['kolab_auth_admin_password'] = ''; - -// Enable audit logging for abuse of administrative privileges. -$config['kolab_auth_auditlog'] = false; - -// As set of rules to define the required rights on the target entry -// which allow an admin user to login as another user (the target). -// The effective rights value refers to either entry level attribute level rights: -// * entry:[read|add|delete] -// * attrib::[read|write|delete] -$config['kolab_auth_admin_rights'] = array( - // Roundcube task => required effective right - 'settings' => 'entry:read', - 'mail' => 'entry:delete', - 'addressbook' => 'entry:delete', - // or use a wildcard entry like this: - '*' => 'entry:read', -); - -// Enable plugins on a role-by-role basis. In this example, the 'acl' plugin -// is enabled for people with a 'cn=professional-user,dc=mykolab,dc=ch' role. -// -// Note that this does NOT mean the 'acl' plugin is disabled for other people. -$config['kolab_auth_role_plugins'] = Array( - 'cn=professional-user,dc=mykolab,dc=ch' => Array( - 'acl', - ), - ); - -// Settings on a role-by-role basis. In this example, the 'htmleditor' setting -// is enabled(1) for people with a 'cn=professional-user,dc=mykolab,dc=ch' role, -// and it cannot be overridden. Sample use-case: disable htmleditor for normal people, -// do not allow the setting to be controlled through the preferences, enable the -// html editor for professional users and allow them to override the setting in -// the preferences. -$config['kolab_auth_role_settings'] = Array( - 'cn=professional-user,dc=mykolab,dc=ch' => Array( - 'htmleditor' => Array( - 'mode' => 'override', - 'value' => 1, - 'allow_override' => true - ), - ), - ); - -// List of LDAP addressbooks (keys of ldap_public configuration array) -// for which base_dn variables (%dc, etc.) will be replaced according to authenticated user DN -// Note: special name '*' for all LDAP addressbooks -$config['kolab_auth_ldap_addressbooks'] = array('*'); - -?> diff --git a/lib/drivers/kolab/plugins/kolab_auth/kolab_auth.php b/lib/drivers/kolab/plugins/kolab_auth/kolab_auth.php deleted file mode 100644 index 926c506..0000000 --- a/lib/drivers/kolab/plugins/kolab_auth/kolab_auth.php +++ /dev/null @@ -1,788 +0,0 @@ - - * - * Copyright (C) 2011-2013, Kolab Systems AG - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -class kolab_auth extends rcube_plugin -{ - static $ldap; - private $username; - private $data = array(); - - public function init() - { - $rcmail = rcube::get_instance(); - - $this->load_config(); - - $this->add_hook('authenticate', array($this, 'authenticate')); - $this->add_hook('startup', array($this, 'startup')); - $this->add_hook('user_create', array($this, 'user_create')); - - // Hook for password change - $this->add_hook('password_ldap_bind', array($this, 'password_ldap_bind')); - - // Hooks related to "Login As" feature - $this->add_hook('template_object_loginform', array($this, 'login_form')); - $this->add_hook('storage_connect', array($this, 'imap_connect')); - $this->add_hook('managesieve_connect', array($this, 'imap_connect')); - $this->add_hook('smtp_connect', array($this, 'smtp_connect')); - $this->add_hook('identity_form', array($this, 'identity_form')); - - // Hook to modify some configuration, e.g. ldap - $this->add_hook('config_get', array($this, 'config_get')); - - // Hook to modify logging directory - $this->add_hook('write_log', array($this, 'write_log')); - $this->username = $_SESSION['username']; - - // Enable debug logs (per-user), when logged as another user - if (!empty($_SESSION['kolab_auth_admin']) && $rcmail->config->get('kolab_auth_auditlog')) { - $rcmail->config->set('debug_level', 1); - $rcmail->config->set('devel_mode', true); - $rcmail->config->set('smtp_log', true); - $rcmail->config->set('log_logins', true); - $rcmail->config->set('log_session', true); - $rcmail->config->set('memcache_debug', true); - $rcmail->config->set('imap_debug', true); - $rcmail->config->set('ldap_debug', true); - $rcmail->config->set('smtp_debug', true); - $rcmail->config->set('sql_debug', true); - - // SQL debug need to be set directly on DB object - // setting config variable will not work here because - // the object is already initialized/configured - if ($db = $rcmail->get_dbh()) { - $db->set_debug(true); - } - } - } - - /** - * Startup hook handler - */ - public function startup($args) - { - // Check access rights when logged in as another user - if (!empty($_SESSION['kolab_auth_admin']) && $args['task'] != 'login' && $args['task'] != 'logout') { - // access to specified task is forbidden, - // redirect to the first task on the list - if (!empty($_SESSION['kolab_auth_allowed_tasks'])) { - $tasks = (array)$_SESSION['kolab_auth_allowed_tasks']; - if (!in_array($args['task'], $tasks) && !in_array('*', $tasks)) { - header('Location: ?_task=' . array_shift($tasks)); - die; - } - - // add script that will remove disabled taskbar buttons - if (!in_array('*', $tasks)) { - $this->add_hook('render_page', array($this, 'render_page')); - } - } - } - - // load per-user settings - $this->load_user_role_plugins_and_settings(); - - return $args; - } - - /** - * Modify some configuration according to LDAP user record - */ - public function config_get($args) - { - // Replaces ldap_vars (%dc, etc) in public kolab ldap addressbooks - // config based on the users base_dn. (for multi domain support) - if ($args['name'] == 'ldap_public' && !empty($args['result'])) { - $rcmail = rcube::get_instance(); - $kolab_books = (array) $rcmail->config->get('kolab_auth_ldap_addressbooks'); - - foreach ($args['result'] as $name => $config) { - if (in_array($name, $kolab_books) || in_array('*', $kolab_books)) { - $args['result'][$name] = $this->patch_ldap_config($config); - } - } - } - else if ($args['name'] == 'kolab_users_directory' && !empty($args['result'])) { - $args['result'] = $this->patch_ldap_config($args['result']); - } - - return $args; - } - - /** - * Helper method to patch the given LDAP directory config with user-specific values - */ - protected function patch_ldap_config($config) - { - if (is_array($config)) { - $config['base_dn'] = self::parse_ldap_vars($config['base_dn']); - $config['search_base_dn'] = self::parse_ldap_vars($config['search_base_dn']); - $config['bind_dn'] = str_replace('%dn', $_SESSION['kolab_dn'], $config['bind_dn']); - - if (!empty($config['groups'])) { - $config['groups']['base_dn'] = self::parse_ldap_vars($config['groups']['base_dn']); - } - } - - return $config; - } - - /** - * Modifies list of plugins and settings according to - * specified LDAP roles - */ - public function load_user_role_plugins_and_settings() - { - if (empty($_SESSION['user_roledns'])) { - return; - } - - $rcmail = rcube::get_instance(); - - // Example 'kolab_auth_role_plugins' = - // - // Array( - // '' => Array('plugin1', 'plugin2'), - // ); - // - // NOTE that may in fact be something like: 'cn=role,%dc' - - $role_plugins = $rcmail->config->get('kolab_auth_role_plugins'); - - // Example $rcmail_config['kolab_auth_role_settings'] = - // - // Array( - // '' => Array( - // '$setting' => Array( - // 'mode' => '(override|merge)', (default: override) - // 'value' => <>, - // 'allow_override' => (true|false) (default: false) - // ), - // ), - // ); - // - // NOTE that may in fact be something like: 'cn=role,%dc' - - $role_settings = $rcmail->config->get('kolab_auth_role_settings'); - - if (!empty($role_plugins)) { - foreach ($role_plugins as $role_dn => $plugins) { - $role_dn = self::parse_ldap_vars($role_dn); - if (!empty($role_plugins[$role_dn])) { - $role_plugins[$role_dn] = array_unique(array_merge((array)$role_plugins[$role_dn], $plugins)); - } else { - $role_plugins[$role_dn] = $plugins; - } - } - } - - if (!empty($role_settings)) { - foreach ($role_settings as $role_dn => $settings) { - $role_dn = self::parse_ldap_vars($role_dn); - if (!empty($role_settings[$role_dn])) { - $role_settings[$role_dn] = array_merge((array)$role_settings[$role_dn], $settings); - } else { - $role_settings[$role_dn] = $settings; - } - } - } - - foreach ($_SESSION['user_roledns'] as $role_dn) { - if (!empty($role_settings[$role_dn]) && is_array($role_settings[$role_dn])) { - foreach ($role_settings[$role_dn] as $setting_name => $setting) { - if (!isset($setting['mode'])) { - $setting['mode'] = 'override'; - } - - if ($setting['mode'] == "override") { - $rcmail->config->set($setting_name, $setting['value']); - } elseif ($setting['mode'] == "merge") { - $orig_setting = $rcmail->config->get($setting_name); - - if (!empty($orig_setting)) { - if (is_array($orig_setting)) { - $rcmail->config->set($setting_name, array_merge($orig_setting, $setting['value'])); - } - } else { - $rcmail->config->set($setting_name, $setting['value']); - } - } - - $dont_override = (array) $rcmail->config->get('dont_override'); - - if (empty($setting['allow_override'])) { - $rcmail->config->set('dont_override', array_merge($dont_override, array($setting_name))); - } - else { - if (in_array($setting_name, $dont_override)) { - $_dont_override = array(); - foreach ($dont_override as $_setting) { - if ($_setting != $setting_name) { - $_dont_override[] = $_setting; - } - } - $rcmail->config->set('dont_override', $_dont_override); - } - } - - if ($setting_name == 'skin') { - if ($rcmail->output->type == 'html') { - $rcmail->output->set_skin($setting['value']); - $rcmail->output->set_env('skin', $setting['value']); - } - } - } - } - - if (!empty($role_plugins[$role_dn])) { - foreach ((array)$role_plugins[$role_dn] as $plugin) { - $this->api->load_plugin($plugin); - } - } - } - } - - /** - * Logging method replacement to print debug/errors into - * a separate (sub)folder for each user - */ - public function write_log($args) - { - $rcmail = rcube::get_instance(); - - if ($rcmail->config->get('log_driver') == 'syslog') { - return $args; - } - - // log_driver == 'file' is assumed here - $log_dir = $rcmail->config->get('log_dir', RCUBE_INSTALL_PATH . 'logs'); - - // Append original username + target username for audit-logging - if ($rcmail->config->get('kolab_auth_auditlog') && !empty($_SESSION['kolab_auth_admin'])) { - $args['dir'] = $log_dir . '/' . strtolower($_SESSION['kolab_auth_admin']) . '/' . strtolower($this->username); - - // Attempt to create the directory - if (!is_dir($args['dir'])) { - @mkdir($args['dir'], 0750, true); - } - } - // Define the user log directory if a username is provided - else if ($rcmail->config->get('per_user_logging') && !empty($this->username)) { - $user_log_dir = $log_dir . '/' . strtolower($this->username); - if (is_writable($user_log_dir)) { - $args['dir'] = $user_log_dir; - } - else if ($args['name'] != 'errors') { - $args['abort'] = true; // don't log if unauthenticed - } - } - - return $args; - } - - /** - * Sets defaults for new user. - */ - public function user_create($args) - { - if (!empty($this->data['user_email'])) { - // addresses list is supported - if (array_key_exists('email_list', $args)) { - $email_list = array_unique($this->data['user_email']); - - // add organization to the list - if (!empty($this->data['user_organization'])) { - foreach ($email_list as $idx => $email) { - $email_list[$idx] = array( - 'organization' => $this->data['user_organization'], - 'email' => $email, - ); - } - } - - $args['email_list'] = $email_list; - } - else { - $args['user_email'] = $this->data['user_email'][0]; - } - } - - if (!empty($this->data['user_name'])) { - $args['user_name'] = $this->data['user_name']; - } - - return $args; - } - - /** - * Modifies login form adding additional "Login As" field - */ - public function login_form($args) - { - $this->add_texts('localization/'); - - $rcmail = rcube::get_instance(); - $admin_login = $rcmail->config->get('kolab_auth_admin_login'); - $group = $rcmail->config->get('kolab_auth_group'); - $role_attr = $rcmail->config->get('kolab_auth_role'); - - // Show "Login As" input - if (empty($admin_login) || (empty($group) && empty($role_attr))) { - return $args; - } - - $input = new html_inputfield(array('name' => '_loginas', 'id' => 'rcmloginas', - 'type' => 'text', 'autocomplete' => 'off')); - $row = html::tag('tr', null, - html::tag('td', 'title', html::label('rcmloginas', rcube::Q($this->gettext('loginas')))) - . html::tag('td', 'input', $input->show(trim(rcube_utils::get_input_value('_loginas', rcube_utils::INPUT_POST)))) - ); - $args['content'] = preg_replace('/<\/tbody>/i', $row . '', $args['content']); - - return $args; - } - - /** - * Find user credentials In LDAP. - */ - public function authenticate($args) - { - // get username and host - $host = $args['host']; - $user = $args['user']; - $pass = $args['pass']; - $loginas = trim(rcube_utils::get_input_value('_loginas', rcube_utils::INPUT_POST)); - - if (empty($user) || (empty($pass) && empty($_SERVER['REMOTE_USER']))) { - $args['abort'] = true; - return $args; - } - - // temporarily set the current username to the one submitted - $this->username = $user; - - $ldap = self::ldap(); - if (!$ldap || !$ldap->ready) { - $args['abort'] = true; - $args['kolab_ldap_error'] = true; - $message = sprintf( - 'Login failure for user %s from %s in session %s (error %s)', - $user, - rcube_utils::remote_ip(), - session_id(), - "LDAP not ready" - ); - - rcube::write_log('userlogins', $message); - - return $args; - } - - // Find user record in LDAP - $record = $ldap->get_user_record($user, $host); - - if (empty($record)) { - $args['abort'] = true; - $message = sprintf( - 'Login failure for user %s from %s in session %s (error %s)', - $user, - rcube_utils::remote_ip(), - session_id(), - "No user record found" - ); - - rcube::write_log('userlogins', $message); - - return $args; - } - - $rcmail = rcube::get_instance(); - $admin_login = $rcmail->config->get('kolab_auth_admin_login'); - $admin_pass = $rcmail->config->get('kolab_auth_admin_password'); - $login_attr = $rcmail->config->get('kolab_auth_login'); - $name_attr = $rcmail->config->get('kolab_auth_name'); - $email_attr = $rcmail->config->get('kolab_auth_email'); - $org_attr = $rcmail->config->get('kolab_auth_organization'); - $role_attr = $rcmail->config->get('kolab_auth_role'); - $imap_attr = $rcmail->config->get('kolab_auth_mailhost'); - - if (!empty($role_attr) && !empty($record[$role_attr])) { - $_SESSION['user_roledns'] = (array)($record[$role_attr]); - } - - if (!empty($imap_attr) && !empty($record[$imap_attr])) { - $default_host = $rcmail->config->get('default_host'); - if (!empty($default_host)) { - rcube::write_log("errors", "Both default host and kolab_auth_mailhost set. Incompatible."); - } else { - $args['host'] = "tls://" . $record[$imap_attr]; - } - } - - // Login As... - if (!empty($loginas) && $admin_login) { - // Authenticate to LDAP - $result = $ldap->bind($record['dn'], $pass); - - if (!$result) { - $args['abort'] = true; - $message = sprintf( - 'Login failure for user %s from %s in session %s (error %s)', - $user, - rcube_utils::remote_ip(), - session_id(), - "Unable to bind with '" . $record['dn'] . "'" - ); - - rcube::write_log('userlogins', $message); - - return $args; - } - - $isadmin = false; - $admin_rights = $rcmail->config->get('kolab_auth_admin_rights', array()); - - // @deprecated: fall-back to the old check if the original user has/belongs to administrative role/group - if (empty($admin_rights)) { - $group = $rcmail->config->get('kolab_auth_group'); - $role_dn = $rcmail->config->get('kolab_auth_role_value'); - - // check role attribute - if (!empty($role_attr) && !empty($role_dn) && !empty($record[$role_attr])) { - $role_dn = $ldap->parse_vars($role_dn, $user, $host); - if (in_array($role_dn, (array)$record[$role_attr])) { - $isadmin = true; - } - } - - // check group - if (!$isadmin && !empty($group)) { - $groups = $ldap->get_user_groups($record['dn'], $user, $host); - if (in_array($group, $groups)) { - $isadmin = true; - } - } - - if ($isadmin) { - // user has admin privileges privilage, get "login as" user credentials - $target_entry = $ldap->get_user_record($loginas, $host); - $allowed_tasks = $rcmail->config->get('kolab_auth_allowed_tasks'); - } - } - else { - // get "login as" user credentials - $target_entry = $ldap->get_user_record($loginas, $host); - - if (!empty($target_entry)) { - // get effective rights to determine login-as permissions - $effective_rights = (array)$ldap->effective_rights($target_entry['dn']); - - if (!empty($effective_rights)) { - $effective_rights['attrib'] = $effective_rights['attributeLevelRights']; - $effective_rights['entry'] = $effective_rights['entryLevelRights']; - - // compare the rights with the permissions mapping - $allowed_tasks = array(); - foreach ($admin_rights as $task => $perms) { - $perms_ = explode(':', $perms); - $type = array_shift($perms_); - $req = array_pop($perms_); - $attrib = array_pop($perms_); - - if (array_key_exists($type, $effective_rights)) { - if ($type == 'entry' && in_array($req, $effective_rights[$type])) { - $allowed_tasks[] = $task; - } - else if ($type == 'attrib' && array_key_exists($attrib, $effective_rights[$type]) && - in_array($req, $effective_rights[$type][$attrib])) { - $allowed_tasks[] = $task; - } - } - } - - $isadmin = !empty($allowed_tasks); - } - } - } - - // Save original user login for log (see below) - if ($login_attr) { - $origname = is_array($record[$login_attr]) ? $record[$login_attr][0] : $record[$login_attr]; - } - else { - $origname = $user; - } - - if (!$isadmin || empty($target_entry)) { - $this->add_texts('localization/'); - - $args['abort'] = true; - $args['error'] = $this->gettext(array( - 'name' => 'loginasnotallowed', - 'vars' => array('user' => rcube::Q($loginas)), - )); - - $message = sprintf( - 'Login failure for user %s (as user %s) from %s in session %s (error %s)', - $user, - $loginas, - rcube_utils::remote_ip(), - session_id(), - "No privileges to login as '" . $loginas . "'" - ); - - rcube::write_log('userlogins', $message); - - return $args; - } - - // replace $record with target entry - $record = $target_entry; - - $args['user'] = $this->username = $loginas; - - // Mark session to use SASL proxy for IMAP authentication - $_SESSION['kolab_auth_admin'] = strtolower($origname); - $_SESSION['kolab_auth_login'] = $rcmail->encrypt($admin_login); - $_SESSION['kolab_auth_password'] = $rcmail->encrypt($admin_pass); - $_SESSION['kolab_auth_allowed_tasks'] = $allowed_tasks; - } - - // Store UID and DN of logged user in session for use by other plugins - $_SESSION['kolab_uid'] = is_array($record['uid']) ? $record['uid'][0] : $record['uid']; - $_SESSION['kolab_dn'] = $record['dn']; - - // Store LDAP replacement variables used for current user - // This improves performance of load_user_role_plugins_and_settings() - // which is executed on every request (via startup hook) and where - // we don't like to use LDAP (connection + bind + search) - $_SESSION['kolab_auth_vars'] = $ldap->get_parse_vars(); - - // Set user login - if ($login_attr) { - $this->data['user_login'] = is_array($record[$login_attr]) ? $record[$login_attr][0] : $record[$login_attr]; - } - if ($this->data['user_login']) { - $args['user'] = $this->username = $this->data['user_login']; - } - - // User name for identity (first log in) - foreach ((array)$name_attr as $field) { - $name = is_array($record[$field]) ? $record[$field][0] : $record[$field]; - if (!empty($name)) { - $this->data['user_name'] = $name; - break; - } - } - // User email(s) for identity (first log in) - foreach ((array)$email_attr as $field) { - $email = is_array($record[$field]) ? array_filter($record[$field]) : $record[$field]; - if (!empty($email)) { - $this->data['user_email'] = array_merge((array)$this->data['user_email'], (array)$email); - } - } - // Organization name for identity (first log in) - foreach ((array)$org_attr as $field) { - $organization = is_array($record[$field]) ? $record[$field][0] : $record[$field]; - if (!empty($organization)) { - $this->data['user_organization'] = $organization; - break; - } - } - - // Log "Login As" usage - if (!empty($origname)) { - rcube::write_log('userlogins', sprintf('Admin login for %s by %s from %s', - $args['user'], $origname, rcube_utils::remote_ip())); - } - - // load per-user settings/plugins - $this->load_user_role_plugins_and_settings(); - - return $args; - } - - /** - * Set user DN for password change (password plugin with ldap_simple driver) - */ - public function password_ldap_bind($args) - { - $args['user_dn'] = $_SESSION['kolab_dn']; - - $rcmail = rcube::get_instance(); - - $rcmail->config->set('password_ldap_method', 'user'); - - return $args; - } - - /** - * Sets SASL Proxy login/password for IMAP and Managesieve auth - */ - public function imap_connect($args) - { - if (!empty($_SESSION['kolab_auth_admin'])) { - $rcmail = rcube::get_instance(); - $admin_login = $rcmail->decrypt($_SESSION['kolab_auth_login']); - $admin_pass = $rcmail->decrypt($_SESSION['kolab_auth_password']); - - $args['auth_cid'] = $admin_login; - $args['auth_pw'] = $admin_pass; - } - - return $args; - } - - /** - * Sets SASL Proxy login/password for SMTP auth - */ - public function smtp_connect($args) - { - if (!empty($_SESSION['kolab_auth_admin'])) { - $rcmail = rcube::get_instance(); - $admin_login = $rcmail->decrypt($_SESSION['kolab_auth_login']); - $admin_pass = $rcmail->decrypt($_SESSION['kolab_auth_password']); - - $args['smtp_auth_cid'] = $admin_login; - $args['smtp_auth_pw'] = $admin_pass; - } - - return $args; - } - - /** - * Hook to replace the plain text input field for email address by a drop-down list - * with all email addresses (including aliases) from this user's LDAP record. - */ - public function identity_form($args) - { - $rcmail = rcube::get_instance(); - $ident_level = intval($rcmail->config->get('identities_level', 0)); - - // do nothing if email address modification is disabled - if ($ident_level == 1 || $ident_level == 3) { - return $args; - } - - $ldap = self::ldap(); - if (!$ldap || !$ldap->ready || empty($_SESSION['kolab_dn'])) { - return $args; - } - - $emails = array(); - $user_record = $ldap->get_record($_SESSION['kolab_dn']); - - foreach ((array)$rcmail->config->get('kolab_auth_email', array()) as $col) { - $values = rcube_addressbook::get_col_values($col, $user_record, true); - if (!empty($values)) - $emails = array_merge($emails, array_filter($values)); - } - - // kolab_delegation might want to modify this addresses list - $plugin = $rcmail->plugins->exec_hook('kolab_auth_emails', array('emails' => $emails)); - $emails = $plugin['emails']; - - if (!empty($emails)) { - $args['form']['addressing']['content']['email'] = array( - 'type' => 'select', - 'options' => array_combine($emails, $emails), - ); - } - - return $args; - } - - /** - * Action executed before the page is rendered to add an onload script - * that will remove all taskbar buttons for disabled tasks - */ - public function render_page($args) - { - $rcmail = rcube::get_instance(); - $tasks = (array)$_SESSION['kolab_auth_allowed_tasks']; - $tasks[] = 'logout'; - - // disable buttons in taskbar - $script = " - \$('a').filter(function() { - var ev = \$(this).attr('onclick'); - return ev && ev.match(/'switch-task','([a-z]+)'/) - && \$.inArray(RegExp.\$1, " . json_encode($tasks) . ") < 0; - }).remove(); - "; - - $rcmail->output->add_script($script, 'docready'); - } - - /** - * Initializes LDAP object and connects to LDAP server - */ - public static function ldap() - { - if (self::$ldap) { - return self::$ldap; - } - - $rcmail = rcube::get_instance(); - $addressbook = $rcmail->config->get('kolab_auth_addressbook'); - - if (!is_array($addressbook)) { - $ldap_config = (array)$rcmail->config->get('ldap_public'); - $addressbook = $ldap_config[$addressbook]; - } - - if (empty($addressbook)) { - return null; - } - - require_once __DIR__ . '/kolab_auth_ldap.php'; - - self::$ldap = new kolab_auth_ldap($addressbook); - - return self::$ldap; - } - - /** - * Parses LDAP DN string with replacing supported variables. - * See kolab_auth_ldap::parse_vars() - * - * @param string $str LDAP DN string - * - * @return string Parsed DN string - */ - public static function parse_ldap_vars($str) - { - if (!empty($_SESSION['kolab_auth_vars'])) { - $str = strtr($str, $_SESSION['kolab_auth_vars']); - } - - return $str; - } -} diff --git a/lib/drivers/kolab/plugins/kolab_auth/kolab_auth_ldap.php b/lib/drivers/kolab/plugins/kolab_auth/kolab_auth_ldap.php deleted file mode 100644 index 30c82bf..0000000 --- a/lib/drivers/kolab/plugins/kolab_auth/kolab_auth_ldap.php +++ /dev/null @@ -1,480 +0,0 @@ - - * - * Copyright (C) 2011-2013, Kolab Systems AG - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -/** - * Wrapper class for rcube_ldap_generic - */ -class kolab_auth_ldap extends rcube_ldap_generic -{ - private $conf = array(); - private $fieldmap = array(); - - - function __construct($p) - { - $rcmail = rcube::get_instance(); - - $this->conf = $p; - $this->conf['kolab_auth_user_displayname'] = $rcmail->config->get('kolab_auth_user_displayname', '{name}'); - - $this->fieldmap = $p['fieldmap']; - $this->fieldmap['uid'] = 'uid'; - - $p['attributes'] = array_values($this->fieldmap); - $p['debug'] = (bool) $rcmail->config->get('ldap_debug'); - - // Connect to the server (with bind) - parent::__construct($p); - $this->_connect(); - - $rcmail->add_shutdown_function(array($this, 'close')); - } - - /** - * Establish a connection to the LDAP server - */ - private function _connect() - { - // try to connect + bind for every host configured - // with OpenLDAP 2.x ldap_connect() always succeeds but ldap_bind will fail if host isn't reachable - // see http://www.php.net/manual/en/function.ldap-connect.php - foreach ((array)$this->config['hosts'] as $host) { - // skip host if connection failed - if (!$this->connect($host)) { - continue; - } - - $bind_pass = $this->config['bind_pass']; - $bind_user = $this->config['bind_user']; - $bind_dn = $this->config['bind_dn']; - - if (empty($bind_pass)) { - $this->ready = true; - } - else { - if (!empty($bind_dn)) { - $this->ready = $this->bind($bind_dn, $bind_pass); - } - else if (!empty($this->config['auth_cid'])) { - $this->ready = $this->sasl_bind($this->config['auth_cid'], $bind_pass, $bind_user); - } - else { - $this->ready = $this->sasl_bind($bind_user, $bind_pass); - } - } - - // connection established, we're done here - if ($this->ready) { - break; - } - - } // end foreach hosts - - if (!is_resource($this->conn)) { - rcube::raise_error(array('code' => 100, 'type' => 'ldap', - 'file' => __FILE__, 'line' => __LINE__, - 'message' => "Could not connect to any LDAP server, last tried $host"), true); - - $this->ready = false; - } - - return $this->ready; - } - - /** - * Fetches user data from LDAP addressbook - */ - function get_user_record($user, $host) - { - $rcmail = rcube::get_instance(); - $filter = $rcmail->config->get('kolab_auth_filter'); - $filter = $this->parse_vars($filter, $user, $host); - $base_dn = $this->parse_vars($this->config['base_dn'], $user, $host); - $scope = $this->config['scope']; - - // @TODO: print error if filter is empty - - // get record - if ($result = parent::search($base_dn, $filter, $scope, $this->attributes)) { - if ($result->count() == 1) { - $entries = $result->entries(true); - $dn = key($entries); - $entry = array_pop($entries); - $entry = $this->field_mapping($dn, $entry); - - return $entry; - } - } - } - - /** - * Fetches user data from LDAP addressbook - */ - function get_user_groups($dn, $user, $host) - { - if (empty($dn) || empty($this->config['groups'])) { - return array(); - } - - $base_dn = $this->parse_vars($this->config['groups']['base_dn'], $user, $host); - $name_attr = $this->config['groups']['name_attr'] ? $this->config['groups']['name_attr'] : 'cn'; - $member_attr = $this->get_group_member_attr(); - $filter = "(member=$dn)(uniqueMember=$dn)"; - - if ($member_attr != 'member' && $member_attr != 'uniqueMember') - $filter .= "($member_attr=$dn)"; - $filter = strtr("(|$filter)", array("\\" => "\\\\")); - - $result = parent::search($base_dn, $filter, 'sub', array('dn', $name_attr)); - - if (!$result) { - return array(); - } - - $groups = array(); - foreach ($result as $entry) { - $dn = $entry['dn']; - $entry = rcube_ldap_generic::normalize_entry($entry); - - $groups[$dn] = $entry[$name_attr]; - } - - return $groups; - } - - /** - * Get a specific LDAP record - * - * @param string DN - * - * @return array Record data - */ - function get_record($dn) - { - if (!$this->ready) { - return; - } - - if ($rec = $this->get_entry($dn)) { - $rec = rcube_ldap_generic::normalize_entry($rec); - $rec = $this->field_mapping($dn, $rec); - } - - return $rec; - } - - /** - * Replace LDAP record data items - * - * @param string $dn DN - * @param array $entry LDAP entry - * - * return bool True on success, False on failure - */ - function replace($dn, $entry) - { - // fields mapping - foreach ($this->fieldmap as $field => $attr) { - if (array_key_exists($field, $entry)) { - $entry[$attr] = $entry[$field]; - if ($attr != $field) { - unset($entry[$field]); - } - } - } - - return $this->mod_replace($dn, $entry); - } - - /** - * Search records (simplified version of rcube_ldap::search) - * - * @param mixed $fields The field name or array of field names to search in - * @param string $value Search value - * @param int $mode Matching mode: - * 0 - partial (*abc*), - * 1 - strict (=), - * 2 - prefix (abc*) - * @param array $required List of fields that cannot be empty - * @param int $limit Number of records - * @param int $count Returns the number of records found - * - * @return array List or false on error - */ - function dosearch($fields, $value, $mode=1, $required = array(), $limit = 0, &$count = 0) - { - if (empty($fields)) { - return array(); - } - - $mode = intval($mode); - - // try to resolve field names into ldap attributes - $fieldmap = $this->fieldmap; - $attrs = array_map(function($f) use ($fieldmap) { - return array_key_exists($f, $fieldmap) ? $fieldmap[$f] : $f; - }, (array)$fields); - - // compose a full-text-search-like filter - if (count($attrs) > 1 || $mode != 1) { - $filter = self::fulltext_search_filter($value, $attrs, $mode); - } - // direct search - else { - $field = $attrs[0]; - $filter = "($field=" . self::quote_string($value) . ")"; - } - - // add required (non empty) fields filter - $req_filter = ''; - - foreach ((array)$required as $field) { - $attr = array_key_exists($field, $this->fieldmap) ? $this->fieldmap[$field] : $field; - - // only add if required field is not already in search filter - if (!in_array($attr, $attrs)) { - $req_filter .= "($attr=*)"; - } - } - - if (!empty($req_filter)) { - $filter = '(&' . $req_filter . $filter . ')'; - } - - // avoid double-wildcard if $value is empty - $filter = preg_replace('/\*+/', '*', $filter); - - // add general filter to query - if (!empty($this->config['filter'])) { - $filter = '(&(' . preg_replace('/^\(|\)$/', '', $this->config['filter']) . ')' . $filter . ')'; - } - - $base_dn = $this->parse_vars($this->config['base_dn']); - $scope = $this->config['scope']; - $attrs = array_values($this->fieldmap); - $list = array(); - - if ($result = $this->search($base_dn, $filter, $scope, $attrs)) { - $count = $result->count(); - $i = 0; - foreach ($result as $entry) { - if ($limit && $limit <= $i) { - break; - } - - $dn = $entry['dn']; - $entry = rcube_ldap_generic::normalize_entry($entry); - $list[$dn] = $this->field_mapping($dn, $entry); - $i++; - } - } - - return $list; - } - - /** - * Set filter used in search() - */ - function set_filter($filter) - { - $this->config['filter'] = $filter; - } - - /** - * Maps LDAP attributes to defined fields - */ - protected function field_mapping($dn, $entry) - { - $entry['dn'] = $dn; - - // fields mapping - foreach ($this->fieldmap as $field => $attr) { - // $entry might be indexed by lower-case attribute names - $attr_lc = strtolower($attr); - if (isset($entry[$attr_lc])) { - $entry[$field] = $entry[$attr_lc]; - } - else if (isset($entry[$attr])) { - $entry[$field] = $entry[$attr]; - } - } - - // compose display name according to config - if (empty($this->fieldmap['displayname'])) { - $entry['displayname'] = rcube_addressbook::compose_search_name( - $entry, - $entry['email'], - $entry['name'], - $this->conf['kolab_auth_user_displayname'] - ); - } - - return $entry; - } - - /** - * Detects group member attribute name - */ - private function get_group_member_attr($object_classes = array()) - { - if (empty($object_classes)) { - $object_classes = $this->config['groups']['object_classes']; - } - if (!empty($object_classes)) { - foreach ((array)$object_classes as $oc) { - switch (strtolower($oc)) { - case 'group': - case 'groupofnames': - case 'kolabgroupofnames': - $member_attr = 'member'; - break; - - case 'groupofuniquenames': - case 'kolabgroupofuniquenames': - $member_attr = 'uniqueMember'; - break; - } - } - } - - if (!empty($member_attr)) { - return $member_attr; - } - - if (!empty($this->config['groups']['member_attr'])) { - return $this->config['groups']['member_attr']; - } - - return 'member'; - } - - /** - * Prepares filter query for LDAP search - */ - function parse_vars($str, $user = null, $host = null) - { - // When authenticating user $user is always set - // if not set it means we use this LDAP object for other - // purposes, e.g. kolab_delegation, then username with - // correct domain is in a session - if (!$user) { - $user = $_SESSION['username']; - } - - if (isset($this->icache[$user])) { - list($user, $dc) = $this->icache[$user]; - } - else { - $orig_user = $user; - $rcmail = rcube::get_instance(); - - // get default domain - if ($username_domain = $rcmail->config->get('username_domain')) { - if ($host && is_array($username_domain) && isset($username_domain[$host])) { - $domain = rcube_utils::parse_host($username_domain[$host], $host); - } - else if (is_string($username_domain)) { - $domain = rcube_utils::parse_host($username_domain, $host); - } - } - - // realmed username (with domain) - if (strpos($user, '@')) { - list($usr, $dom) = explode('@', $user); - - // unrealm domain, user login can contain a domain alias - if ($dom != $domain && ($dc = $this->domain_root_dn($dom))) { - // @FIXME: we should replace domain in $user, I suppose - } - } - else if ($domain) { - $user .= '@' . $domain; - } - - $this->icache[$orig_user] = array($user, $dc); - } - - // replace variables in filter - list($u, $d) = explode('@', $user); - - // hierarchal domain string - if (empty($dc)) { - $dc = 'dc=' . strtr($d, array('.' => ',dc=')); - } - - $replaces = array('%dc' => $dc, '%d' => $d, '%fu' => $user, '%u' => $u); - - $this->parse_replaces = $replaces; - - return strtr($str, $replaces); - } - - /** - * Returns variables used for replacement in (last) parse_vars() call - * - * @return array Variable-value hash array - */ - public function get_parse_vars() - { - return $this->parse_replaces; - } - - /** - * Register additional fields - */ - public function extend_fieldmap($map) - { - foreach ((array)$map as $name => $attr) { - if (!in_array($attr, $this->attributes)) { - $this->attributes[] = $attr; - $this->fieldmap[$name] = $attr; - } - } - } - - /** - * HTML-safe DN string encoding - * - * @param string $str DN string - * - * @return string Encoded HTML identifier string - */ - static function dn_encode($str) - { - return rtrim(strtr(base64_encode($str), '+/', '-_'), '='); - } - - /** - * Decodes DN string encoded with _dn_encode() - * - * @param string $str Encoded HTML identifier string - * - * @return string DN string - */ - static function dn_decode($str) - { - $str = str_pad(strtr($str, '-_', '+/'), strlen($str) % 4, '=', STR_PAD_RIGHT); - return base64_decode($str); - } -} diff --git a/lib/drivers/kolab/plugins/kolab_auth/localization/bg_BG.inc b/lib/drivers/kolab/plugins/kolab_auth/localization/bg_BG.inc deleted file mode 100644 index 01e1ac2..0000000 --- a/lib/drivers/kolab/plugins/kolab_auth/localization/bg_BG.inc +++ /dev/null @@ -1,10 +0,0 @@ - diff --git a/lib/drivers/kolab/plugins/kolab_auth/localization/de_CH.inc b/lib/drivers/kolab/plugins/kolab_auth/localization/de_CH.inc deleted file mode 100644 index 3918e6e..0000000 --- a/lib/drivers/kolab/plugins/kolab_auth/localization/de_CH.inc +++ /dev/null @@ -1,11 +0,0 @@ - diff --git a/lib/drivers/kolab/plugins/kolab_auth/localization/de_DE.inc b/lib/drivers/kolab/plugins/kolab_auth/localization/de_DE.inc deleted file mode 100644 index 3918e6e..0000000 --- a/lib/drivers/kolab/plugins/kolab_auth/localization/de_DE.inc +++ /dev/null @@ -1,11 +0,0 @@ - diff --git a/lib/drivers/kolab/plugins/kolab_auth/localization/en_US.inc b/lib/drivers/kolab/plugins/kolab_auth/localization/en_US.inc deleted file mode 100644 index 4882bdc..0000000 --- a/lib/drivers/kolab/plugins/kolab_auth/localization/en_US.inc +++ /dev/null @@ -1,14 +0,0 @@ - diff --git a/lib/drivers/kolab/plugins/kolab_auth/localization/es_ES.inc b/lib/drivers/kolab/plugins/kolab_auth/localization/es_ES.inc deleted file mode 100644 index ed203e6..0000000 --- a/lib/drivers/kolab/plugins/kolab_auth/localization/es_ES.inc +++ /dev/null @@ -1,9 +0,0 @@ - diff --git a/lib/drivers/kolab/plugins/kolab_auth/localization/et_EE.inc b/lib/drivers/kolab/plugins/kolab_auth/localization/et_EE.inc deleted file mode 100644 index ed203e6..0000000 --- a/lib/drivers/kolab/plugins/kolab_auth/localization/et_EE.inc +++ /dev/null @@ -1,9 +0,0 @@ - diff --git a/lib/drivers/kolab/plugins/kolab_auth/localization/fr_FR.inc b/lib/drivers/kolab/plugins/kolab_auth/localization/fr_FR.inc deleted file mode 100644 index 6538f5b..0000000 --- a/lib/drivers/kolab/plugins/kolab_auth/localization/fr_FR.inc +++ /dev/null @@ -1,11 +0,0 @@ - diff --git a/lib/drivers/kolab/plugins/kolab_auth/localization/ja_JP.inc b/lib/drivers/kolab/plugins/kolab_auth/localization/ja_JP.inc deleted file mode 100644 index e360737..0000000 --- a/lib/drivers/kolab/plugins/kolab_auth/localization/ja_JP.inc +++ /dev/null @@ -1,10 +0,0 @@ - diff --git a/lib/drivers/kolab/plugins/kolab_auth/localization/nl_NL.inc b/lib/drivers/kolab/plugins/kolab_auth/localization/nl_NL.inc deleted file mode 100644 index ea3a1c0..0000000 --- a/lib/drivers/kolab/plugins/kolab_auth/localization/nl_NL.inc +++ /dev/null @@ -1,10 +0,0 @@ - diff --git a/lib/drivers/kolab/plugins/kolab_auth/localization/pl_PL.inc b/lib/drivers/kolab/plugins/kolab_auth/localization/pl_PL.inc deleted file mode 100644 index ed203e6..0000000 --- a/lib/drivers/kolab/plugins/kolab_auth/localization/pl_PL.inc +++ /dev/null @@ -1,9 +0,0 @@ - diff --git a/lib/drivers/kolab/plugins/kolab_auth/localization/pt_BR.inc b/lib/drivers/kolab/plugins/kolab_auth/localization/pt_BR.inc deleted file mode 100644 index e594c2e..0000000 --- a/lib/drivers/kolab/plugins/kolab_auth/localization/pt_BR.inc +++ /dev/null @@ -1,10 +0,0 @@ - diff --git a/lib/drivers/kolab/plugins/kolab_auth/localization/ru_RU.inc b/lib/drivers/kolab/plugins/kolab_auth/localization/ru_RU.inc deleted file mode 100644 index ac9e5a7..0000000 --- a/lib/drivers/kolab/plugins/kolab_auth/localization/ru_RU.inc +++ /dev/null @@ -1,11 +0,0 @@ - diff --git a/lib/drivers/kolab/plugins/libkolab/LICENSE b/lib/drivers/kolab/plugins/libkolab/LICENSE deleted file mode 100644 index dba13ed..0000000 --- a/lib/drivers/kolab/plugins/libkolab/LICENSE +++ /dev/null @@ -1,661 +0,0 @@ - GNU AFFERO GENERAL PUBLIC LICENSE - Version 3, 19 November 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU Affero General Public License is a free, copyleft license for -software and other kinds of works, specifically designed to ensure -cooperation with the community in the case of network server software. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -our General Public Licenses are 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. - - 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. - - Developers that use our General Public Licenses protect your rights -with two steps: (1) assert copyright on the software, and (2) offer -you this License which gives you legal permission to copy, distribute -and/or modify the software. - - A secondary benefit of defending all users' freedom is that -improvements made in alternate versions of the program, if they -receive widespread use, become available for other developers to -incorporate. Many developers of free software are heartened and -encouraged by the resulting cooperation. However, in the case of -software used on network servers, this result may fail to come about. -The GNU General Public License permits making a modified version and -letting the public access it on a server without ever releasing its -source code to the public. - - The GNU Affero General Public License is designed specifically to -ensure that, in such cases, the modified source code becomes available -to the community. It requires the operator of a network server to -provide the source code of the modified version running there to the -users of that server. Therefore, public use of a modified version, on -a publicly accessible server, gives the public access to the source -code of the modified version. - - An older license, called the Affero General Public License and -published by Affero, was designed to accomplish similar goals. This is -a different license, not a version of the Affero GPL, but Affero has -released a new version of the Affero GPL which permits relicensing under -this license. - - 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 Affero 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. Remote Network Interaction; Use with the GNU General Public License. - - Notwithstanding any other provision of this License, if you modify the -Program, your modified version must prominently offer all users -interacting with it remotely through a computer network (if your version -supports such interaction) an opportunity to receive the Corresponding -Source of your version by providing access to the Corresponding Source -from a network server at no charge, through some standard or customary -means of facilitating copying of software. This Corresponding Source -shall include the Corresponding Source for any work covered by version 3 -of the GNU General Public License that is incorporated pursuant to the -following paragraph. - - 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 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 work with which it is combined will remain governed by version -3 of the GNU General Public License. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU Affero 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 Affero 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 Affero 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 Affero 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. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If your software can interact with users remotely through a computer -network, you should also make sure that it provides a way for users to -get its source. For example, if your program is a web application, its -interface could display a "Source" link that leads users to an archive -of the code. There are many ways you could offer source, and different -solutions will be better for different programs; see section 13 for the -specific requirements. - - 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 AGPL, see -. diff --git a/lib/drivers/kolab/plugins/libkolab/README b/lib/drivers/kolab/plugins/libkolab/README deleted file mode 100644 index 2f94839..0000000 --- a/lib/drivers/kolab/plugins/libkolab/README +++ /dev/null @@ -1,28 +0,0 @@ -libkolab plugin to access to Kolab groupware data -================================================= - -The contained library classes establish a connection to the Kolab server -and manage the access to the Kolab groupware objects stored in various -IMAP folders. For reading and writing these objects, the PHP bindings of -the libkolabxml library are used. - - -REQUIREMENTS ------------- -* libkolabxml PHP bindings - - kolabformat.so loaded into PHP - - kolabformat.php placed somewhere in the include_path -* PEAR: HTTP/Request2 -* PEAR: Net/URL2 - - -INSTALLATION ------------- -To use local cache you need to create a dedicated table in Roundcube's database. -To do so, execute the SQL commands in SQL/.initial.sql - - -CONFIGURATION -------------- -Rename config.inc.php.dist to config.inc.php in the plugin folder. -For available configuration options see config.inc.php.dist file. diff --git a/lib/drivers/kolab/plugins/libkolab/SQL/mysql.initial.sql b/lib/drivers/kolab/plugins/libkolab/SQL/mysql.initial.sql deleted file mode 100644 index a1497da..0000000 --- a/lib/drivers/kolab/plugins/libkolab/SQL/mysql.initial.sql +++ /dev/null @@ -1,191 +0,0 @@ -/** - * libkolab database schema - * - * @version 1.1 - * @author Thomas Bruederli - * @licence GNU AGPL - **/ - -/*!40014 SET FOREIGN_KEY_CHECKS=0 */; - -DROP TABLE IF EXISTS `kolab_folders`; - -CREATE TABLE `kolab_folders` ( - `folder_id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, - `resource` VARCHAR(255) NOT NULL, - `type` VARCHAR(32) NOT NULL, - `synclock` INT(10) NOT NULL DEFAULT '0', - `ctag` VARCHAR(40) DEFAULT NULL, - `changed` DATETIME DEFAULT NULL, - `objectcount` BIGINT DEFAULT NULL, - PRIMARY KEY(`folder_id`), - INDEX `resource_type` (`resource`, `type`) -) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; - -DROP TABLE IF EXISTS `kolab_cache`; - -DROP TABLE IF EXISTS `kolab_cache_contact`; - -CREATE TABLE `kolab_cache_contact` ( - `folder_id` BIGINT UNSIGNED NOT NULL, - `msguid` BIGINT UNSIGNED NOT NULL, - `uid` VARCHAR(128) CHARACTER SET ascii NOT NULL, - `created` DATETIME DEFAULT NULL, - `changed` DATETIME DEFAULT NULL, - `data` LONGTEXT NOT NULL, - `xml` LONGBLOB NOT NULL, - `tags` TEXT NOT NULL, - `words` TEXT NOT NULL, - `type` VARCHAR(32) CHARACTER SET ascii NOT NULL, - `name` VARCHAR(255) NOT NULL, - `firstname` VARCHAR(255) NOT NULL, - `surname` VARCHAR(255) NOT NULL, - `email` VARCHAR(255) NOT NULL, - CONSTRAINT `fk_kolab_cache_contact_folder` FOREIGN KEY (`folder_id`) - REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE, - PRIMARY KEY(`folder_id`,`msguid`), - INDEX `contact_type` (`folder_id`,`type`), - INDEX `contact_uid2msguid` (`folder_id`,`uid`,`msguid`) -) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; - -DROP TABLE IF EXISTS `kolab_cache_event`; - -CREATE TABLE `kolab_cache_event` ( - `folder_id` BIGINT UNSIGNED NOT NULL, - `msguid` BIGINT UNSIGNED NOT NULL, - `uid` VARCHAR(128) CHARACTER SET ascii NOT NULL, - `created` DATETIME DEFAULT NULL, - `changed` DATETIME DEFAULT NULL, - `data` LONGTEXT NOT NULL, - `xml` LONGBLOB NOT NULL, - `tags` TEXT NOT NULL, - `words` TEXT NOT NULL, - `dtstart` DATETIME, - `dtend` DATETIME, - CONSTRAINT `fk_kolab_cache_event_folder` FOREIGN KEY (`folder_id`) - REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE, - PRIMARY KEY(`folder_id`,`msguid`), - INDEX `event_uid2msguid` (`folder_id`,`uid`,`msguid`) -) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; - -DROP TABLE IF EXISTS `kolab_cache_task`; - -CREATE TABLE `kolab_cache_task` ( - `folder_id` BIGINT UNSIGNED NOT NULL, - `msguid` BIGINT UNSIGNED NOT NULL, - `uid` VARCHAR(128) CHARACTER SET ascii NOT NULL, - `created` DATETIME DEFAULT NULL, - `changed` DATETIME DEFAULT NULL, - `data` LONGTEXT NOT NULL, - `xml` LONGBLOB NOT NULL, - `tags` TEXT NOT NULL, - `words` TEXT NOT NULL, - `dtstart` DATETIME, - `dtend` DATETIME, - CONSTRAINT `fk_kolab_cache_task_folder` FOREIGN KEY (`folder_id`) - REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE, - PRIMARY KEY(`folder_id`,`msguid`), - INDEX `task_uid2msguid` (`folder_id`,`uid`,`msguid`) -) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; - -DROP TABLE IF EXISTS `kolab_cache_journal`; - -CREATE TABLE `kolab_cache_journal` ( - `folder_id` BIGINT UNSIGNED NOT NULL, - `msguid` BIGINT UNSIGNED NOT NULL, - `uid` VARCHAR(128) CHARACTER SET ascii NOT NULL, - `created` DATETIME DEFAULT NULL, - `changed` DATETIME DEFAULT NULL, - `data` LONGTEXT NOT NULL, - `xml` LONGBLOB NOT NULL, - `tags` TEXT NOT NULL, - `words` TEXT NOT NULL, - `dtstart` DATETIME, - `dtend` DATETIME, - CONSTRAINT `fk_kolab_cache_journal_folder` FOREIGN KEY (`folder_id`) - REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE, - PRIMARY KEY(`folder_id`,`msguid`), - INDEX `journal_uid2msguid` (`folder_id`,`uid`,`msguid`) -) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; - -DROP TABLE IF EXISTS `kolab_cache_note`; - -CREATE TABLE `kolab_cache_note` ( - `folder_id` BIGINT UNSIGNED NOT NULL, - `msguid` BIGINT UNSIGNED NOT NULL, - `uid` VARCHAR(128) CHARACTER SET ascii NOT NULL, - `created` DATETIME DEFAULT NULL, - `changed` DATETIME DEFAULT NULL, - `data` LONGTEXT NOT NULL, - `xml` LONGBLOB NOT NULL, - `tags` TEXT NOT NULL, - `words` TEXT NOT NULL, - CONSTRAINT `fk_kolab_cache_note_folder` FOREIGN KEY (`folder_id`) - REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE, - PRIMARY KEY(`folder_id`,`msguid`), - INDEX `note_uid2msguid` (`folder_id`,`uid`,`msguid`) -) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; - -DROP TABLE IF EXISTS `kolab_cache_file`; - -CREATE TABLE `kolab_cache_file` ( - `folder_id` BIGINT UNSIGNED NOT NULL, - `msguid` BIGINT UNSIGNED NOT NULL, - `uid` VARCHAR(128) CHARACTER SET ascii NOT NULL, - `created` DATETIME DEFAULT NULL, - `changed` DATETIME DEFAULT NULL, - `data` LONGTEXT NOT NULL, - `xml` LONGBLOB NOT NULL, - `tags` TEXT NOT NULL, - `words` TEXT NOT NULL, - `filename` varchar(255) DEFAULT NULL, - CONSTRAINT `fk_kolab_cache_file_folder` FOREIGN KEY (`folder_id`) - REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE, - PRIMARY KEY(`folder_id`,`msguid`), - INDEX `folder_filename` (`folder_id`, `filename`), - INDEX `file_uid2msguid` (`folder_id`,`uid`,`msguid`) -) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; - -DROP TABLE IF EXISTS `kolab_cache_configuration`; - -CREATE TABLE `kolab_cache_configuration` ( - `folder_id` BIGINT UNSIGNED NOT NULL, - `msguid` BIGINT UNSIGNED NOT NULL, - `uid` VARCHAR(128) CHARACTER SET ascii NOT NULL, - `created` DATETIME DEFAULT NULL, - `changed` DATETIME DEFAULT NULL, - `data` LONGTEXT NOT NULL, - `xml` LONGBLOB NOT NULL, - `tags` TEXT NOT NULL, - `words` TEXT NOT NULL, - `type` VARCHAR(32) CHARACTER SET ascii NOT NULL, - CONSTRAINT `fk_kolab_cache_configuration_folder` FOREIGN KEY (`folder_id`) - REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE, - PRIMARY KEY(`folder_id`,`msguid`), - INDEX `configuration_type` (`folder_id`,`type`), - INDEX `configuration_uid2msguid` (`folder_id`,`uid`,`msguid`) -) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; - -DROP TABLE IF EXISTS `kolab_cache_freebusy`; - -CREATE TABLE `kolab_cache_freebusy` ( - `folder_id` BIGINT UNSIGNED NOT NULL, - `msguid` BIGINT UNSIGNED NOT NULL, - `uid` VARCHAR(128) CHARACTER SET ascii NOT NULL, - `created` DATETIME DEFAULT NULL, - `changed` DATETIME DEFAULT NULL, - `data` LONGTEXT NOT NULL, - `xml` LONGBLOB NOT NULL, - `tags` TEXT NOT NULL, - `words` TEXT NOT NULL, - `dtstart` DATETIME, - `dtend` DATETIME, - CONSTRAINT `fk_kolab_cache_freebusy_folder` FOREIGN KEY (`folder_id`) - REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE, - PRIMARY KEY(`folder_id`,`msguid`), - INDEX `freebusy_uid2msguid` (`folder_id`,`uid`,`msguid`) -) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; - -/*!40014 SET FOREIGN_KEY_CHECKS=1 */; - -REPLACE INTO `system` (`name`, `value`) VALUES ('libkolab-version', '2015020600'); diff --git a/lib/drivers/kolab/plugins/libkolab/SQL/mysql/2013011000.sql b/lib/drivers/kolab/plugins/libkolab/SQL/mysql/2013011000.sql deleted file mode 100644 index fe6741a..0000000 --- a/lib/drivers/kolab/plugins/libkolab/SQL/mysql/2013011000.sql +++ /dev/null @@ -1 +0,0 @@ --- empty \ No newline at end of file diff --git a/lib/drivers/kolab/plugins/libkolab/SQL/mysql/2013041900.sql b/lib/drivers/kolab/plugins/libkolab/SQL/mysql/2013041900.sql deleted file mode 100644 index 76577e6..0000000 --- a/lib/drivers/kolab/plugins/libkolab/SQL/mysql/2013041900.sql +++ /dev/null @@ -1,3 +0,0 @@ -DELETE FROM `kolab_cache` WHERE `type` = 'file'; -ALTER TABLE `kolab_cache` ADD `filename` varchar(255) DEFAULT NULL; -ALTER TABLE `kolab_cache` ADD INDEX `resource_filename` (`resource`, `filename`); diff --git a/lib/drivers/kolab/plugins/libkolab/SQL/mysql/2013100400.sql b/lib/drivers/kolab/plugins/libkolab/SQL/mysql/2013100400.sql deleted file mode 100644 index d41d0e1..0000000 --- a/lib/drivers/kolab/plugins/libkolab/SQL/mysql/2013100400.sql +++ /dev/null @@ -1,174 +0,0 @@ -CREATE TABLE `kolab_folders` ( - `folder_id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, - `resource` VARCHAR(255) NOT NULL, - `type` VARCHAR(32) NOT NULL, - `synclock` INT(10) NOT NULL DEFAULT '0', - `ctag` VARCHAR(40) DEFAULT NULL, - PRIMARY KEY(`folder_id`), - INDEX `resource_type` (`resource`, `type`) -) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; - -CREATE TABLE `kolab_cache_contact` ( - `folder_id` BIGINT UNSIGNED NOT NULL, - `msguid` BIGINT UNSIGNED NOT NULL, - `uid` VARCHAR(128) CHARACTER SET ascii NOT NULL, - `created` DATETIME DEFAULT NULL, - `changed` DATETIME DEFAULT NULL, - `data` TEXT NOT NULL, - `xml` TEXT NOT NULL, - `tags` VARCHAR(255) NOT NULL, - `words` TEXT NOT NULL, - `type` VARCHAR(32) CHARACTER SET ascii NOT NULL, - CONSTRAINT `fk_kolab_cache_contact_folder` FOREIGN KEY (`folder_id`) - REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE, - PRIMARY KEY(`folder_id`,`msguid`), - INDEX `contact_type` (`folder_id`,`type`) -) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; - -CREATE TABLE `kolab_cache_event` ( - `folder_id` BIGINT UNSIGNED NOT NULL, - `msguid` BIGINT UNSIGNED NOT NULL, - `uid` VARCHAR(128) CHARACTER SET ascii NOT NULL, - `created` DATETIME DEFAULT NULL, - `changed` DATETIME DEFAULT NULL, - `data` TEXT NOT NULL, - `xml` TEXT NOT NULL, - `tags` VARCHAR(255) NOT NULL, - `words` TEXT NOT NULL, - `dtstart` DATETIME, - `dtend` DATETIME, - CONSTRAINT `fk_kolab_cache_event_folder` FOREIGN KEY (`folder_id`) - REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE, - PRIMARY KEY(`folder_id`,`msguid`) -) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; - -CREATE TABLE `kolab_cache_task` ( - `folder_id` BIGINT UNSIGNED NOT NULL, - `msguid` BIGINT UNSIGNED NOT NULL, - `uid` VARCHAR(128) CHARACTER SET ascii NOT NULL, - `created` DATETIME DEFAULT NULL, - `changed` DATETIME DEFAULT NULL, - `data` TEXT NOT NULL, - `xml` TEXT NOT NULL, - `tags` VARCHAR(255) NOT NULL, - `words` TEXT NOT NULL, - `dtstart` DATETIME, - `dtend` DATETIME, - CONSTRAINT `fk_kolab_cache_task_folder` FOREIGN KEY (`folder_id`) - REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE, - PRIMARY KEY(`folder_id`,`msguid`) -) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; - -CREATE TABLE `kolab_cache_journal` ( - `folder_id` BIGINT UNSIGNED NOT NULL, - `msguid` BIGINT UNSIGNED NOT NULL, - `uid` VARCHAR(128) CHARACTER SET ascii NOT NULL, - `created` DATETIME DEFAULT NULL, - `changed` DATETIME DEFAULT NULL, - `data` TEXT NOT NULL, - `xml` TEXT NOT NULL, - `tags` VARCHAR(255) NOT NULL, - `words` TEXT NOT NULL, - `dtstart` DATETIME, - `dtend` DATETIME, - CONSTRAINT `fk_kolab_cache_journal_folder` FOREIGN KEY (`folder_id`) - REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE, - PRIMARY KEY(`folder_id`,`msguid`) -) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; - -CREATE TABLE `kolab_cache_note` ( - `folder_id` BIGINT UNSIGNED NOT NULL, - `msguid` BIGINT UNSIGNED NOT NULL, - `uid` VARCHAR(128) CHARACTER SET ascii NOT NULL, - `created` DATETIME DEFAULT NULL, - `changed` DATETIME DEFAULT NULL, - `data` TEXT NOT NULL, - `xml` TEXT NOT NULL, - `tags` VARCHAR(255) NOT NULL, - `words` TEXT NOT NULL, - CONSTRAINT `fk_kolab_cache_note_folder` FOREIGN KEY (`folder_id`) - REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE, - PRIMARY KEY(`folder_id`,`msguid`) -) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; - -CREATE TABLE `kolab_cache_file` ( - `folder_id` BIGINT UNSIGNED NOT NULL, - `msguid` BIGINT UNSIGNED NOT NULL, - `uid` VARCHAR(128) CHARACTER SET ascii NOT NULL, - `created` DATETIME DEFAULT NULL, - `changed` DATETIME DEFAULT NULL, - `data` TEXT NOT NULL, - `xml` TEXT NOT NULL, - `tags` VARCHAR(255) NOT NULL, - `words` TEXT NOT NULL, - `filename` varchar(255) DEFAULT NULL, - CONSTRAINT `fk_kolab_cache_file_folder` FOREIGN KEY (`folder_id`) - REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE, - PRIMARY KEY(`folder_id`,`msguid`), - INDEX `folder_filename` (`folder_id`, `filename`) -) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; - -CREATE TABLE `kolab_cache_configuration` ( - `folder_id` BIGINT UNSIGNED NOT NULL, - `msguid` BIGINT UNSIGNED NOT NULL, - `uid` VARCHAR(128) CHARACTER SET ascii NOT NULL, - `created` DATETIME DEFAULT NULL, - `changed` DATETIME DEFAULT NULL, - `data` TEXT NOT NULL, - `xml` TEXT NOT NULL, - `tags` VARCHAR(255) NOT NULL, - `words` TEXT NOT NULL, - `type` VARCHAR(32) CHARACTER SET ascii NOT NULL, - CONSTRAINT `fk_kolab_cache_configuration_folder` FOREIGN KEY (`folder_id`) - REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE, - PRIMARY KEY(`folder_id`,`msguid`), - INDEX `configuration_type` (`folder_id`,`type`) -) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; - -CREATE TABLE `kolab_cache_freebusy` ( - `folder_id` BIGINT UNSIGNED NOT NULL, - `msguid` BIGINT UNSIGNED NOT NULL, - `uid` VARCHAR(128) CHARACTER SET ascii NOT NULL, - `created` DATETIME DEFAULT NULL, - `changed` DATETIME DEFAULT NULL, - `data` TEXT NOT NULL, - `xml` TEXT NOT NULL, - `tags` VARCHAR(255) NOT NULL, - `words` TEXT NOT NULL, - `dtstart` DATETIME, - `dtend` DATETIME, - CONSTRAINT `fk_kolab_cache_freebusy_folder` FOREIGN KEY (`folder_id`) - REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE, - PRIMARY KEY(`folder_id`,`msguid`) -) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; - - --- Migrate data from old kolab_cache table - -INSERT INTO kolab_folders (resource, type) - SELECT DISTINCT resource, type - FROM kolab_cache WHERE type IN ('event','contact','task','file'); - -INSERT INTO kolab_cache_event (folder_id, msguid, uid, created, changed, data, xml, tags, words, dtstart, dtend) - SELECT kolab_folders.folder_id, msguid, uid, created, changed, data, xml, tags, words, dtstart, dtend - FROM kolab_cache LEFT JOIN kolab_folders ON (kolab_folders.resource = kolab_cache.resource) - WHERE kolab_cache.type = 'event' AND kolab_folders.folder_id IS NOT NULL; - -INSERT INTO kolab_cache_task (folder_id, msguid, uid, created, changed, data, xml, tags, words, dtstart, dtend) - SELECT kolab_folders.folder_id, msguid, uid, created, changed, data, xml, tags, words, dtstart, dtend - FROM kolab_cache LEFT JOIN kolab_folders ON (kolab_folders.resource = kolab_cache.resource) - WHERE kolab_cache.type = 'task' AND kolab_folders.folder_id IS NOT NULL; - -INSERT INTO kolab_cache_contact (folder_id, msguid, uid, created, changed, data, xml, tags, words, type) - SELECT kolab_folders.folder_id, msguid, uid, created, changed, data, xml, tags, words, kolab_cache.type - FROM kolab_cache LEFT JOIN kolab_folders ON (kolab_folders.resource = kolab_cache.resource) - WHERE kolab_cache.type IN ('contact','distribution-list') AND kolab_folders.folder_id IS NOT NULL; - -INSERT INTO kolab_cache_file (folder_id, msguid, uid, created, changed, data, xml, tags, words, filename) - SELECT kolab_folders.folder_id, msguid, uid, created, changed, data, xml, tags, words, filename - FROM kolab_cache LEFT JOIN kolab_folders ON (kolab_folders.resource = kolab_cache.resource) - WHERE kolab_cache.type = 'file' AND kolab_folders.folder_id IS NOT NULL; - - -DROP TABLE IF EXISTS `kolab_cache`; - diff --git a/lib/drivers/kolab/plugins/libkolab/SQL/mysql/2013110400.sql b/lib/drivers/kolab/plugins/libkolab/SQL/mysql/2013110400.sql deleted file mode 100644 index 5b7a9ef..0000000 --- a/lib/drivers/kolab/plugins/libkolab/SQL/mysql/2013110400.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE `kolab_cache_contact` CHANGE `xml` `xml` LONGTEXT NOT NULL; diff --git a/lib/drivers/kolab/plugins/libkolab/SQL/mysql/2013121100.sql b/lib/drivers/kolab/plugins/libkolab/SQL/mysql/2013121100.sql deleted file mode 100644 index 8cab5ef..0000000 --- a/lib/drivers/kolab/plugins/libkolab/SQL/mysql/2013121100.sql +++ /dev/null @@ -1,13 +0,0 @@ --- well, these deletes are really optional --- we can clear all caches or only contacts/events/tasks --- the issue we're fixing here was about contacts (Bug #2662) -DELETE FROM `kolab_folders` WHERE `type` IN ('contact', 'event', 'task'); - -ALTER TABLE `kolab_cache_contact` CHANGE `xml` `xml` LONGBLOB NOT NULL; -ALTER TABLE `kolab_cache_event` CHANGE `xml` `xml` LONGBLOB NOT NULL; -ALTER TABLE `kolab_cache_task` CHANGE `xml` `xml` LONGBLOB NOT NULL; -ALTER TABLE `kolab_cache_journal` CHANGE `xml` `xml` LONGBLOB NOT NULL; -ALTER TABLE `kolab_cache_note` CHANGE `xml` `xml` LONGBLOB NOT NULL; -ALTER TABLE `kolab_cache_file` CHANGE `xml` `xml` LONGBLOB NOT NULL; -ALTER TABLE `kolab_cache_configuration` CHANGE `xml` `xml` LONGBLOB NOT NULL; -ALTER TABLE `kolab_cache_freebusy` CHANGE `xml` `xml` LONGBLOB NOT NULL; diff --git a/lib/drivers/kolab/plugins/libkolab/SQL/mysql/2014021000.sql b/lib/drivers/kolab/plugins/libkolab/SQL/mysql/2014021000.sql deleted file mode 100644 index 31ce699..0000000 --- a/lib/drivers/kolab/plugins/libkolab/SQL/mysql/2014021000.sql +++ /dev/null @@ -1,9 +0,0 @@ -ALTER TABLE `kolab_cache_contact` ADD `name` VARCHAR(255) NOT NULL, - ADD `firstname` VARCHAR(255) NOT NULL, - ADD `surname` VARCHAR(255) NOT NULL, - ADD `email` VARCHAR(255) NOT NULL; - --- updating or clearing all contacts caches is required. --- either run `bin/modcache.sh update --type=contact` or execute the following query: --- DELETE FROM `kolab_folders` WHERE `type`='contact'; - diff --git a/lib/drivers/kolab/plugins/libkolab/SQL/mysql/2014032700.sql b/lib/drivers/kolab/plugins/libkolab/SQL/mysql/2014032700.sql deleted file mode 100644 index a45fae3..0000000 --- a/lib/drivers/kolab/plugins/libkolab/SQL/mysql/2014032700.sql +++ /dev/null @@ -1,8 +0,0 @@ -ALTER TABLE `kolab_cache_configuration` ADD INDEX `configuration_uid2msguid` (`folder_id`, `uid`, `msguid`); -ALTER TABLE `kolab_cache_contact` ADD INDEX `contact_uid2msguid` (`folder_id`, `uid`, `msguid`); -ALTER TABLE `kolab_cache_event` ADD INDEX `event_uid2msguid` (`folder_id`, `uid`, `msguid`); -ALTER TABLE `kolab_cache_task` ADD INDEX `task_uid2msguid` (`folder_id`, `uid`, `msguid`); -ALTER TABLE `kolab_cache_journal` ADD INDEX `journal_uid2msguid` (`folder_id`, `uid`, `msguid`); -ALTER TABLE `kolab_cache_note` ADD INDEX `note_uid2msguid` (`folder_id`, `uid`, `msguid`); -ALTER TABLE `kolab_cache_file` ADD INDEX `file_uid2msguid` (`folder_id`, `uid`, `msguid`); -ALTER TABLE `kolab_cache_freebusy` ADD INDEX `freebusy_uid2msguid` (`folder_id`, `uid`, `msguid`); diff --git a/lib/drivers/kolab/plugins/libkolab/SQL/mysql/2014040900.sql b/lib/drivers/kolab/plugins/libkolab/SQL/mysql/2014040900.sql deleted file mode 100644 index cfcaa9d..0000000 --- a/lib/drivers/kolab/plugins/libkolab/SQL/mysql/2014040900.sql +++ /dev/null @@ -1,16 +0,0 @@ -ALTER TABLE `kolab_cache_contact` CHANGE `data` `data` LONGTEXT NOT NULL; -ALTER TABLE `kolab_cache_event` CHANGE `data` `data` LONGTEXT NOT NULL; -ALTER TABLE `kolab_cache_task` CHANGE `data` `data` LONGTEXT NOT NULL; -ALTER TABLE `kolab_cache_journal` CHANGE `data` `data` LONGTEXT NOT NULL; -ALTER TABLE `kolab_cache_note` CHANGE `data` `data` LONGTEXT NOT NULL; -ALTER TABLE `kolab_cache_file` CHANGE `data` `data` LONGTEXT NOT NULL; -ALTER TABLE `kolab_cache_configuration` CHANGE `data` `data` LONGTEXT NOT NULL; -ALTER TABLE `kolab_cache_freebusy` CHANGE `data` `data` LONGTEXT NOT NULL; - --- rebuild cache entries for xcal objects with alarms -DELETE FROM `kolab_cache_event` WHERE tags LIKE '% x-has-alarms %'; -DELETE FROM `kolab_cache_task` WHERE tags LIKE '% x-has-alarms %'; - --- force cache synchronization -UPDATE `kolab_folders` SET ctag='' WHERE `type` IN ('event','task'); - diff --git a/lib/drivers/kolab/plugins/libkolab/SQL/mysql/2014112700.sql b/lib/drivers/kolab/plugins/libkolab/SQL/mysql/2014112700.sql deleted file mode 100644 index 90c77b8..0000000 --- a/lib/drivers/kolab/plugins/libkolab/SQL/mysql/2014112700.sql +++ /dev/null @@ -1,2 +0,0 @@ --- delete cache entries for old folder identifiers -DELETE FROM `kolab_folders` WHERE `resource` LIKE 'imap://anonymous@%'; diff --git a/lib/drivers/kolab/plugins/libkolab/SQL/mysql/2015011600.sql b/lib/drivers/kolab/plugins/libkolab/SQL/mysql/2015011600.sql deleted file mode 100644 index be523ae..0000000 --- a/lib/drivers/kolab/plugins/libkolab/SQL/mysql/2015011600.sql +++ /dev/null @@ -1,8 +0,0 @@ -ALTER TABLE `kolab_cache_contact` MODIFY `tags` text NOT NULL; -ALTER TABLE `kolab_cache_event` MODIFY `tags` text NOT NULL; -ALTER TABLE `kolab_cache_task` MODIFY `tags` text NOT NULL; -ALTER TABLE `kolab_cache_journal` MODIFY `tags` text NOT NULL; -ALTER TABLE `kolab_cache_note` MODIFY `tags` text NOT NULL; -ALTER TABLE `kolab_cache_file` MODIFY `tags` text NOT NULL; -ALTER TABLE `kolab_cache_configuration` MODIFY `tags` text NOT NULL; -ALTER TABLE `kolab_cache_freebusy` MODIFY `tags` text NOT NULL; diff --git a/lib/drivers/kolab/plugins/libkolab/SQL/mysql/2015020600.sql b/lib/drivers/kolab/plugins/libkolab/SQL/mysql/2015020600.sql deleted file mode 100644 index d9077a0..0000000 --- a/lib/drivers/kolab/plugins/libkolab/SQL/mysql/2015020600.sql +++ /dev/null @@ -1,4 +0,0 @@ --- improve cache synchronization (#3933) -ALTER TABLE `kolab_folders` - ADD `changed` DATETIME DEFAULT NULL, - ADD `objectcount` BIGINT DEFAULT NULL; diff --git a/lib/drivers/kolab/plugins/libkolab/SQL/oracle.initial.sql b/lib/drivers/kolab/plugins/libkolab/SQL/oracle.initial.sql deleted file mode 100644 index cf1fae5..0000000 --- a/lib/drivers/kolab/plugins/libkolab/SQL/oracle.initial.sql +++ /dev/null @@ -1,186 +0,0 @@ -/** - * libkolab database schema - * - * @version 1.1 - * @author Aleksander Machniak - * @licence GNU AGPL - **/ - - -CREATE TABLE "kolab_folders" ( - "folder_id" number NOT NULL PRIMARY KEY, - "resource" VARCHAR(255) NOT NULL, - "type" VARCHAR(32) NOT NULL, - "synclock" integer DEFAULT 0 NOT NULL, - "ctag" VARCHAR(40) DEFAULT NULL, - "changed" timestamp DEFAULT NULL, - "objectcount" number DEFAULT NULL -); - -CREATE INDEX "kolab_folders_resource_idx" ON "kolab_folders" ("resource", "type"); - -CREATE SEQUENCE "kolab_folders_seq" - START WITH 1 INCREMENT BY 1 NOMAXVALUE; - -CREATE TRIGGER "kolab_folders_seq_trig" -BEFORE INSERT ON "kolab_folders" FOR EACH ROW -BEGIN - :NEW."folder_id" := "kolab_folders_seq".nextval; -END; -/ - -CREATE TABLE "kolab_cache_contact" ( - "folder_id" number NOT NULL - REFERENCES "kolab_folders" ("folder_id") ON DELETE CASCADE, - "msguid" number NOT NULL, - "uid" varchar(128) NOT NULL, - "created" timestamp DEFAULT NULL, - "changed" timestamp DEFAULT NULL, - "data" clob NOT NULL, - "xml" clob NOT NULL, - "tags" clob DEFAULT NULL, - "words" clob DEFAULT NULL, - "type" varchar(32) NOT NULL, - "name" varchar(255) DEFAULT NULL, - "firstname" varchar(255) DEFAULT NULL, - "surname" varchar(255) DEFAULT NULL, - "email" varchar(255) DEFAULT NULL, - PRIMARY KEY ("folder_id", "msguid") -); - -CREATE INDEX "kolab_cache_contact_type_idx" ON "kolab_cache_contact" ("folder_id", "type"); -CREATE INDEX "kolab_cache_contact_uid2msguid" ON "kolab_cache_contact" ("folder_id", "uid", "msguid"); - - -CREATE TABLE "kolab_cache_event" ( - "folder_id" number NOT NULL - REFERENCES "kolab_folders" ("folder_id") ON DELETE CASCADE, - "msguid" number NOT NULL, - "uid" varchar(128) NOT NULL, - "created" timestamp DEFAULT NULL, - "changed" timestamp DEFAULT NULL, - "data" clob NOT NULL, - "xml" clob NOT NULL, - "tags" clob DEFAULT NULL, - "words" clob DEFAULT NULL, - "dtstart" timestamp DEFAULT NULL, - "dtend" timestamp DEFAULT NULL, - PRIMARY KEY ("folder_id", "msguid") -); - -CREATE INDEX "kolab_cache_event_uid2msguid" ON "kolab_cache_event" ("folder_id", "uid", "msguid"); - - -CREATE TABLE "kolab_cache_task" ( - "folder_id" number NOT NULL - REFERENCES "kolab_folders" ("folder_id") ON DELETE CASCADE, - "msguid" number NOT NULL, - "uid" varchar(128) NOT NULL, - "created" timestamp DEFAULT NULL, - "changed" timestamp DEFAULT NULL, - "data" clob NOT NULL, - "xml" clob NOT NULL, - "tags" clob DEFAULT NULL, - "words" clob DEFAULT NULL, - "dtstart" timestamp DEFAULT NULL, - "dtend" timestamp DEFAULT NULL, - PRIMARY KEY ("folder_id", "msguid") -); - -CREATE INDEX "kolab_cache_task_uid2msguid" ON "kolab_cache_task" ("folder_id", "uid", "msguid"); - - -CREATE TABLE "kolab_cache_journal" ( - "folder_id" number NOT NULL - REFERENCES "kolab_folders" ("folder_id") ON DELETE CASCADE, - "msguid" number NOT NULL, - "uid" varchar(128) NOT NULL, - "created" timestamp DEFAULT NULL, - "changed" timestamp DEFAULT NULL, - "data" clob NOT NULL, - "xml" clob NOT NULL, - "tags" clob DEFAULT NULL, - "words" clob DEFAULT NULL, - "dtstart" timestamp DEFAULT NULL, - "dtend" timestamp DEFAULT NULL, - PRIMARY KEY ("folder_id", "msguid") -); - -CREATE INDEX "kolab_cache_journal_uid2msguid" ON "kolab_cache_journal" ("folder_id", "uid", "msguid"); - - -CREATE TABLE "kolab_cache_note" ( - "folder_id" number NOT NULL - REFERENCES "kolab_folders" ("folder_id") ON DELETE CASCADE, - "msguid" number NOT NULL, - "uid" varchar(128) NOT NULL, - "created" timestamp DEFAULT NULL, - "changed" timestamp DEFAULT NULL, - "data" clob NOT NULL, - "xml" clob NOT NULL, - "tags" clob DEFAULT NULL, - "words" clob DEFAULT NULL, - PRIMARY KEY ("folder_id", "msguid") -); - -CREATE INDEX "kolab_cache_note_uid2msguid" ON "kolab_cache_note" ("folder_id", "uid", "msguid"); - - -CREATE TABLE "kolab_cache_file" ( - "folder_id" number NOT NULL - REFERENCES "kolab_folders" ("folder_id") ON DELETE CASCADE, - "msguid" number NOT NULL, - "uid" varchar(128) NOT NULL, - "created" timestamp DEFAULT NULL, - "changed" timestamp DEFAULT NULL, - "data" clob NOT NULL, - "xml" clob NOT NULL, - "tags" clob DEFAULT NULL, - "words" clob DEFAULT NULL, - "filename" varchar(255) DEFAULT NULL, - PRIMARY KEY ("folder_id", "msguid") -); - -CREATE INDEX "kolab_cache_file_filename" ON "kolab_cache_file" ("folder_id", "filename"); -CREATE INDEX "kolab_cache_file_uid2msguid" ON "kolab_cache_file" ("folder_id", "uid", "msguid"); - - -CREATE TABLE "kolab_cache_configuration" ( - "folder_id" number NOT NULL - REFERENCES "kolab_folders" ("folder_id") ON DELETE CASCADE, - "msguid" number NOT NULL, - "uid" varchar(128) NOT NULL, - "created" timestamp DEFAULT NULL, - "changed" timestamp DEFAULT NULL, - "data" clob NOT NULL, - "xml" clob NOT NULL, - "tags" clob DEFAULT NULL, - "words" clob DEFAULT NULL, - "type" varchar(32) NOT NULL, - PRIMARY KEY ("folder_id", "msguid") -); - -CREATE INDEX "kolab_cache_config_type" ON "kolab_cache_configuration" ("folder_id", "type"); -CREATE INDEX "kolab_cache_config_uid2msguid" ON "kolab_cache_configuration" ("folder_id", "uid", "msguid"); - - -CREATE TABLE "kolab_cache_freebusy" ( - "folder_id" number NOT NULL - REFERENCES "kolab_folders" ("folder_id") ON DELETE CASCADE, - "msguid" number NOT NULL, - "uid" varchar(128) NOT NULL, - "created" timestamp DEFAULT NULL, - "changed" timestamp DEFAULT NULL, - "data" clob NOT NULL, - "xml" clob NOT NULL, - "tags" clob DEFAULT NULL, - "words" clob DEFAULT NULL, - "dtstart" timestamp DEFAULT NULL, - "dtend" timestamp DEFAULT NULL, - PRIMARY KEY("folder_id", "msguid") -); - -CREATE INDEX "kolab_cache_fb_uid2msguid" ON "kolab_cache_freebusy" ("folder_id", "uid", "msguid"); - - -INSERT INTO "system" ("name", "value") VALUES ('libkolab-version', '2015020600'); diff --git a/lib/drivers/kolab/plugins/libkolab/SQL/oracle/2015011600.sql b/lib/drivers/kolab/plugins/libkolab/SQL/oracle/2015011600.sql deleted file mode 100644 index 69f7953..0000000 --- a/lib/drivers/kolab/plugins/libkolab/SQL/oracle/2015011600.sql +++ /dev/null @@ -1,40 +0,0 @@ --- direct change from varchar to clob does not work, need temp column (#4257) -ALTER TABLE "kolab_cache_contact" ADD "tags1" clob DEFAULT NULL; -UPDATE "kolab_cache_contact" SET "tags1" = "tags"; -ALTER TABLE "kolab_cache_contact" DROP COLUMN "tags"; -ALTER TABLE "kolab_cache_contact" RENAME COLUMN "tags1" TO "tags"; - -ALTER TABLE "kolab_cache_event" ADD "tags1" clob DEFAULT NULL; -UPDATE "kolab_cache_event" SET "tags1" = "tags"; -ALTER TABLE "kolab_cache_event" DROP COLUMN "tags"; -ALTER TABLE "kolab_cache_event" RENAME COLUMN "tags1" TO "tags"; - -ALTER TABLE "kolab_cache_task" ADD "tags1" clob DEFAULT NULL; -UPDATE "kolab_cache_task" SET "tags1" = "tags"; -ALTER TABLE "kolab_cache_task" DROP COLUMN "tags"; -ALTER TABLE "kolab_cache_task" RENAME COLUMN "tags1" TO "tags"; - -ALTER TABLE "kolab_cache_journal" ADD "tags1" clob DEFAULT NULL; -UPDATE "kolab_cache_journal" SET "tags1" = "tags"; -ALTER TABLE "kolab_cache_journal" DROP COLUMN "tags"; -ALTER TABLE "kolab_cache_journal" RENAME COLUMN "tags1" TO "tags"; - -ALTER TABLE "kolab_cache_note" ADD "tags1" clob DEFAULT NULL; -UPDATE "kolab_cache_note" SET "tags1" = "tags"; -ALTER TABLE "kolab_cache_note" DROP COLUMN "tags"; -ALTER TABLE "kolab_cache_note" RENAME COLUMN "tags1" TO "tags"; - -ALTER TABLE "kolab_cache_file" ADD "tags1" clob DEFAULT NULL; -UPDATE "kolab_cache_file" SET "tags1" = "tags"; -ALTER TABLE "kolab_cache_file" DROP COLUMN "tags"; -ALTER TABLE "kolab_cache_file" RENAME COLUMN "tags1" TO "tags"; - -ALTER TABLE "kolab_cache_configuration" ADD "tags1" clob DEFAULT NULL; -UPDATE "kolab_cache_configuration" SET "tags1" = "tags"; -ALTER TABLE "kolab_cache_configuration" DROP COLUMN "tags"; -ALTER TABLE "kolab_cache_configuration" RENAME COLUMN "tags1" TO "tags"; - -ALTER TABLE "kolab_cache_freebusy" ADD "tags1" clob DEFAULT NULL; -UPDATE "kolab_cache_freebusy" SET "tags1" = "tags"; -ALTER TABLE "kolab_cache_freebusy" DROP COLUMN "tags"; -ALTER TABLE "kolab_cache_freebusy" RENAME COLUMN "tags1" TO "tags"; diff --git a/lib/drivers/kolab/plugins/libkolab/SQL/oracle/2015020600.sql b/lib/drivers/kolab/plugins/libkolab/SQL/oracle/2015020600.sql deleted file mode 100644 index a605649..0000000 --- a/lib/drivers/kolab/plugins/libkolab/SQL/oracle/2015020600.sql +++ /dev/null @@ -1,4 +0,0 @@ --- improve cache synchronization (#3933) -ALTER TABLE "kolab_folders" - ADD "changed" timestamp DEFAULT NULL, - ADD "objectcount" number DEFAULT NULL; diff --git a/lib/drivers/kolab/plugins/libkolab/SQL/postgres.initial.sql b/lib/drivers/kolab/plugins/libkolab/SQL/postgres.initial.sql deleted file mode 100644 index e06346c..0000000 --- a/lib/drivers/kolab/plugins/libkolab/SQL/postgres.initial.sql +++ /dev/null @@ -1,31 +0,0 @@ -/** - * libkolab database schema - * - * @version @package_version@ - * @author Sidlyarenko Sergey - * @licence GNU AGPL - **/ - -DROP TABLE IF EXISTS kolab_cache; - -CREATE TABLE kolab_cache ( - resource character varying(255) NOT NULL, - type character varying(32) NOT NULL, - msguid NUMERIC(20) NOT NULL, - uid character varying(128) NOT NULL, - created timestamp without time zone DEFAULT NULL, - changed timestamp without time zone DEFAULT NULL, - data text NOT NULL, - xml text NOT NULL, - dtstart timestamp without time zone, - dtend timestamp without time zone, - tags character varying(255) NOT NULL, - words text NOT NULL, - filename character varying(255) DEFAULT NULL, - PRIMARY KEY(resource, type, msguid) -); - -CREATE INDEX kolab_cache_resource_filename_idx ON kolab_cache (resource, filename); - - -INSERT INTO system (name, value) VALUES ('libkolab-version', '2013041900'); diff --git a/lib/drivers/kolab/plugins/libkolab/SQL/sqlite.initial.sql b/lib/drivers/kolab/plugins/libkolab/SQL/sqlite.initial.sql deleted file mode 100644 index 6f6f3c9..0000000 --- a/lib/drivers/kolab/plugins/libkolab/SQL/sqlite.initial.sql +++ /dev/null @@ -1,159 +0,0 @@ -/** - * libkolab database schema - * - * @version 1.1 - * @author Thomas Bruederli - * @licence GNU AGPL - **/ - -CREATE TABLE kolab_folders ( - folder_id INTEGER NOT NULL PRIMARY KEY, - resource VARCHAR(255) NOT NULL, - type VARCHAR(32) NOT NULL, - synclock INTEGER NOT NULL DEFAULT '0', - ctag VARCHAR(40) DEFAULT NULL, - changed DATETIME DEFAULT NULL, - objectcount INTEGER DEFAULT NULL -); - -CREATE INDEX ix_resource_type ON kolab_folders(resource, type); - -CREATE TABLE kolab_cache_contact ( - folder_id INTEGER NOT NULL, - msguid INTEGER NOT NULL, - uid VARCHAR(128) NOT NULL, - created DATETIME DEFAULT NULL, - changed DATETIME DEFAULT NULL, - data TEXT NOT NULL, - xml TEXT NOT NULL, - tags TEXT NOT NULL, - words TEXT NOT NULL, - type VARCHAR(32) NOT NULL, - name VARCHAR(255) NOT NULL, - firstname VARCHAR(255) NOT NULL, - surname VARCHAR(255) NOT NULL, - email VARCHAR(255) NOT NULL, - PRIMARY KEY(folder_id,msguid) -); - -CREATE INDEX ix_contact_type ON kolab_cache_contact(folder_id,type); -CREATE INDEX ix_contact_uid2msguid ON kolab_cache_contact(folder_id,uid,msguid); - -CREATE TABLE kolab_cache_event ( - folder_id INTEGER NOT NULL, - msguid INTEGER NOT NULL, - uid VARCHAR(128) NOT NULL, - created DATETIME DEFAULT NULL, - changed DATETIME DEFAULT NULL, - data TEXT NOT NULL, - xml TEXT NOT NULL, - tags TEXT NOT NULL, - words TEXT NOT NULL, - dtstart DATETIME, - dtend DATETIME, - PRIMARY KEY(folder_id,msguid) -); - -CREATE INDEX ix_event_uid2msguid ON kolab_cache_event(folder_id,uid,msguid); - -CREATE TABLE kolab_cache_task ( - folder_id INTEGER NOT NULL, - msguid INTEGER NOT NULL, - uid VARCHAR(128) NOT NULL, - created DATETIME DEFAULT NULL, - changed DATETIME DEFAULT NULL, - data TEXT NOT NULL, - xml TEXT NOT NULL, - tags TEXT NOT NULL, - words TEXT NOT NULL, - dtstart DATETIME, - dtend DATETIME, - PRIMARY KEY(folder_id,msguid) -); - -CREATE INDEX ix_task_uid2msguid ON kolab_cache_task(folder_id,uid,msguid); - -CREATE TABLE kolab_cache_journal ( - folder_id INTEGER NOT NULL, - msguid INTEGER NOT NULL, - uid VARCHAR(128) NOT NULL, - created DATETIME DEFAULT NULL, - changed DATETIME DEFAULT NULL, - data TEXT NOT NULL, - xml TEXT NOT NULL, - tags TEXT NOT NULL, - words TEXT NOT NULL, - dtstart DATETIME, - dtend DATETIME, - PRIMARY KEY(folder_id,msguid) -); - -CREATE INDEX ix_journal_uid2msguid ON kolab_cache_journal(folder_id,uid,msguid); - -CREATE TABLE kolab_cache_note ( - folder_id INTEGER NOT NULL, - msguid INTEGER NOT NULL, - uid VARCHAR(128) NOT NULL, - created DATETIME DEFAULT NULL, - changed DATETIME DEFAULT NULL, - data TEXT NOT NULL, - xml TEXT NOT NULL, - tags TEXT NOT NULL, - words TEXT NOT NULL, - PRIMARY KEY(folder_id,msguid) -); - -CREATE INDEX ix_note_uid2msguid ON kolab_cache_note(folder_id,uid,msguid); - -CREATE TABLE kolab_cache_file ( - folder_id INTEGER NOT NULL, - msguid INTEGER NOT NULL, - uid VARCHAR(128) NOT NULL, - created DATETIME DEFAULT NULL, - changed DATETIME DEFAULT NULL, - data TEXT NOT NULL, - xml TEXT NOT NULL, - tags TEXT NOT NULL, - words TEXT NOT NULL, - filename varchar(255) DEFAULT NULL, - PRIMARY KEY(folder_id,msguid) -); - -CREATE INDEX ix_folder_filename ON kolab_cache_file(folder_id,filename); -CREATE INDEX ix_file_uid2msguid ON kolab_cache_file(folder_id,uid,msguid); - -CREATE TABLE kolab_cache_configuration ( - folder_id INTEGER NOT NULL, - msguid INTEGER NOT NULL, - uid VARCHAR(128) NOT NULL, - created DATETIME DEFAULT NULL, - changed DATETIME DEFAULT NULL, - data TEXT NOT NULL, - xml TEXT NOT NULL, - tags TEXT NOT NULL, - words TEXT NOT NULL, - type VARCHAR(32) NOT NULL, - PRIMARY KEY(folder_id,msguid) -); - -CREATE INDEX ix_configuration_type ON kolab_cache_configuration(folder_id,type); -CREATE INDEX ix_configuration_uid2msguid ON kolab_cache_configuration(folder_id,uid,msguid); - -CREATE TABLE kolab_cache_freebusy ( - folder_id INTEGER NOT NULL, - msguid INTEGER NOT NULL, - uid VARCHAR(128) NOT NULL, - created DATETIME DEFAULT NULL, - changed DATETIME DEFAULT NULL, - data TEXT NOT NULL, - xml TEXT NOT NULL, - tags TEXT NOT NULL, - words TEXT NOT NULL, - dtstart DATETIME, - dtend DATETIME, - PRIMARY KEY(folder_id,msguid) -); - -CREATE INDEX ix_freebusy_uid2msguid ON kolab_cache_freebusy(folder_id,uid,msguid); - -INSERT INTO system (name, value) VALUES ('libkolab-version', '2015020600'); diff --git a/lib/drivers/kolab/plugins/libkolab/UPGRADING b/lib/drivers/kolab/plugins/libkolab/UPGRADING deleted file mode 100644 index e7f04d8..0000000 --- a/lib/drivers/kolab/plugins/libkolab/UPGRADING +++ /dev/null @@ -1,9 +0,0 @@ -UPGRADING instructions -====================== - -To update database schema please run in Roundcube bin/ directory: - -updatedb.sh --package=libkolab --version= --dir=../plugins/libkolab/SQL - -[*] Replace with Roundcube version e.g. 0.7.3 -[*] Roundcube should be upgraded before plugin upgrades diff --git a/lib/drivers/kolab/plugins/libkolab/bin/modcache.sh b/lib/drivers/kolab/plugins/libkolab/bin/modcache.sh deleted file mode 100755 index 8208b6c..0000000 --- a/lib/drivers/kolab/plugins/libkolab/bin/modcache.sh +++ /dev/null @@ -1,238 +0,0 @@ -#!/usr/bin/env php - - * - * 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 Affero 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -define('INSTALL_PATH', realpath('.') . '/' ); -ini_set('display_errors', 1); - -if (!file_exists(INSTALL_PATH . 'program/include/clisetup.php')) - die("Execute this from the Roundcube installation dir!\n\n"); - -require_once INSTALL_PATH . 'program/include/clisetup.php'; - -function print_usage() -{ - print "Usage: modcache.sh ACTION [OPTIONS] [USERNAME ARGS ...]\n"; - print "Possible actions are: expunge, clear, prewarm\n"; - print "-a, --all Clear/expunge all caches\n"; - print "-h, --host IMAP host name\n"; - print "-u, --user IMAP user name to authenticate\n"; - print "-t, --type Object types to clear/expunge cache\n"; - print "-l, --limit Limit the number of records to be expunged\n"; -} - -// read arguments -$opts = rcube_utils::get_opt(array( - 'a' => 'all', - 'h' => 'host', - 'u' => 'user', - 'p' => 'password', - 't' => 'type', - 'l' => 'limit', - 'v' => 'verbose', -)); - -$opts['username'] = !empty($opts[1]) ? $opts[1] : $opts['user']; -$action = $opts[0]; - -$rcmail = rcube::get_instance(rcube::INIT_WITH_DB | rcube::INIT_WITH_PLUGINS); - - -// connect to database -$db = $rcmail->get_dbh(); -$db->db_connect('w'); -if (!$db->is_connected() || $db->is_error()) - die("No DB connection\n"); - -ini_set('display_errors', 1); - -// All supported object types -$all_types = array('contact','configuration','event','file','journal','note','task'); - -/* - * Script controller - */ -switch (strtolower($action)) { - -/* - * Clear/expunge all cache records - */ -case 'expunge': - $folder_types = $opts['type'] ? explode(',', $opts['type']) : $all_types; - $folder_types_db = array_map(array($db, 'quote'), $folder_types); - $expire = strtotime(!empty($opts[2]) ? $opts[2] : 'now - 10 days'); - $sql_where = "type IN (" . join(',', $folder_types_db) . ")"; - - if ($opts['username']) { - $sql_where .= ' AND resource LIKE ?'; - } - - $sql_query = "DELETE FROM %s WHERE folder_id IN (SELECT folder_id FROM kolab_folders WHERE $sql_where) AND created <= " . $db->quote(date('Y-m-d 00:00:00', $expire)); - if ($opts['limit']) { - $sql_query .= ' LIMIT ' . intval($opts['limit']); - } - foreach ($folder_types as $type) { - $table_name = 'kolab_cache_' . $type; - $db->query(sprintf($sql_query, $table_name), resource_prefix($opts).'%'); - echo $db->affected_rows() . " records deleted from '$table_name'\n"; - } - - $db->query("UPDATE kolab_folders SET ctag='' WHERE $sql_where", resource_prefix($opts).'%'); - break; - -case 'clear': - $folder_types = $opts['type'] ? explode(',', $opts['type']) : $all_types; - $folder_types_db = array_map(array($db, 'quote'), $folder_types); - - if ($opts['all']) { - $sql_query = "DELETE FROM kolab_folders WHERE 1"; - } - else if ($opts['username']) { - $sql_query = "DELETE FROM kolab_folders WHERE type IN (" . join(',', $folder_types_db) . ") AND resource LIKE ?"; - } - - if ($sql_query) { - $db->query($sql_query, resource_prefix($opts).'%'); - echo $db->affected_rows() . " records deleted from 'kolab_folders'\n"; - } - break; - - -/* - * Prewarm cache by synchronizing objects for the given user - */ -case 'prewarm': - // make sure libkolab classes are loaded - $rcmail->plugins->load_plugin('libkolab'); - - if (authenticate($opts)) { - $folder_types = $opts['type'] ? explode(',', $opts['type']) : $all_types; - foreach ($folder_types as $type) { - // sync every folder of the given type - foreach (kolab_storage::get_folders($type) as $folder) { - echo "Synching " . $folder->name . " ($type) ... "; - echo $folder->count($type) . "\n"; - - // also sync distribution lists in contact folders - if ($type == 'contact') { - echo "Synching " . $folder->name . " (distribution-list) ... "; - echo $folder->count('distribution-list') . "\n"; - } - } - } - } - else - die("Authentication failed for " . $opts['user']); - break; - -/** - * Update the cache meta columns from the serialized/xml data - * (might be run after a schema update) - */ -case 'update': - // make sure libkolab classes are loaded - $rcmail->plugins->load_plugin('libkolab'); - - $folder_types = $opts['type'] ? explode(',', $opts['type']) : $all_types; - foreach ($folder_types as $type) { - $class = 'kolab_storage_cache_' . $type; - $sql_result = $db->query("SELECT folder_id FROM kolab_folders WHERE type=? AND synclock = 0", $type); - while ($sql_result && ($sql_arr = $db->fetch_assoc($sql_result))) { - $folder = new $class; - $folder->select_by_id($sql_arr['folder_id']); - echo "Updating " . $sql_arr['folder_id'] . " ($type) "; - foreach ($folder->select() as $object) { - $object['_formatobj']->to_array(); // load data - $folder->save($object['_msguid'], $object, $object['_msguid']); - echo "."; - } - echo "done.\n"; - } - } - break; - - -/* - * Unknown action => show usage - */ -default: - print_usage(); - exit; -} - - -/** - * Compose cache resource URI prefix for the given user credentials - */ -function resource_prefix($opts) -{ - return 'imap://' . str_replace('%', '\\%', urlencode($opts['username'])) . '@' . $opts['host'] . '/'; -} - - -/** - * Authenticate to the IMAP server with the given user credentials - */ -function authenticate(&$opts) -{ - global $rcmail; - - // prompt for password - if (empty($opts['password']) && ($opts['username'] || $opts['user'])) { - $opts['password'] = prompt_silent("Password: "); - } - - // simulate "login as" feature - if ($opts['user'] && $opts['user'] != $opts['username']) - $_POST['_loginas'] = $opts['username']; - else if (empty($opts['user'])) - $opts['user'] = $opts['username']; - - // let the kolab_auth plugin do its magic - $auth = $rcmail->plugins->exec_hook('authenticate', array( - 'host' => trim($opts['host']), - 'user' => trim($opts['user']), - 'pass' => $opts['password'], - 'cookiecheck' => false, - 'valid' => !empty($opts['user']) && !empty($opts['host']), - )); - - if ($auth['valid']) { - $storage = $rcmail->get_storage(); - if ($storage->connect($auth['host'], $auth['user'], $auth['pass'], 143, false)) { - if ($opts['verbose']) - echo "IMAP login succeeded.\n"; - if (($user = rcube_user::query($opts['username'], $auth['host'])) && $user->ID) - $rcmail->user = $user; - } - else - die("Login to IMAP server failed!\n"); - } - else { - die("Invalid login credentials!\n"); - } - - return $auth['valid']; -} - diff --git a/lib/drivers/kolab/plugins/libkolab/bin/randomcontacts.sh b/lib/drivers/kolab/plugins/libkolab/bin/randomcontacts.sh deleted file mode 100755 index e6154bb..0000000 --- a/lib/drivers/kolab/plugins/libkolab/bin/randomcontacts.sh +++ /dev/null @@ -1,181 +0,0 @@ -#!/usr/bin/env php - - * - * Copyright (C) 2014, Kolab Systems AG - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -define('INSTALL_PATH', realpath('.') . '/' ); -ini_set('display_errors', 1); - -if (!file_exists(INSTALL_PATH . 'program/include/clisetup.php')) - die("Execute this from the Roundcube installation dir!\n\n"); - -require_once INSTALL_PATH . 'program/include/clisetup.php'; - -function print_usage() -{ - print "Usage: randomcontacts.sh [OPTIONS] USERNAME FOLDER\n"; - print "Create random contact that for then given user in the specified folder.\n"; - print "-n, --num Number of contacts to be created, defaults to 50\n"; - print "-h, --host IMAP host name\n"; - print "-p, --password IMAP user password\n"; -} - -// read arguments -$opts = rcube_utils::get_opt(array( - 'n' => 'num', - 'h' => 'host', - 'u' => 'user', - 'p' => 'pass', - 'v' => 'verbose', -)); - -$opts['username'] = !empty($opts[0]) ? $opts[0] : $opts['user']; -$opts['folder'] = $opts[1]; - -$rcmail = rcube::get_instance(rcube::INIT_WITH_DB | rcube::INIT_WITH_PLUGINS); -$rcmail->plugins->load_plugins(array('libkolab')); -ini_set('display_errors', 1); - - -if (empty($opts['host'])) { - $opts['host'] = $rcmail->config->get('default_host'); - if (is_array($opts['host'])) // not unique - $opts['host'] = null; -} - -if (empty($opts['username']) || empty($opts['folder']) || empty($opts['host'])) { - print_usage(); - exit; -} - -// prompt for password -if (empty($opts['pass'])) { - $opts['pass'] = rcube_utils::prompt_silent("Password: "); -} - -// parse $host URL -$a_host = parse_url($opts['host']); -if ($a_host['host']) { - $host = $a_host['host']; - $imap_ssl = (isset($a_host['scheme']) && in_array($a_host['scheme'], array('ssl','imaps','tls'))) ? TRUE : FALSE; - $imap_port = isset($a_host['port']) ? $a_host['port'] : ($imap_ssl ? 993 : 143); -} -else { - $host = $opts['host']; - $imap_port = 143; -} - -// instantiate IMAP class -$IMAP = $rcmail->get_storage(); - -// try to connect to IMAP server -if ($IMAP->connect($host, $opts['username'], $opts['pass'], $imap_port, $imap_ssl)) { - print "IMAP login successful.\n"; - $user = rcube_user::query($opts['username'], $host); - $rcmail->user = $user ?: new rcube_user(null, array('username' => $opts['username'], 'host' => $host)); -} -else { - die("IMAP login failed for user " . $opts['username'] . " @ $host\n"); -} - -// get contacts folder -$folder = kolab_storage::get_folder($opts['folder']); -if (!$folder || empty($folder->type)) { - die("Invalid Address Book " . $opts['folder'] . "\n"); -} - -$format = new kolab_format_contact; - -$num = $opts['num'] ? intval($opts['num']) : 50; -echo "Creating $num contacts in " . $folder->get_resource_uri() . "\n"; - -for ($i=0; $i < $num; $i++) { - // generate random names - $contact = array( - 'surname' => random_string(rand(1,2)), - 'firstname' => random_string(rand(1,2)), - 'organization' => random_string(rand(0,2)), - 'profession' => random_string(rand(1,2)), - 'email' => array(), - 'phone' => array(), - 'address' => array(), - 'notes' => random_string(rand(10,200)), - ); - - // randomly add email addresses - $em = rand(1,3); - for ($e=0; $e < $em; $e++) { - $type = array_rand($format->emailtypes); - $contact['email'][] = array( - 'address' => strtolower(random_string(1) . '@' . random_string(1) . '.tld'), - 'type' => $type, - ); - } - - // randomly add phone numbers - $ph = rand(1,4); - for ($p=0; $p < $ph; $p++) { - $type = array_rand($format->phonetypes); - $contact['phone'][] = array( - 'number' => '+'.rand(2,8).rand(1,9).rand(1,9).rand(0,9).rand(0,9).rand(0,9).rand(0,9).rand(0,9).rand(0,9).rand(0,9).rand(0,9), - 'type' => $type, - ); - } - - // randomly add addresses - $ad = rand(0,2); - for ($a=0; $a < $ad; $a++) { - $type = array_rand($format->addresstypes); - $contact['address'][] = array( - 'street' => random_string(rand(1,3)), - 'locality' => random_string(rand(1,2)), - 'code' => rand(1000, 89999), - 'country' => random_string(1), - 'type' => $type, - ); - } - - $contact['name'] = $contact['firstname'] . ' ' . $contact['surname']; - - if ($folder->save($contact, 'contact')) { - echo "."; - } - else { - echo "x"; - break; // abort on error - } -} - -echo " done.\n"; - - - -function random_string($len) -{ - $words = explode(" ", "The Hough transform is named after Paul Hough who patented the method in 1962. It is a technique which can be used to isolate features of a particular shape within an image. Because it requires that the desired features be specified in some parametric form, the classical Hough transform is most commonly used for the de- tection of regular curves such as lines, circles, ellipses, etc. A generalized Hough transform can be employed in applications where a simple analytic description of a features is not possible. Due to the computational complexity of the generalized Hough algorithm, we restrict the main focus of this discussion to the classical Hough transform. Despite its domain restrictions, the classical Hough transform hereafter referred to without the classical prefix retains many applications, as most manufac- tured parts and many anatomical parts investigated in medical imagery contain feature boundaries which can be described by regular curves. The main advantage of the Hough transform technique is that it is tolerant of gaps in feature boundary descriptions and is relatively unaffected by image noise."); - for ($i = 0; $i < $len; $i++) { - $str .= $words[rand(0,count($words)-1)] . " "; - } - - return rtrim($str); -} diff --git a/lib/drivers/kolab/plugins/libkolab/bin/readcache.sh b/lib/drivers/kolab/plugins/libkolab/bin/readcache.sh deleted file mode 100755 index 238741f..0000000 --- a/lib/drivers/kolab/plugins/libkolab/bin/readcache.sh +++ /dev/null @@ -1,150 +0,0 @@ -#!/usr/bin/env php - - * - * Copyright (C) 2014, Kolab Systems AG - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -define('INSTALL_PATH', realpath('.') . '/' ); -ini_set('display_errors', 1); -libxml_use_internal_errors(true); - -if (!file_exists(INSTALL_PATH . 'program/include/clisetup.php')) - die("Execute this from the Roundcube installation dir!\n\n"); - -require_once INSTALL_PATH . 'program/include/clisetup.php'; - -function print_usage() -{ - print "Usage: readcache.sh [OPTIONS] FOLDER\n"; - print "-h, --host IMAP host name\n"; - print "-l, --limit Limit the number of records to be listed\n"; -} - -// read arguments -$opts = rcube_utils::get_opt(array( - 'h' => 'host', - 'l' => 'limit', - 'v' => 'verbose', -)); - -$folder = $opts[0]; -$imap_host = $opts['host']; - -$rcmail = rcube::get_instance(rcube::INIT_WITH_DB | rcube::INIT_WITH_PLUGINS); - -if (empty($imap_host)) { - $default_host = $rcmail->config->get('default_host'); - if (is_array($default_host)) { - list($k,$v) = each($default_host); - $imap_host = is_numeric($k) ? $v : $k; - } - else { - $imap_host = $default_host; - } - - // strip protocol prefix - $imap_host = preg_replace('!^[a-z]+://!', '', $imap_host); -} - -if (empty($folder) || empty($imap_host)) { - print_usage(); - exit; -} - -// connect to database -$db = $rcmail->get_dbh(); -$db->db_connect('r'); -if (!$db->is_connected() || $db->is_error()) - die("No DB connection\n"); - - -// resolve folder_id -if (!is_numeric($folder)) { - if (strpos($folder, '@')) { - list($mailbox, $domain) = explode('@', $folder); - list($username, $subpath) = explode('/', preg_replace('!^user/!', '', $mailbox), 2); - $folder_uri = 'imap://' . urlencode($username.'@'.$domain) . '@' . $imap_host . '/' . $subpath; - } - else { - die("Invalid mailbox identifier! Example: user/john.doe/Calendar@example.org\n"); - } - - print "Resolving folder $folder_uri..."; - $sql_result = $db->query('SELECT * FROM `kolab_folders` WHERE `resource`=?', $folder_uri); - if ($sql_result && ($folder_data = $db->fetch_assoc($sql_result))) { - $folder_id = $folder_data['folder_id']; - print $folder_id; - } - print "\n"; -} -else { - $folder_id = intval($folder); - $sql_result = $db->query('SELECT * FROM `kolab_folders` WHERE `folder_id`=?', $folder_id); - if ($sql_result) { - $folder_data = $db->fetch_assoc($sql_result); - } -} - -if (empty($folder_data)) { - die("Can't find cache mailbox for '$folder'\n"); -} - -print "Querying cache for folder $folder_id ($folder_data[type])...\n"; - -$extra_cols = array( - 'event' => array('dtstart','dtend'), - 'contact' => array('type'), -); - -$cache_table = $db->table_name('kolab_cache_' . $folder_data['type']); -$extra_cols_ = $extra_cols[$folder_data['type']] ?: array(); -$sql_arr = $db->fetch_assoc($db->query("SELECT COUNT(*) as cnt FROM `$cache_table` WHERE `folder_id`=?", intval($folder_id))); - -print "CTag = " . $folder_data['ctag'] . "\n"; -print "Lock = " . $folder_data['synclock'] . "\n"; -print "Count = " . $sql_arr['cnt'] . "\n"; -print "----------------------------------------------------------------------------------\n"; -print "\t\t\t\t\t"; -print join("\t", array_map(function($c) { return '<' . strtoupper($c) . '>'; }, $extra_cols_)); -print "\n----------------------------------------------------------------------------------\n"; - -$result = $db->limitquery("SELECT * FROM `$cache_table` WHERE `folder_id`=?", 0, $opts['limit'], intval($folder_id)); -while ($result && ($sql_arr = $db->fetch_assoc($result))) { - print $sql_arr['msguid'] . "\t" . $sql_arr['uid'] . "\t" . $sql_arr['changed']; - - // try to unserialize data block - $object = @unserialize(@base64_decode($sql_arr['data'])); - print "\t" . ($object === false ? 'FAIL!' : ($object['uid'] == $sql_arr['uid'] ? 'OK' : '!!!')); - - // check XML validity - $xml = simplexml_load_string($sql_arr['xml']); - print "\t" . ($xml === false ? 'FAIL!' : 'OK'); - - // print extra cols - array_walk($extra_cols_, function($c) use ($sql_arr) { - print "\t" . $sql_arr[$c]; - }); - - print "\n"; -} - -print "----------------------------------------------------------------------------------\n"; -echo "Done.\n"; diff --git a/lib/drivers/kolab/plugins/libkolab/composer.json b/lib/drivers/kolab/plugins/libkolab/composer.json deleted file mode 100644 index 41b70ea..0000000 --- a/lib/drivers/kolab/plugins/libkolab/composer.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "kolab/libkolab", - "type": "roundcube-plugin", - "description": "Plugin to setup a basic environment for the interaction with a Kolab server.", - "homepage": "https://git.kolab.org/diffusion/RPK/", - "license": "AGPLv3", - "version": "3.2.8", - "authors": [ - { - "name": "Thomas Bruederli", - "email": "bruederli@kolabsys.com", - "role": "Lead" - }, - { - "name": "Aleksander Machniak", - "email": "machniak@kolabsys.com", - "role": "Developer" - } - ], - "repositories": [ - { - "type": "composer", - "url": "http://plugins.roundcube.net" - } - ], - "require": { - "php": ">=5.3.0", - "roundcube/plugin-installer": ">=0.1.3", - "caxy/php-htmldiff": "dev-master" - } -} diff --git a/lib/drivers/kolab/plugins/libkolab/config.inc.php.dist b/lib/drivers/kolab/plugins/libkolab/config.inc.php.dist deleted file mode 100644 index a2c15e8..0000000 --- a/lib/drivers/kolab/plugins/libkolab/config.inc.php.dist +++ /dev/null @@ -1,75 +0,0 @@ -/freebusy -$config['kolab_freebusy_server'] = null; - -// Enables listing of only subscribed folders. This e.g. will limit -// folders in calendar view or available addressbooks -$config['kolab_use_subscriptions'] = false; - -// List any of 'personal','shared','other' namespaces to be excluded from groupware folder listing -// example: array('other'); -$config['kolab_skip_namespace'] = null; - -// Enables the use of displayname folder annotations as introduced in KEP:? -// for displaying resource folder names (experimental!) -$config['kolab_custom_display_names'] = false; - -// Configuration of HTTP requests. -// See http://pear.php.net/manual/en/package.http.http-request2.config.php -// for list of supported configuration options (array keys) -$config['kolab_http_request'] = array(); - -// When kolab_cache is enabled Roundcube's messages cache will be redundant -// when working on kolab folders. Here we can: -// 2 - bypass messages/indexes cache completely -// 1 - bypass only messages, but use index cache -$config['kolab_messages_cache_bypass'] = 0; - -// These event properties contribute to a significant revision to the calendar component -// and if changed will increment the sequence number relevant for scheduling according to RFC 5545 -$config['kolab_event_scheduling_properties'] = array('start', 'end', 'allday', 'recurrence', 'location', 'status', 'cancelled'); - -// These task properties contribute to a significant revision to the calendar component -// and if changed will increment the sequence number relevant for scheduling according to RFC 5545 -$config['kolab_task_scheduling_properties'] = array('start', 'due', 'summary', 'status'); - -// LDAP directory to find avilable users for folder sharing. -// Either contains an array with LDAP addressbook configuration or refers to entry in $config['ldap_public']. -// If not specified, the configuraton from 'kolab_auth_addressbook' will be used. -// Should be provided for multi-domain setups with placeholders like %dc, %d, %u, %fu or %dn. -$config['kolab_users_directory'] = null; - -// Filter to be used for resolving user folders in LDAP. -// Defaults to the 'kolab_auth_filter' configuration option. -$config['kolab_users_filter'] = '(&(objectclass=kolabInetOrgPerson)(|(uid=%u)(mail=%fu)))'; - -// Which property of the LDAP user record to use for user folder mapping in IMAP. -// Defaults to the 'kolab_auth_login' configuration option. -$config['kolab_users_id_attrib'] = null; - -// Use these attributes when searching users in LDAP -$config['kolab_users_search_attrib'] = array('cn','mail','alias'); - -// JSON-RPC endpoint configuration of the Bonnie web service providing historic data for groupware objects -$config['kolab_bonnie_api'] = array( - 'uri' => 'https://:8080/api/rpc', - 'user' => 'webclient', - 'pass' => 'Welcome2KolabSystems', - 'secret' => '8431f191707fffffff00000000cccc', - 'debug' => true, // logs requests/responses to /bonnie - 'timeout' => 30, -); diff --git a/lib/drivers/kolab/plugins/libkolab/js/audittrail.js b/lib/drivers/kolab/plugins/libkolab/js/audittrail.js deleted file mode 100644 index 84fdf61..0000000 --- a/lib/drivers/kolab/plugins/libkolab/js/audittrail.js +++ /dev/null @@ -1,269 +0,0 @@ -/** - * Kolab groupware audit trail utilities - * - * @author Thomas Bruederli - * - * @licstart The following is the entire license notice for the - * JavaScript code in this file. - * - * Copyright (C) 2015, Kolab Systems AG - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - * @licend The above is the entire license notice - * for the JavaScript code in this file. - */ - -var libkolab_audittrail = {} - -libkolab_audittrail.quote_html = function(str) -{ - return String(str).replace(//g, '>').replace(/"/g, '"'); -}; - - -// show object changelog in a dialog -libkolab_audittrail.object_history_dialog = function(p) -{ - // render dialog - var $dialog = $(p.container); - - // close show dialog first - if ($dialog.is(':ui-dialog')) - $dialog.dialog('close'); - - // hide and reset changelog table - $dialog.find('div.notfound-message').remove(); - $dialog.find('.changelog-table').show().children('tbody') - .html('' + rcmail.gettext('loading') + ''); - - // open jquery UI dialog - $dialog.dialog({ - modal: false, - resizable: true, - closeOnEscape: true, - title: p.title, - open: function() { - $dialog.attr('aria-hidden', 'false'); - }, - close: function() { - $dialog.dialog('destroy').attr('aria-hidden', 'true').hide(); - }, - buttons: [ - { - text: rcmail.gettext('close'), - click: function() { $dialog.dialog('close'); }, - autofocus: true - } - ], - minWidth: 450, - width: 650, - height: 350, - minHeight: 200 - }) - .show().children('.compare-button').hide(); - - // initialize event handlers for history dialog UI elements - if (!$dialog.data('initialized')) { - // compare button - $dialog.find('.compare-button input').click(function(e) { - var rev1 = $dialog.find('.changelog-table input.diff-rev1:checked').val(), - rev2 = $dialog.find('.changelog-table input.diff-rev2:checked').val(); - - if (rev1 && rev2 && rev1 != rev2) { - // swap revisions if the user got it wrong - if (rev1 > rev2) { - var tmp = rev2; - rev2 = rev1; - rev1 = tmp; - } - - if (p.comparefunc) { - p.comparefunc(rev1, rev2); - } - } - else { - alert('Invalid selection!') - } - - if (!rcube_event.is_keyboard(e) && this.blur) { - this.blur(); - } - return false; - }); - - // delegate handlers for list actions - $dialog.find('.changelog-table tbody').on('click', 'td.actions a', function(e) { - var link = $(this), - action = link.hasClass('restore') ? 'restore' : 'show', - event = $('#eventhistory').data('event'), - rev = link.attr('data-rev'); - - // ignore clicks on first row (current revision) - if (link.closest('tr').hasClass('first')) { - return false; - } - - // let the user confirm the restore action - if (action == 'restore' && !confirm(rcmail.gettext('revisionrestoreconfirm', p.module).replace('$rev', rev))) { - return false; - } - - if (p.listfunc) { - p.listfunc(action, rev); - } - - if (!rcube_event.is_keyboard(e) && this.blur) { - this.blur(); - } - return false; - }) - .on('click', 'input.diff-rev1', function(e) { - if (!this.checked) return true; - - var rev1 = this.value, selection_valid = false; - $dialog.find('.changelog-table input.diff-rev2').each(function(i, elem) { - $(elem).prop('disabled', elem.value <= rev1); - if (elem.checked && elem.value > rev1) { - selection_valid = true; - } - }); - if (!selection_valid) { - $dialog.find('.changelog-table input.diff-rev2:not([disabled])').last().prop('checked', true); - } - }); - - $dialog.addClass('changelog-dialog').data('initialized', true); - } - - return $dialog; -}; - -// callback from server with changelog data -libkolab_audittrail.render_changelog = function(data, object, folder) -{ - var Q = libkolab_audittrail.quote_html; - - var $dialog = $('.changelog-dialog') - if (data === false || !data.length) { - return false; - } - - var i, change, accessible, op_append, - first = data.length - 1, last = 0, - is_writeable = !!folder.editable, - op_labels = { - RECEIVE: 'actionreceive', - APPEND: 'actionappend', - MOVE: 'actionmove', - DELETE: 'actiondelete', - READ: 'actionread', - FLAGSET: 'actionflagset', - FLAGCLEAR: 'actionflagclear' - }, - actions = ' ' + - (is_writeable ? '' : ''), - tbody = $dialog.find('.changelog-table tbody').html(''); - - for (i=first; i >= 0; i--) { - change = data[i]; - accessible = change.date && change.user; - - if (change.op == 'MOVE' && change.mailbox) { - op_append = ' ⇢ ' + change.mailbox; - } - else if ((change.op == 'FLAGSET' || change.op == 'FLAGCLEAR') && change.flags) { - op_append = ': ' + change.flags; - } - else { - op_append = ''; - } - - $('') - .append('' + (accessible && change.op != 'DELETE' ? - ' '+ - '' - : '')) - .append('' + Q(i+1) + '') - .append('' + Q(change.date || '') + '') - .append('' + Q(change.user || 'undisclosed') + '') - .append('' + Q(rcmail.gettext(op_labels[change.op] || '', 'libkolab') + op_append) + '') - .append('' + (accessible && change.op != 'DELETE' ? actions.replace(/\{rev\}/g, change.rev) : '') + '') - .appendTo(tbody); - } - - if (first > 0) { - $dialog.find('.compare-button').fadeIn(200); - $dialog.find('.changelog-table tr.last input.diff-rev1').click(); - } - - // set dialog size according to content - libkolab_audittrail.dialog_resize($dialog.get(0), $dialog.height() + 15, 600); - - return $dialog; -}; - -// resize and reposition (center) the dialog window -libkolab_audittrail.dialog_resize = function(id, height, width) -{ - var win = $(window), w = win.width(), h = win.height(); - $(id).dialog('option', { height: Math.min(h-20, height+130), width: Math.min(w-20, width+50) }) - .dialog('option', 'position', ['center', 'center']); // only works in a separate call (!?) -}; - - -// register handlers for mail message history -window.rcmail && rcmail.addEventListener('init', function(e) { - var loading_lock; - - if (rcmail.env.task == 'mail') { - rcmail.register_command('kolab-mail-history', function() { - var dialog, uid = rcmail.get_single_uid(), rec = { uid: uid, mbox: rcmail.get_message_mailbox(uid) }; - if (!uid || !window.libkolab_audittrail) { - return false; - } - - // render dialog - $dialog = libkolab_audittrail.object_history_dialog({ - module: 'libkolab', - container: '#mailmessagehistory', - title: rcmail.gettext('objectchangelog','libkolab') - }); - - $dialog.data('rec', rec); - - // fetch changelog data - loading_lock = rcmail.set_busy(true, 'loading', loading_lock); - rcmail.http_post('plugin.message-changelog', { _uid: rec.uid, _mbox: rec.mbox }, loading_lock); - - }, rcmail.env.action == 'show'); - - rcmail.addEventListener('plugin.message_render_changelog', function(data) { - var $dialog = $('#mailmessagehistory'), - rec = $dialog.data('rec'); - - if (data === false || !data.length || !rec) { - // display 'unavailable' message - $('
' + rcmail.gettext('objectchangelognotavailable','libkolab') + '
') - .insertBefore($dialog.find('.changelog-table').hide()); - return; - } - - data.module = 'libkolab'; - libkolab_audittrail.render_changelog(data, rec, {}); - }); - - rcmail.env.message_commands.push('kolab-mail-history'); - } -}); diff --git a/lib/drivers/kolab/plugins/libkolab/js/folderlist.js b/lib/drivers/kolab/plugins/libkolab/js/folderlist.js deleted file mode 100644 index 64f8a35..0000000 --- a/lib/drivers/kolab/plugins/libkolab/js/folderlist.js +++ /dev/null @@ -1,353 +0,0 @@ -/** - * Kolab groupware folders treelist widget - * - * @author Thomas Bruederli - * - * @licstart The following is the entire license notice for the - * JavaScript code in this file. - * - * Copyright (C) 2014, Kolab Systems AG - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - * @licend The above is the entire license notice - * for the JavaScript code in this file. - */ - -function kolab_folderlist(node, p) -{ - // extends treelist.js - rcube_treelist_widget.call(this, node, p); - - // private vars - var me = this; - var search_results; - var search_results_widget; - var search_results_container; - var listsearch_request; - var search_messagebox; - - var Q = rcmail.quote_html; - - // render the results for folderlist search - function render_search_results(results) - { - if (results.length) { - // create treelist widget to present the search results - if (!search_results_widget) { - var list_id = (me.container.attr('id') || p.id_prefix || '0') - search_results_container = $('
') - .html(p.search_title ? '

' + p.search_title + '

' : '') - .insertAfter(me.container); - - search_results_widget = new rcube_treelist_widget('
    ', { - id_prefix: p.id_prefix, - id_encode: p.id_encode, - id_decode: p.id_decode, - selectable: false - }); - // copy classes from main list - search_results_widget.container.addClass(me.container.attr('class')).attr('aria-labelledby', 'st:' + list_id); - - // register click handler on search result's checkboxes to select the given item for listing - search_results_widget.container - .appendTo(search_results_container) - .on('click', 'input[type=checkbox], a.subscribed, span.subscribed', function(e) { - var node, has_children, li = $(this).closest('li'), - id = li.attr('id').replace(new RegExp('^'+p.id_prefix), ''); - if (p.id_decode) - id = p.id_decode(id); - node = search_results_widget.get_node(id); - has_children = node.children && node.children.length; - - e.stopPropagation(); - e.bubbles = false; - - // activate + subscribe - if ($(e.target).hasClass('subscribed')) { - search_results[id].subscribed = true; - $(e.target).attr('aria-checked', 'true'); - li.children().first() - .toggleClass('subscribed') - .find('input[type=checkbox]').get(0).checked = true; - - if (has_children && search_results[id].group == 'other user') { - li.find('ul li > div').addClass('subscribed') - .find('a.subscribed').attr('aria-checked', 'true');; - } - } - else if (!this.checked) { - return; - } - - // copy item to the main list - add_result2list(id, li, true); - - if (has_children) { - li.find('input[type=checkbox]').first().prop('disabled', true).prop('checked', true); - li.find('a.subscribed, span.subscribed').first().hide(); - } - else { - li.remove(); - } - - // set partial subscription status - if (search_results[id].subscribed && search_results[id].parent && search_results[id].group == 'other') { - parent_subscription_status($(me.get_item(id, true))); - } - - // set focus to cloned checkbox - if (rcube_event.is_keyboard(e)) { - $(me.get_item(id, true)).find('input[type=checkbox]').first().focus(); - } - }) - .on('click', function(e) { - var prop, id = String($(e.target).closest('li').attr('id')).replace(new RegExp('^'+p.id_prefix), ''); - if (p.id_decode) - id = p.id_decode(id); - - if (!rcube_event.is_keyboard(e) && e.target.blur) - e.target.blur(); - - // forward event - if (prop = search_results[id]) { - e.data = prop; - if (me.triggerEvent('click-item', e) === false) { - e.stopPropagation(); - return false; - } - } - }); - } - - // add results to list - for (var prop, item, i=0; i < results.length; i++) { - prop = results[i]; - item = $(prop.html); - search_results[prop.id] = prop; - search_results_widget.insert({ - id: prop.id, - classes: [ prop.group || '' ], - html: item, - collapsed: true, - virtual: prop.virtual - }, prop.parent); - - // disable checkbox if item already exists in main list - if (me.get_node(prop.id) && !me.get_node(prop.id).virtual) { - item.find('input[type=checkbox]').first().prop('disabled', true).prop('checked', true); - item.find('a.subscribed, span.subscribed').hide(); - } - } - - search_results_container.show(); - } - } - - // helper method to (recursively) add a search result item to the main list widget - function add_result2list(id, li, active) - { - var node = search_results_widget.get_node(id), - prop = search_results[id], - parent_id = prop.parent || null, - has_children = node.children && node.children.length, - dom_node = has_children ? li.children().first().clone(true, true) : li.children().first(), - childs = []; - - // find parent node and insert at the right place - if (parent_id && me.get_node(parent_id)) { - dom_node.children('span,a').first().html(Q(prop.editname || prop.listname)); - } - else if (parent_id && search_results[parent_id]) { - // copy parent tree from search results - add_result2list(parent_id, $(search_results_widget.get_item(parent_id)), false); - } - else if (parent_id) { - // use full name for list display - dom_node.children('span,a').first().html(Q(prop.name)); - } - - // replace virtual node with a real one - if (me.get_node(id)) { - $(me.get_item(id, true)).children().first() - .replaceWith(dom_node) - .removeClass('virtual'); - } - else { - // copy childs, too - if (has_children && prop.group == 'other user') { - for (var cid, j=0; j < node.children.length; j++) { - if ((cid = node.children[j].id) && search_results[cid]) { - childs.push(search_results_widget.get_node(cid)); - } - } - } - - // move this result item to the main list widget - me.insert({ - id: id, - classes: [ prop.group || '' ], - virtual: prop.virtual, - html: dom_node, - level: node.level, - collapsed: true, - children: childs - }, parent_id, prop.group); - } - - delete prop.html; - prop.active = active; - me.triggerEvent('insert-item', { id: id, data: prop, item: li }); - - // register childs, too - if (childs.length) { - for (var cid, j=0; j < node.children.length; j++) { - if ((cid = node.children[j].id) && search_results[cid]) { - prop = search_results[cid]; - delete prop.html; - prop.active = false; - me.triggerEvent('insert-item', { id: cid, data: prop }); - } - } - } - } - - // update the given item's parent's (partial) subscription state - function parent_subscription_status(li) - { - var top_li = li.closest(me.container.children('li')), - all_childs = $('li > div:not(.treetoggle)', top_li), - subscribed = all_childs.filter('.subscribed').length; - - if (subscribed == 0) { - top_li.children('div:first').removeClass('subscribed partial'); - } - else { - top_li.children('div:first') - .addClass('subscribed')[subscribed < all_childs.length ? 'addClass' : 'removeClass']('partial'); - } - } - - // do some magic when search is performed on the widget - this.addEventListener('search', function(search) { - // hide search results - if (search_results_widget) { - search_results_container.hide(); - search_results_widget.reset(); - } - search_results = {}; - - if (search_messagebox) - rcmail.hide_message(search_messagebox); - - // send search request(s) to server - if (search.query && search.execute) { - // require a minimum length for the search string - if (rcmail.env.autocomplete_min_length && search.query.length < rcmail.env.autocomplete_min_length && search.query != '*') { - search_messagebox = rcmail.display_message( - rcmail.get_label('autocompletechars').replace('$min', rcmail.env.autocomplete_min_length)); - return; - } - - if (listsearch_request) { - // ignore, let the currently running request finish - if (listsearch_request.query == search.query) { - return; - } - else { // cancel previous search request - rcmail.multi_thread_request_abort(listsearch_request.id); - listsearch_request = null; - } - } - - var sources = p.search_sources || [ 'folders' ]; - var reqid = rcmail.multi_thread_http_request({ - items: sources, - threads: rcmail.env.autocomplete_threads || 1, - action: p.search_action || 'listsearch', - postdata: { action:'search', q:search.query, source:'%s' }, - lock: rcmail.display_message(rcmail.get_label('searching'), 'loading'), - onresponse: render_search_results, - whendone: function(data){ - listsearch_request = null; - me.triggerEvent('search-complete', data); - } - }); - - listsearch_request = { id:reqid, query:search.query }; - } - else if (!search.query && listsearch_request) { - rcmail.multi_thread_request_abort(listsearch_request.id); - listsearch_request = null; - } - }); - - this.container.on('click', 'a.subscribed, span.subscribed', function(e) { - var li = $(this).closest('li'), - id = li.attr('id').replace(new RegExp('^'+p.id_prefix), ''), - div = li.children().first(), - is_subscribed; - - if (me.is_search()) { - id = id.replace(/--xsR$/, ''); - li = $(me.get_item(id, true)); - div = $(div).add(li.children().first()); - } - - if (p.id_decode) - id = p.id_decode(id); - - div.toggleClass('subscribed'); - is_subscribed = div.hasClass('subscribed'); - $(this).attr('aria-checked', is_subscribed ? 'true' : 'false'); - me.triggerEvent('subscribe', { id: id, subscribed: is_subscribed, item: li }); - - // update subscribe state of all 'virtual user' child folders - if (li.hasClass('other user')) { - $('ul li > div', li).each(function() { - $(this)[is_subscribed ? 'addClass' : 'removeClass']('subscribed'); - $('.subscribed', div).attr('aria-checked', is_subscribed ? 'true' : 'false'); - }); - div.removeClass('partial'); - } - // propagate subscription state to parent 'virtual user' folder - else if (li.closest('li.other.user').length) { - parent_subscription_status(li); - } - - e.stopPropagation(); - return false; - }); - - this.container.on('click', 'a.remove', function(e) { - var li = $(this).closest('li'), - id = li.attr('id').replace(new RegExp('^'+p.id_prefix), ''); - - if (me.is_search()) { - id = id.replace(/--xsR$/, ''); - li = $(me.get_item(id, true)); - } - - if (p.id_decode) - id = p.id_decode(id); - - me.triggerEvent('remove', { id: id, item: li }); - - e.stopPropagation(); - return false; - }); -} - -// link prototype from base class -kolab_folderlist.prototype = rcube_treelist_widget.prototype; diff --git a/lib/drivers/kolab/plugins/libkolab/lib/kolab_bonnie_api.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_bonnie_api.php deleted file mode 100644 index 6905dca..0000000 --- a/lib/drivers/kolab/plugins/libkolab/lib/kolab_bonnie_api.php +++ /dev/null @@ -1,97 +0,0 @@ - - * - * Copyright (C) 2014, Kolab Systems AG - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -class kolab_bonnie_api -{ - public $ready = false; - - private $config = array(); - private $client = null; - - - /** - * Default constructor - */ - public function __construct($config) - { - $this->config = $config; - - $this->client = new kolab_bonnie_api_client($config['uri'], $config['timeout'] ?: 30, (bool)$config['debug']); - - $this->client->set_secret($config['secret']); - $this->client->set_authentication($config['user'], $config['pass']); - $this->client->set_request_user(rcube::get_instance()->get_user_name()); - - $this->ready = !empty($config['secret']) && !empty($config['user']) && !empty($config['pass']); - } - - /** - * Wrapper function for .changelog() API call - */ - public function changelog($type, $uid, $mailbox, $msguid=null) - { - return $this->client->execute($type.'.changelog', array('uid' => $uid, 'mailbox' => $mailbox, 'msguid' => $msguid)); - } - - /** - * Wrapper function for .diff() API call - */ - public function diff($type, $uid, $rev1, $rev2, $mailbox, $msguid=null, $instance=null) - { - return $this->client->execute($type.'.diff', array( - 'uid' => $uid, - 'rev1' => $rev1, - 'rev2' => $rev2, - 'mailbox' => $mailbox, - 'msguid' => $msguid, - 'instance' => $instance, - )); - } - - /** - * Wrapper function for .get() API call - */ - public function get($type, $uid, $rev, $mailbox, $msguid=null) - { - return $this->client->execute($type.'.get', array('uid' => $uid, 'rev' => $rev, 'mailbox' => $mailbox, 'msguid' => $msguid)); - } - - /** - * Wrapper function for .rawdata() API call - */ - public function rawdata($type, $uid, $rev, $mailbox, $msguid=null) - { - return $this->client->execute($type.'.rawdata', array('uid' => $uid, 'rev' => $rev, 'mailbox' => $mailbox, 'msguid' => $msguid)); - } - - /** - * Generic wrapper for direct API calls - */ - public function _execute($method, $params = array()) - { - return $this->client->execute($method, $params); - } - -} \ No newline at end of file diff --git a/lib/drivers/kolab/plugins/libkolab/lib/kolab_bonnie_api_client.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_bonnie_api_client.php deleted file mode 100644 index bc209f4..0000000 --- a/lib/drivers/kolab/plugins/libkolab/lib/kolab_bonnie_api_client.php +++ /dev/null @@ -1,239 +0,0 @@ - - * - * Copyright (C) 2014, Kolab Systems AG - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -class kolab_bonnie_api_client -{ - /** - * URL of the RPC endpoint - * @var string - */ - protected $url; - - /** - * HTTP client timeout in seconds - * @var integer - */ - protected $timeout; - - /** - * Debug flag - * @var bool - */ - protected $debug; - - /** - * Username for authentication - * @var string - */ - protected $username; - - /** - * Password for authentication - * @var string - */ - protected $password; - - /** - * Secret key for request signing - * @var string - */ - protected $secret; - - /** - * Default HTTP headers to send to the server - * @var array - */ - protected $headers = array( - 'Connection' => 'close', - 'Content-Type' => 'application/json', - 'Accept' => 'application/json', - ); - - /** - * Constructor - * - * @param string $url Server URL - * @param integer $timeout Request timeout - * @param bool $debug Enabled debug logging - * @param array $headers Custom HTTP headers - */ - public function __construct($url, $timeout = 5, $debug = false, $headers = array()) - { - $this->url = $url; - $this->timeout = $timeout; - $this->debug = $debug; - $this->headers = array_merge($this->headers, $headers); - } - - /** - * Setter for secret key for request signing - */ - public function set_secret($secret) - { - $this->secret = $secret; - } - - /** - * Setter for the X-Request-User header - */ - public function set_request_user($username) - { - $this->headers['X-Request-User'] = $username; - } - - /** - * Set authentication parameters - * - * @param string $username Username - * @param string $password Password - */ - public function set_authentication($username, $password) - { - $this->username = $username; - $this->password = $password; - } - - /** - * Automatic mapping of procedures - * - * @param string $method Procedure name - * @param array $params Procedure arguments - * @return mixed - */ - public function __call($method, $params) - { - return $this->execute($method, $params); - } - - /** - * Execute an RPC command - * - * @param string $method Procedure name - * @param array $params Procedure arguments - * @return mixed - */ - public function execute($method, array $params = array()) - { - $id = mt_rand(); - - $payload = array( - 'jsonrpc' => '2.0', - 'method' => $method, - 'id' => $id, - ); - - if (!empty($params)) { - $payload['params'] = $params; - } - - $result = $this->send_request($payload, $method != 'system.keygen'); - - if (isset($result['id']) && $result['id'] == $id && array_key_exists('result', $result)) { - return $result['result']; - } - else if (isset($result['error'])) { - $this->_debug('ERROR', $result); - } - - return null; - } - - /** - * Do the HTTP request - * - * @param string $payload Data to send - */ - protected function send_request($payload, $sign = true) - { - try { - $payload_ = json_encode($payload); - - // add request signature - if ($sign && !empty($this->secret)) { - $this->headers['X-Request-Sign'] = $this->request_signature($payload_); - } - else if ($this->headers['X-Request-Sign']) { - unset($this->headers['X-Request-Sign']); - } - - $this->_debug('REQUEST', $payload, $this->headers); - $request = libkolab::http_request($this->url, 'POST', array('timeout' => $this->timeout)); - $request->setHeader($this->headers); - $request->setAuth($this->username, $this->password); - $request->setBody($payload_); - - $response = $request->send(); - - if ($response->getStatus() == 200) { - $result = json_decode($response->getBody(), true); - $this->_debug('RESPONSE', $result); - } - else { - throw new Exception(sprintf("HTTP %d %s", $response->getStatus(), $response->getReasonPhrase())); - } - } - catch (Exception $e) { - rcube::raise_error(array( - 'code' => 500, - 'type' => 'php', - 'message' => "Bonnie API request failed: " . $e->getMessage(), - ), true); - - return array('id' => $payload['id'], 'error' => $e->getMessage(), 'code' => -32000); - } - - return is_array($result) ? $result : array(); - } - - /** - * Compute the hmac signature for the current event payload using - * the secret key configured for this API client - * - * @param string $data The request payload data - * @return string The request signature - */ - protected function request_signature($data) - { - // TODO: get the session key with a system.keygen call - return hash_hmac('sha256', $this->headers['X-Request-User'] . ':' . $data, $this->secret); - } - - /** - * Write debug log - */ - protected function _debug(/* $message, $data1, data2, ...*/) - { - if (!$this->debug) - return; - - $args = func_get_args(); - - $msg = array(); - foreach ($args as $arg) { - $msg[] = !is_string($arg) ? var_export($arg, true) : $arg; - } - - rcube::write_log('bonnie', join(";\n", $msg)); - } - -} \ No newline at end of file diff --git a/lib/drivers/kolab/plugins/libkolab/lib/kolab_date_recurrence.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_date_recurrence.php deleted file mode 100644 index f2483c9..0000000 --- a/lib/drivers/kolab/plugins/libkolab/lib/kolab_date_recurrence.php +++ /dev/null @@ -1,139 +0,0 @@ - - * - * Copyright (C) 2012, Kolab Systems AG - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -class kolab_date_recurrence -{ - private /* EventCal */ $engine; - private /* kolab_format_xcal */ $object; - private /* DateTime */ $start; - private /* DateTime */ $next; - private /* cDateTime */ $cnext; - private /* DateInterval */ $duration; - - /** - * Default constructor - * - * @param array The Kolab object to operate on - */ - function __construct($object) - { - $data = $object->to_array(); - - $this->object = $object; - $this->engine = $object->to_libcal(); - $this->start = $this->next = $data['start']; - $this->cnext = kolab_format::get_datetime($this->next); - - if (is_object($data['start']) && is_object($data['end'])) - $this->duration = $data['start']->diff($data['end']); - else - $this->duration = new DateInterval('PT' . ($data['end'] - $data['start']) . 'S'); - } - - /** - * Get date/time of the next occurence of this event - * - * @param boolean Return a Unix timestamp instead of a DateTime object - * @return mixed DateTime object/unix timestamp or False if recurrence ended - */ - public function next_start($timestamp = false) - { - $time = false; - - if ($this->engine && $this->next) { - if (($cnext = new cDateTime($this->engine->getNextOccurence($this->cnext))) && $cnext->isValid()) { - $next = kolab_format::php_datetime($cnext); - $time = $timestamp ? $next->format('U') : $next; - $this->cnext = $cnext; - $this->next = $next; - } - } - - return $time; - } - - /** - * Get the next recurring instance of this event - * - * @return mixed Array with event properties or False if recurrence ended - */ - public function next_instance() - { - if ($next_start = $this->next_start()) { - $next_end = clone $next_start; - $next_end->add($this->duration); - - $next = $this->object->to_array(); - $next['start'] = $next_start; - $next['end'] = $next_end; - - $recurrence_id_format = libkolab::recurrence_id_format($next); - $next['recurrence_date'] = clone $next_start; - $next['_instance'] = $next_start->format($recurrence_id_format); - - unset($next['_formatobj']); - - return $next; - } - - return false; - } - - /** - * Get the end date of the occurence of this recurrence cycle - * - * @return DateTime|bool End datetime of the last event or False if recurrence exceeds limit - */ - public function end() - { - $event = $this->object->to_array(); - - // recurrence end date is given - if ($event['recurrence']['UNTIL'] instanceof DateTime) { - return $event['recurrence']['UNTIL']; - } - - // let libkolab do the work - if ($this->engine && ($cend = $this->engine->getLastOccurrence()) && ($end_dt = kolab_format::php_datetime(new cDateTime($cend)))) { - return $end_dt; - } - - // determine a reasonable end date if none given - if (!$event['recurrence']['COUNT'] && $event['end'] instanceof DateTime) { - switch ($event['recurrence']['FREQ']) { - case 'YEARLY': $intvl = 'P100Y'; break; - case 'MONTHLY': $intvl = 'P20Y'; break; - default: $intvl = 'P10Y'; break; - } - - $end_dt = clone $event['end']; - $end_dt->add(new DateInterval($intvl)); - return $end_dt; - } - - return false; - } -} diff --git a/lib/drivers/kolab/plugins/libkolab/lib/kolab_format.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_format.php deleted file mode 100644 index 5041dd3..0000000 --- a/lib/drivers/kolab/plugins/libkolab/lib/kolab_format.php +++ /dev/null @@ -1,707 +0,0 @@ - - * - * Copyright (C) 2012, Kolab Systems AG - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -abstract class kolab_format -{ - public static $timezone; - - public /*abstract*/ $CTYPE; - public /*abstract*/ $CTYPEv2; - - protected /*abstract*/ $objclass; - protected /*abstract*/ $read_func; - protected /*abstract*/ $write_func; - - protected $obj; - protected $data; - protected $xmldata; - protected $xmlobject; - protected $formaterror; - protected $loaded = false; - protected $version = '3.0'; - - const KTYPE_PREFIX = 'application/x-vnd.kolab.'; - const PRODUCT_ID = 'Roundcube-libkolab-1.1'; - - // mapping table for valid PHP timezones not supported by libkolabxml - // basically the entire list of ftp://ftp.iana.org/tz/data/backward - protected static $timezone_map = array( - 'Africa/Asmera' => 'Africa/Asmara', - 'Africa/Timbuktu' => 'Africa/Abidjan', - 'America/Argentina/ComodRivadavia' => 'America/Argentina/Catamarca', - 'America/Atka' => 'America/Adak', - 'America/Buenos_Aires' => 'America/Argentina/Buenos_Aires', - 'America/Catamarca' => 'America/Argentina/Catamarca', - 'America/Coral_Harbour' => 'America/Atikokan', - 'America/Cordoba' => 'America/Argentina/Cordoba', - 'America/Ensenada' => 'America/Tijuana', - 'America/Fort_Wayne' => 'America/Indiana/Indianapolis', - 'America/Indianapolis' => 'America/Indiana/Indianapolis', - 'America/Jujuy' => 'America/Argentina/Jujuy', - 'America/Knox_IN' => 'America/Indiana/Knox', - 'America/Louisville' => 'America/Kentucky/Louisville', - 'America/Mendoza' => 'America/Argentina/Mendoza', - 'America/Porto_Acre' => 'America/Rio_Branco', - 'America/Rosario' => 'America/Argentina/Cordoba', - 'America/Virgin' => 'America/Port_of_Spain', - 'Asia/Ashkhabad' => 'Asia/Ashgabat', - 'Asia/Calcutta' => 'Asia/Kolkata', - 'Asia/Chungking' => 'Asia/Shanghai', - 'Asia/Dacca' => 'Asia/Dhaka', - 'Asia/Katmandu' => 'Asia/Kathmandu', - 'Asia/Macao' => 'Asia/Macau', - 'Asia/Saigon' => 'Asia/Ho_Chi_Minh', - 'Asia/Tel_Aviv' => 'Asia/Jerusalem', - 'Asia/Thimbu' => 'Asia/Thimphu', - 'Asia/Ujung_Pandang' => 'Asia/Makassar', - 'Asia/Ulan_Bator' => 'Asia/Ulaanbaatar', - 'Atlantic/Faeroe' => 'Atlantic/Faroe', - 'Atlantic/Jan_Mayen' => 'Europe/Oslo', - 'Australia/ACT' => 'Australia/Sydney', - 'Australia/Canberra' => 'Australia/Sydney', - 'Australia/LHI' => 'Australia/Lord_Howe', - 'Australia/NSW' => 'Australia/Sydney', - 'Australia/North' => 'Australia/Darwin', - 'Australia/Queensland' => 'Australia/Brisbane', - 'Australia/South' => 'Australia/Adelaide', - 'Australia/Tasmania' => 'Australia/Hobart', - 'Australia/Victoria' => 'Australia/Melbourne', - 'Australia/West' => 'Australia/Perth', - 'Australia/Yancowinna' => 'Australia/Broken_Hill', - 'Brazil/Acre' => 'America/Rio_Branco', - 'Brazil/DeNoronha' => 'America/Noronha', - 'Brazil/East' => 'America/Sao_Paulo', - 'Brazil/West' => 'America/Manaus', - 'Canada/Atlantic' => 'America/Halifax', - 'Canada/Central' => 'America/Winnipeg', - 'Canada/East-Saskatchewan' => 'America/Regina', - 'Canada/Eastern' => 'America/Toronto', - 'Canada/Mountain' => 'America/Edmonton', - 'Canada/Newfoundland' => 'America/St_Johns', - 'Canada/Pacific' => 'America/Vancouver', - 'Canada/Saskatchewan' => 'America/Regina', - 'Canada/Yukon' => 'America/Whitehorse', - 'Chile/Continental' => 'America/Santiago', - 'Chile/EasterIsland' => 'Pacific/Easter', - 'Cuba' => 'America/Havana', - 'Egypt' => 'Africa/Cairo', - 'Eire' => 'Europe/Dublin', - 'Europe/Belfast' => 'Europe/London', - 'Europe/Tiraspol' => 'Europe/Chisinau', - 'GB' => 'Europe/London', - 'GB-Eire' => 'Europe/London', - 'Greenwich' => 'Etc/GMT', - 'Hongkong' => 'Asia/Hong_Kong', - 'Iceland' => 'Atlantic/Reykjavik', - 'Iran' => 'Asia/Tehran', - 'Israel' => 'Asia/Jerusalem', - 'Jamaica' => 'America/Jamaica', - 'Japan' => 'Asia/Tokyo', - 'Kwajalein' => 'Pacific/Kwajalein', - 'Libya' => 'Africa/Tripoli', - 'Mexico/BajaNorte' => 'America/Tijuana', - 'Mexico/BajaSur' => 'America/Mazatlan', - 'Mexico/General' => 'America/Mexico_City', - 'NZ' => 'Pacific/Auckland', - 'NZ-CHAT' => 'Pacific/Chatham', - 'Navajo' => 'America/Denver', - 'PRC' => 'Asia/Shanghai', - 'Pacific/Ponape' => 'Pacific/Pohnpei', - 'Pacific/Samoa' => 'Pacific/Pago_Pago', - 'Pacific/Truk' => 'Pacific/Chuuk', - 'Pacific/Yap' => 'Pacific/Chuuk', - 'Poland' => 'Europe/Warsaw', - 'Portugal' => 'Europe/Lisbon', - 'ROC' => 'Asia/Taipei', - 'ROK' => 'Asia/Seoul', - 'Singapore' => 'Asia/Singapore', - 'Turkey' => 'Europe/Istanbul', - 'UCT' => 'Etc/UCT', - 'US/Alaska' => 'America/Anchorage', - 'US/Aleutian' => 'America/Adak', - 'US/Arizona' => 'America/Phoenix', - 'US/Central' => 'America/Chicago', - 'US/East-Indiana' => 'America/Indiana/Indianapolis', - 'US/Eastern' => 'America/New_York', - 'US/Hawaii' => 'Pacific/Honolulu', - 'US/Indiana-Starke' => 'America/Indiana/Knox', - 'US/Michigan' => 'America/Detroit', - 'US/Mountain' => 'America/Denver', - 'US/Pacific' => 'America/Los_Angeles', - 'US/Samoa' => 'Pacific/Pago_Pago', - 'Universal' => 'Etc/UTC', - 'W-SU' => 'Europe/Moscow', - 'Zulu' => 'Etc/UTC', - ); - - /** - * Factory method to instantiate a kolab_format object of the given type and version - * - * @param string Object type to instantiate - * @param float Format version - * @param string Cached xml data to initialize with - * @return object kolab_format - */ - public static function factory($type, $version = '3.0', $xmldata = null) - { - if (!isset(self::$timezone)) - self::$timezone = new DateTimeZone('UTC'); - - if (!self::supports($version)) - return PEAR::raiseError("No support for Kolab format version " . $version); - - $type = preg_replace('/configuration\.[a-z._]+$/', 'configuration', $type); - $suffix = preg_replace('/[^a-z]+/', '', $type); - $classname = 'kolab_format_' . $suffix; - if (class_exists($classname)) - return new $classname($xmldata, $version); - - return PEAR::raiseError("Failed to load Kolab Format wrapper for type " . $type); - } - - /** - * Determine support for the given format version - * - * @param float Format version to check - * @return boolean True if supported, False otherwise - */ - public static function supports($version) - { - if ($version == '2.0') - return class_exists('kolabobject'); - // default is version 3 - return class_exists('kolabformat'); - } - - /** - * Convert the given date/time value into a cDateTime object - * - * @param mixed Date/Time value either as unix timestamp, date string or PHP DateTime object - * @param DateTimeZone The timezone the date/time is in. Use global default if Null, local time if False - * @param boolean True of the given date has no time component - * @return object The libkolabxml date/time object - */ - public static function get_datetime($datetime, $tz = null, $dateonly = false) - { - // use timezone information from datetime of global setting - if (!$tz && $tz !== false) { - if ($datetime instanceof DateTime) - $tz = $datetime->getTimezone(); - if (!$tz) - $tz = self::$timezone; - } - $result = new cDateTime(); - - try { - // got a unix timestamp (in UTC) - if (is_numeric($datetime)) { - $datetime = new DateTime('@'.$datetime, new DateTimeZone('UTC')); - if ($tz) $datetime->setTimezone($tz); - } - else if (is_string($datetime) && strlen($datetime)) { - $datetime = $tz ? new DateTime($datetime, $tz) : new DateTime($datetime); - } - } - catch (Exception $e) {} - - if ($datetime instanceof DateTime) { - $result->setDate($datetime->format('Y'), $datetime->format('n'), $datetime->format('j')); - - if (!$dateonly) - $result->setTime($datetime->format('G'), $datetime->format('i'), $datetime->format('s')); - - if ($tz && in_array($tz->getName(), array('UTC', 'GMT', '+00:00', 'Z'))) { - $result->setUTC(true); - } - else if ($tz !== false) { - $tzid = $tz->getName(); - if (array_key_exists($tzid, self::$timezone_map)) - $tzid = self::$timezone_map[$tzid]; - $result->setTimezone($tzid); - } - } - - return $result; - } - - /** - * Convert the given cDateTime into a PHP DateTime object - * - * @param object cDateTime The libkolabxml datetime object - * @return object DateTime PHP datetime instance - */ - public static function php_datetime($cdt) - { - if (!is_object($cdt) || !$cdt->isValid()) - return null; - - $d = new DateTime; - $d->setTimezone(self::$timezone); - - try { - if ($tzs = $cdt->timezone()) { - $tz = new DateTimeZone($tzs); - $d->setTimezone($tz); - } - else if ($cdt->isUTC()) { - $d->setTimezone(new DateTimeZone('UTC')); - } - } - catch (Exception $e) { } - - $d->setDate($cdt->year(), $cdt->month(), $cdt->day()); - - if ($cdt->isDateOnly()) { - $d->_dateonly = true; - $d->setTime(12, 0, 0); // set time to noon to avoid timezone troubles - } - else { - $d->setTime($cdt->hour(), $cdt->minute(), $cdt->second()); - } - - return $d; - } - - /** - * Convert a libkolabxml vector to a PHP array - * - * @param object vector Object - * @return array Indexed array containing vector elements - */ - public static function vector2array($vec, $max = PHP_INT_MAX) - { - $arr = array(); - for ($i=0; $i < $vec->size() && $i < $max; $i++) - $arr[] = $vec->get($i); - return $arr; - } - - /** - * Build a libkolabxml vector (string) from a PHP array - * - * @param array Array with vector elements - * @return object vectors - */ - public static function array2vector($arr) - { - $vec = new vectors; - foreach ((array)$arr as $val) { - if (strlen($val)) - $vec->push($val); - } - return $vec; - } - - /** - * Parse the X-Kolab-Type header from MIME messages and return the object type in short form - * - * @param string X-Kolab-Type header value - * @return string Kolab object type (contact,event,task,note,etc.) - */ - public static function mime2object_type($x_kolab_type) - { - return preg_replace( - array('/dictionary.[a-z.]+$/', '/contact.distlist$/'), - array( 'dictionary', 'distribution-list'), - substr($x_kolab_type, strlen(self::KTYPE_PREFIX)) - ); - } - - - /** - * Default constructor of all kolab_format_* objects - */ - public function __construct($xmldata = null, $version = null) - { - $this->obj = new $this->objclass; - $this->xmldata = $xmldata; - - if ($version) - $this->version = $version; - - // use libkolab module if available - if (class_exists('kolabobject')) - $this->xmlobject = new XMLObject(); - } - - /** - * Check for format errors after calling kolabformat::write*() - * - * @return boolean True if there were errors, False if OK - */ - protected function format_errors() - { - $ret = $log = false; - switch (kolabformat::error()) { - case kolabformat::NoError: - $ret = false; - break; - case kolabformat::Warning: - $ret = false; - $uid = is_object($this->obj) ? $this->obj->uid() : $this->data['uid']; - $log = "Warning @ $uid"; - break; - default: - $ret = true; - $log = "Error"; - } - - if ($log && !isset($this->formaterror)) { - rcube::raise_error(array( - 'code' => 660, - 'type' => 'php', - 'file' => __FILE__, - 'line' => __LINE__, - 'message' => "kolabformat $log: " . kolabformat::errorMessage(), - ), true); - - $this->formaterror = $ret; - } - - return $ret; - } - - /** - * Save the last generated UID to the object properties. - * Should be called after kolabformat::writeXXXX(); - */ - protected function update_uid() - { - // get generated UID - if (!$this->data['uid']) { - if ($this->xmlobject) { - $this->data['uid'] = $this->xmlobject->getSerializedUID(); - } - if (empty($this->data['uid'])) { - $this->data['uid'] = kolabformat::getSerializedUID(); - } - $this->obj->setUid($this->data['uid']); - } - } - - /** - * Initialize libkolabxml object with cached xml data - */ - protected function init() - { - if (!$this->loaded) { - if ($this->xmldata) { - $this->load($this->xmldata); - $this->xmldata = null; - } - $this->loaded = true; - } - } - - /** - * Get constant value for libkolab's version parameter - * - * @param float Version value to convert - * @return int Constant value of either kolabobject::KolabV2 or kolabobject::KolabV3 or false if kolabobject module isn't available - */ - protected function libversion($v = null) - { - if (class_exists('kolabobject')) { - $version = $v ?: $this->version; - if ($version <= '2.0') - return kolabobject::KolabV2; - else - return kolabobject::KolabV3; - } - - return false; - } - - /** - * Determine the correct libkolab(xml) wrapper function for the given call - * depending on the available PHP modules - */ - protected function libfunc($func) - { - if (is_array($func) || strpos($func, '::')) - return $func; - else if (class_exists('kolabobject')) - return array($this->xmlobject, $func); - else - return 'kolabformat::' . $func; - } - - /** - * Direct getter for object properties - */ - public function __get($var) - { - return $this->data[$var]; - } - - /** - * Load Kolab object data from the given XML block - * - * @param string XML data - * @return boolean True on success, False on failure - */ - public function load($xml) - { - $this->formaterror = null; - $read_func = $this->libfunc($this->read_func); - - if (is_array($read_func)) - $r = call_user_func($read_func, $xml, $this->libversion()); - else - $r = call_user_func($read_func, $xml, false); - - if (is_resource($r)) - $this->obj = new $this->objclass($r); - else if (is_a($r, $this->objclass)) - $this->obj = $r; - - $this->loaded = !$this->format_errors(); - } - - /** - * Write object data to XML format - * - * @param float Format version to write - * @return string XML data - */ - public function write($version = null) - { - $this->formaterror = null; - - $this->init(); - $write_func = $this->libfunc($this->write_func); - if (is_array($write_func)) - $this->xmldata = call_user_func($write_func, $this->obj, $this->libversion($version), self::PRODUCT_ID); - else - $this->xmldata = call_user_func($write_func, $this->obj, self::PRODUCT_ID); - - if (!$this->format_errors()) - $this->update_uid(); - else - $this->xmldata = null; - - return $this->xmldata; - } - - /** - * Set properties to the kolabformat object - * - * @param array Object data as hash array - */ - public function set(&$object) - { - $this->init(); - - if (!empty($object['uid'])) - $this->obj->setUid($object['uid']); - - // set some automatic values if missing - if (empty($object['created']) && method_exists($this->obj, 'setCreated')) { - $cdt = $this->obj->created(); - $object['created'] = $cdt && $cdt->isValid() ? self::php_datetime($cdt) : new DateTime('now', new DateTimeZone('UTC')); - if (!$cdt || !$cdt->isValid()) - $this->obj->setCreated(self::get_datetime($object['created'])); - } - - $object['changed'] = new DateTime('now', new DateTimeZone('UTC')); - $this->obj->setLastModified(self::get_datetime($object['changed'])); - - // Save custom properties of the given object - if (isset($object['x-custom']) && method_exists($this->obj, 'setCustomProperties')) { - $vcustom = new vectorcs; - foreach ((array)$object['x-custom'] as $cp) { - if (is_array($cp)) - $vcustom->push(new CustomProperty($cp[0], $cp[1])); - } - $this->obj->setCustomProperties($vcustom); - } - // load custom properties from XML for caching (#2238) if method exists (#3125) - else if (method_exists($this->obj, 'customProperties')) { - $object['x-custom'] = array(); - $vcustom = $this->obj->customProperties(); - for ($i=0; $i < $vcustom->size(); $i++) { - $cp = $vcustom->get($i); - $object['x-custom'][] = array($cp->identifier, $cp->value); - } - } - } - - /** - * Convert the Kolab object into a hash array data structure - * - * @param array Additional data for merge - * - * @return array Kolab object data as hash array - */ - public function to_array($data = array()) - { - $this->init(); - - // read object properties into local data object - $object = array( - 'uid' => $this->obj->uid(), - 'changed' => self::php_datetime($this->obj->lastModified()), - ); - - // not all container support the created property - if (method_exists($this->obj, 'created')) { - $object['created'] = self::php_datetime($this->obj->created()); - } - - // read custom properties - if (method_exists($this->obj, 'customProperties')) { - $vcustom = $this->obj->customProperties(); - for ($i=0; $i < $vcustom->size(); $i++) { - $cp = $vcustom->get($i); - $object['x-custom'][] = array($cp->identifier, $cp->value); - } - } - - // merge with additional data, e.g. attachments from the message - if ($data) { - foreach ($data as $idx => $value) { - if (is_array($value)) { - $object[$idx] = array_merge((array)$object[$idx], $value); - } - else { - $object[$idx] = $value; - } - } - } - - return $object; - } - - /** - * Object validation method to be implemented by derived classes - */ - abstract public function is_valid(); - - /** - * Callback for kolab_storage_cache to get object specific tags to cache - * - * @return array List of tags to save in cache - */ - public function get_tags() - { - return array(); - } - - /** - * Callback for kolab_storage_cache to get words to index for fulltext search - * - * @return array List of words to save in cache - */ - public function get_words() - { - return array(); - } - - /** - * Utility function to extract object attachment data - * - * @param array Hash array reference to append attachment data into - */ - public function get_attachments(&$object, $all = false) - { - $this->init(); - - // handle attachments - $vattach = $this->obj->attachments(); - for ($i=0; $i < $vattach->size(); $i++) { - $attach = $vattach->get($i); - - // skip cid: attachments which are mime message parts handled by kolab_storage_folder - if (substr($attach->uri(), 0, 4) != 'cid:' && $attach->label()) { - $name = $attach->label(); - $key = $name . (isset($object['_attachments'][$name]) ? '.'.$i : ''); - $content = $attach->data(); - $object['_attachments'][$key] = array( - 'id' => 'i:'.$i, - 'name' => $name, - 'mimetype' => $attach->mimetype(), - 'size' => strlen($content), - 'content' => $content, - ); - } - else if ($all && substr($attach->uri(), 0, 4) == 'cid:') { - $key = $attach->uri(); - $object['_attachments'][$key] = array( - 'id' => $key, - 'name' => $attach->label(), - 'mimetype' => $attach->mimetype(), - ); - } - else if (in_array(substr($attach->uri(), 0, 4), array('http','imap'))) { - $object['links'][] = $attach->uri(); - } - } - } - - /** - * Utility function to set attachment properties to the kolabformat object - * - * @param array Object data as hash array - * @param boolean True to always overwrite attachment information - */ - protected function set_attachments($object, $write = true) - { - // save attachments - $vattach = new vectorattachment; - foreach ((array) $object['_attachments'] as $cid => $attr) { - if (empty($attr)) - continue; - $attach = new Attachment; - $attach->setLabel((string)$attr['name']); - $attach->setUri('cid:' . $cid, $attr['mimetype'] ?: 'application/octet-stream'); - if ($attach->isValid()) { - $vattach->push($attach); - $write = true; - } - else { - rcube::raise_error(array( - 'code' => 660, - 'type' => 'php', - 'file' => __FILE__, - 'line' => __LINE__, - 'message' => "Invalid attributes for attachment $cid: " . var_export($attr, true), - ), true); - } - } - - foreach ((array) $object['links'] as $link) { - $attach = new Attachment; - $attach->setUri($link, 'unknown'); - $vattach->push($attach); - $write = true; - } - - if ($write) { - $this->obj->setAttachments($vattach); - } - } -} diff --git a/lib/drivers/kolab/plugins/libkolab/lib/kolab_format_configuration.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_format_configuration.php deleted file mode 100644 index ceb7ebb..0000000 --- a/lib/drivers/kolab/plugins/libkolab/lib/kolab_format_configuration.php +++ /dev/null @@ -1,284 +0,0 @@ - - * - * Copyright (C) 2012, Kolab Systems AG - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -class kolab_format_configuration extends kolab_format -{ - public $CTYPE = 'application/vnd.kolab+xml'; - public $CTYPEv2 = 'application/x-vnd.kolab.configuration'; - - protected $objclass = 'Configuration'; - protected $read_func = 'readConfiguration'; - protected $write_func = 'writeConfiguration'; - - private $type_map = array( - 'category' => Configuration::TypeCategoryColor, - 'dictionary' => Configuration::TypeDictionary, - 'file_driver' => Configuration::TypeFileDriver, - 'relation' => Configuration::TypeRelation, - 'snippet' => Configuration::TypeSnippet, - ); - - private $driver_settings_fields = array('host', 'port', 'username', 'password'); - - /** - * Set properties to the kolabformat object - * - * @param array Object data as hash array - */ - public function set(&$object) - { - // read type-specific properties - switch ($object['type']) { - case 'dictionary': - $dict = new Dictionary($object['language']); - $dict->setEntries(self::array2vector($object['e'])); - $this->obj = new Configuration($dict); - break; - - case 'category': - // TODO: implement this - $categories = new vectorcategorycolor; - $this->obj = new Configuration($categories); - break; - - case 'file_driver': - $driver = new FileDriver($object['driver'], $object['title']); - - $driver->setEnabled((bool) $object['enabled']); - - foreach ($this->driver_settings_fields as $field) { - $value = $object[$field]; - if ($value !== null) { - $driver->{'set' . ucfirst($field)}($value); - } - } - - $this->obj = new Configuration($driver); - break; - - case 'relation': - $relation = new Relation(strval($object['name']), strval($object['category'])); - - if ($object['color']) { - $relation->setColor($object['color']); - } - if ($object['parent']) { - $relation->setParent($object['parent']); - } - if ($object['iconName']) { - $relation->setIconName($object['iconName']); - } - if ($object['priority'] > 0) { - $relation->setPriority((int) $object['priority']); - } - if (!empty($object['members'])) { - $relation->setMembers(self::array2vector($object['members'])); - } - - $this->obj = new Configuration($relation); - break; - - case 'snippet': - $collection = new SnippetCollection($object['name']); - $snippets = new vectorsnippets; - - foreach ((array) $object['snippets'] as $item) { - $snippet = new snippet($item['name'], $item['text']); - $snippet->setTextType(strtolower($item['type']) == 'html' ? Snippet::HTML : Snippet::Plain); - if ($item['shortcut']) { - $snippet->setShortCut($item['shortcut']); - } - - $snippets->push($snippet); - } - - $collection->setSnippets($snippets); - - $this->obj = new Configuration($collection); - break; - - default: - return false; - } - - // adjust content-type string - $this->CTYPEv2 = 'application/x-vnd.kolab.configuration.' . $object['type']; - - // reset old object data, otherwise set() will overwrite current data (#4095) - $this->xmldata = null; - // set common object properties - parent::set($object); - - // cache this data - $this->data = $object; - unset($this->data['_formatobj']); - } - - /** - * - */ - public function is_valid() - { - return $this->data || (is_object($this->obj) && $this->obj->isValid()); - } - - /** - * Convert the Configuration object into a hash array data structure - * - * @param array Additional data for merge - * - * @return array Config object data as hash array - */ - public function to_array($data = array()) - { - // return cached result - if (!empty($this->data)) { - return $this->data; - } - - // read common object props into local data object - $object = parent::to_array($data); - - $type_map = array_flip($this->type_map); - - $object['type'] = $type_map[$this->obj->type()]; - - // read type-specific properties - switch ($object['type']) { - case 'dictionary': - $dict = $this->obj->dictionary(); - $object['language'] = $dict->language(); - $object['e'] = self::vector2array($dict->entries()); - break; - - case 'category': - // TODO: implement this - break; - - case 'file_driver': - $driver = $this->obj->fileDriver(); - - $object['driver'] = $driver->driver(); - $object['title'] = $driver->title(); - $object['enabled'] = $driver->enabled(); - - foreach ($this->driver_settings_fields as $field) { - $object[$field] = $driver->{$field}(); - } - - break; - - case 'relation': - $relation = $this->obj->relation(); - - $object['name'] = $relation->name(); - $object['category'] = $relation->type(); - $object['color'] = $relation->color(); - $object['parent'] = $relation->parent(); - $object['iconName'] = $relation->iconName(); - $object['priority'] = $relation->priority(); - $object['members'] = self::vector2array($relation->members()); - - break; - - case 'snippet': - $collection = $this->obj->snippets(); - - $object['name'] = $collection->name(); - $object['snippets'] = array(); - - $snippets = $collection->snippets(); - for ($i=0; $i < $snippets->size(); $i++) { - $snippet = $snippets->get($i); - $object['snippets'][] = array( - 'name' => $snippet->name(), - 'text' => $snippet->text(), - 'type' => $snippet->textType() == Snippet::HTML ? 'html' : 'plain', - 'shortcut' => $snippet->shortCut(), - ); - } - - break; - } - - // adjust content-type string - if ($object['type']) { - $this->CTYPEv2 = 'application/x-vnd.kolab.configuration.' . $object['type']; - } - - $this->data = $object; - return $this->data; - } - - /** - * Callback for kolab_storage_cache to get object specific tags to cache - * - * @return array List of tags to save in cache - */ - public function get_tags() - { - $tags = array(); - - switch ($this->data['type']) { - case 'dictionary': - $tags = array($this->data['language']); - break; - - case 'relation': - $tags = array('category:' . $this->data['category']); - break; - } - - return $tags; - } - - /** - * Callback for kolab_storage_cache to get words to index for fulltext search - * - * @return array List of words to save in cache - */ - public function get_words() - { - $words = array(); - - foreach ((array)$this->data['members'] as $url) { - $member = kolab_storage_config::parse_member_url($url); - - if (empty($member)) { - if (strpos($url, 'urn:uuid:') === 0) { - $words[] = substr($url, 9); - } - } - else if (!empty($member['params']['message-id'])) { - $words[] = $member['params']['message-id']; - } - else { - // derive message identifier from URI - $words[] = md5($url); - } - } - - return $words; - } -} diff --git a/lib/drivers/kolab/plugins/libkolab/lib/kolab_format_contact.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_format_contact.php deleted file mode 100644 index 806a819..0000000 --- a/lib/drivers/kolab/plugins/libkolab/lib/kolab_format_contact.php +++ /dev/null @@ -1,482 +0,0 @@ - - * - * Copyright (C) 2012, Kolab Systems AG - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -class kolab_format_contact extends kolab_format -{ - public $CTYPE = 'application/vcard+xml'; - public $CTYPEv2 = 'application/x-vnd.kolab.contact'; - - protected $objclass = 'Contact'; - protected $read_func = 'readContact'; - protected $write_func = 'writeContact'; - - public static $fulltext_cols = array('name', 'firstname', 'surname', 'middlename', 'email:address'); - - public $phonetypes = array( - 'home' => Telephone::Home, - 'work' => Telephone::Work, - 'text' => Telephone::Text, - 'main' => Telephone::Voice, - 'homefax' => Telephone::Fax, - 'workfax' => Telephone::Fax, - 'mobile' => Telephone::Cell, - 'video' => Telephone::Video, - 'pager' => Telephone::Pager, - 'car' => Telephone::Car, - 'other' => Telephone::Textphone, - ); - - public $emailtypes = array( - 'home' => Email::Home, - 'work' => Email::Work, - 'other' => Email::NoType, - ); - - public $addresstypes = array( - 'home' => Address::Home, - 'work' => Address::Work, - 'office' => 0, - ); - - private $gendermap = array( - 'female' => Contact::Female, - 'male' => Contact::Male, - ); - - private $relatedmap = array( - 'manager' => Related::Manager, - 'assistant' => Related::Assistant, - 'spouse' => Related::Spouse, - 'children' => Related::Child, - ); - - - /** - * Default constructor - */ - function __construct($xmldata = null, $version = 3.0) - { - parent::__construct($xmldata, $version); - - // complete phone types - $this->phonetypes['homefax'] |= Telephone::Home; - $this->phonetypes['workfax'] |= Telephone::Work; - } - - /** - * Set contact properties to the kolabformat object - * - * @param array Contact data as hash array - */ - public function set(&$object) - { - // set common object properties - parent::set($object); - - // do the hard work of setting object values - $nc = new NameComponents; - $nc->setSurnames(self::array2vector($object['surname'])); - $nc->setGiven(self::array2vector($object['firstname'])); - $nc->setAdditional(self::array2vector($object['middlename'])); - $nc->setPrefixes(self::array2vector($object['prefix'])); - $nc->setSuffixes(self::array2vector($object['suffix'])); - $this->obj->setNameComponents($nc); - $this->obj->setName($object['name']); - $this->obj->setCategories(self::array2vector($object['categories'])); - - if (isset($object['nickname'])) - $this->obj->setNickNames(self::array2vector($object['nickname'])); - if (isset($object['jobtitle'])) - $this->obj->setTitles(self::array2vector($object['jobtitle'])); - - // organisation related properties (affiliation) - $org = new Affiliation; - $offices = new vectoraddress; - if ($object['organization']) - $org->setOrganisation($object['organization']); - if ($object['department']) - $org->setOrganisationalUnits(self::array2vector($object['department'])); - if ($object['profession']) - $org->setRoles(self::array2vector($object['profession'])); - - $rels = new vectorrelated; - foreach (array('manager','assistant') as $field) { - if (!empty($object[$field])) { - $reltype = $this->relatedmap[$field]; - foreach ((array)$object[$field] as $value) { - $rels->push(new Related(Related::Text, $value, $reltype)); - } - } - } - $org->setRelateds($rels); - - // im, email, url - $this->obj->setIMaddresses(self::array2vector($object['im'])); - - if (class_exists('vectoremail')) { - $vemails = new vectoremail; - foreach ((array)$object['email'] as $email) { - $type = $this->emailtypes[$email['type']]; - $vemails->push(new Email($email['address'], intval($type))); - } - } - else { - $vemails = self::array2vector(array_map(function($v){ return $v['address']; }, $object['email'])); - } - $this->obj->setEmailAddresses($vemails); - - $vurls = new vectorurl; - foreach ((array)$object['website'] as $url) { - $type = $url['type'] == 'blog' ? Url::Blog : Url::NoType; - $vurls->push(new Url($url['url'], $type)); - } - $this->obj->setUrls($vurls); - - // addresses - $adrs = new vectoraddress; - foreach ((array)$object['address'] as $address) { - $adr = new Address; - $type = $this->addresstypes[$address['type']]; - if (isset($type)) - $adr->setTypes($type); - else if ($address['type']) - $adr->setLabel($address['type']); - if ($address['street']) - $adr->setStreet($address['street']); - if ($address['locality']) - $adr->setLocality($address['locality']); - if ($address['code']) - $adr->setCode($address['code']); - if ($address['region']) - $adr->setRegion($address['region']); - if ($address['country']) - $adr->setCountry($address['country']); - - if ($address['type'] == 'office') - $offices->push($adr); - else - $adrs->push($adr); - } - $this->obj->setAddresses($adrs); - $org->setAddresses($offices); - - // add org affiliation after addresses are set - $orgs = new vectoraffiliation; - $orgs->push($org); - $this->obj->setAffiliations($orgs); - - // telephones - $tels = new vectortelephone; - foreach ((array)$object['phone'] as $phone) { - $tel = new Telephone; - if (isset($this->phonetypes[$phone['type']])) - $tel->setTypes($this->phonetypes[$phone['type']]); - $tel->setNumber($phone['number']); - $tels->push($tel); - } - $this->obj->setTelephones($tels); - - if (isset($object['gender'])) - $this->obj->setGender($this->gendermap[$object['gender']] ? $this->gendermap[$object['gender']] : Contact::NotSet); - if (isset($object['notes'])) - $this->obj->setNote($object['notes']); - if (isset($object['freebusyurl'])) - $this->obj->setFreeBusyUrl($object['freebusyurl']); - if (isset($object['lang'])) - $this->obj->setLanguages(self::array2vector($object['lang'])); - if (isset($object['birthday'])) - $this->obj->setBDay(self::get_datetime($object['birthday'], false, true)); - if (isset($object['anniversary'])) - $this->obj->setAnniversary(self::get_datetime($object['anniversary'], false, true)); - - if (!empty($object['photo'])) { - if ($type = rcube_mime::image_content_type($object['photo'])) - $this->obj->setPhoto($object['photo'], $type); - } - else if (isset($object['photo'])) - $this->obj->setPhoto('',''); - else if ($this->obj->photoMimetype()) // load saved photo for caching - $object['photo'] = $this->obj->photo(); - - // spouse and children are relateds - $rels = new vectorrelated; - foreach (array('spouse','children') as $field) { - if (!empty($object[$field])) { - $reltype = $this->relatedmap[$field]; - foreach ((array)$object[$field] as $value) { - $rels->push(new Related(Related::Text, $value, $reltype)); - } - } - } - // add other relateds - if (is_array($object['related'])) { - foreach ($object['related'] as $value) { - $rels->push(new Related(Related::Text, $value)); - } - } - $this->obj->setRelateds($rels); - - // insert/replace crypto keys - $pgp_index = $pkcs7_index = -1; - $keys = $this->obj->keys(); - for ($i=0; $i < $keys->size(); $i++) { - $key = $keys->get($i); - if ($pgp_index < 0 && $key->type() == Key::PGP) - $pgp_index = $i; - else if ($pkcs7_index < 0 && $key->type() == Key::PKCS7_MIME) - $pkcs7_index = $i; - } - - $pgpkey = $object['pgppublickey'] ? new Key($object['pgppublickey'], Key::PGP) : new Key(); - $pkcs7key = $object['pkcs7publickey'] ? new Key($object['pkcs7publickey'], Key::PKCS7_MIME) : new Key(); - - if ($pgp_index >= 0) - $keys->set($pgp_index, $pgpkey); - else if (!empty($object['pgppublickey'])) - $keys->push($pgpkey); - if ($pkcs7_index >= 0) - $keys->set($pkcs7_index, $pkcs7key); - else if (!empty($object['pkcs7publickey'])) - $keys->push($pkcs7key); - - $this->obj->setKeys($keys); - - // TODO: handle language, gpslocation, etc. - - // set type property for proper caching - $object['_type'] = 'contact'; - - // cache this data - $this->data = $object; - unset($this->data['_formatobj']); - } - - /** - * - */ - public function is_valid() - { - return !$this->formaterror && ($this->data || (is_object($this->obj) && $this->obj->uid() /*$this->obj->isValid()*/)); - } - - /** - * Convert the Contact object into a hash array data structure - * - * @param array Additional data for merge - * - * @return array Contact data as hash array - */ - public function to_array($data = array()) - { - // return cached result - if (!empty($this->data)) - return $this->data; - - // read common object props into local data object - $object = parent::to_array($data); - - $object['name'] = $this->obj->name(); - - $nc = $this->obj->nameComponents(); - $object['surname'] = join(' ', self::vector2array($nc->surnames())); - $object['firstname'] = join(' ', self::vector2array($nc->given())); - $object['middlename'] = join(' ', self::vector2array($nc->additional())); - $object['prefix'] = join(' ', self::vector2array($nc->prefixes())); - $object['suffix'] = join(' ', self::vector2array($nc->suffixes())); - $object['nickname'] = join(' ', self::vector2array($this->obj->nickNames())); - $object['jobtitle'] = join(' ', self::vector2array($this->obj->titles())); - $object['categories'] = self::vector2array($this->obj->categories()); - - // organisation related properties (affiliation) - $orgs = $this->obj->affiliations(); - if ($orgs->size()) { - $org = $orgs->get(0); - $object['organization'] = $org->organisation(); - $object['profession'] = join(' ', self::vector2array($org->roles())); - $object['department'] = join(' ', self::vector2array($org->organisationalUnits())); - $this->read_relateds($org->relateds(), $object); - } - - $object['im'] = self::vector2array($this->obj->imAddresses()); - - $emails = $this->obj->emailAddresses(); - if ($emails instanceof vectoremail) { - $emailtypes = array_flip($this->emailtypes); - for ($i=0; $i < $emails->size(); $i++) { - $email = $emails->get($i); - $object['email'][] = array('address' => $email->address(), 'type' => $emailtypes[$email->types()]); - } - } - else { - $object['email'] = self::vector2array($emails); - } - - $urls = $this->obj->urls(); - for ($i=0; $i < $urls->size(); $i++) { - $url = $urls->get($i); - $subtype = $url->type() == Url::Blog ? 'blog' : 'homepage'; - $object['website'][] = array('url' => $url->url(), 'type' => $subtype); - } - - // addresses - $this->read_addresses($this->obj->addresses(), $object); - if ($org && ($offices = $org->addresses())) - $this->read_addresses($offices, $object, 'office'); - - // telehones - $tels = $this->obj->telephones(); - $teltypes = array_flip($this->phonetypes); - for ($i=0; $i < $tels->size(); $i++) { - $tel = $tels->get($i); - $object['phone'][] = array('number' => $tel->number(), 'type' => $teltypes[$tel->types()]); - } - - $object['notes'] = $this->obj->note(); - $object['freebusyurl'] = $this->obj->freeBusyUrl(); - $object['lang'] = self::vector2array($this->obj->languages()); - - if ($bday = self::php_datetime($this->obj->bDay())) - $object['birthday'] = $bday; - - if ($anniversary = self::php_datetime($this->obj->anniversary())) - $object['anniversary'] = $anniversary; - - $gendermap = array_flip($this->gendermap); - if (($g = $this->obj->gender()) && $gendermap[$g]) - $object['gender'] = $gendermap[$g]; - - if ($this->obj->photoMimetype()) - $object['photo'] = $this->obj->photo(); - else if ($this->xmlobject && ($photo_name = $this->xmlobject->pictureAttachmentName())) - $object['photo'] = $photo_name; - - // relateds -> spouse, children - $this->read_relateds($this->obj->relateds(), $object, 'related'); - - // crypto settings: currently only key values are supported - $keys = $this->obj->keys(); - for ($i=0; is_object($keys) && $i < $keys->size(); $i++) { - $key = $keys->get($i); - if ($key->type() == Key::PGP) - $object['pgppublickey'] = $key->key(); - else if ($key->type() == Key::PKCS7_MIME) - $object['pkcs7publickey'] = $key->key(); - } - - $this->data = $object; - return $this->data; - } - - /** - * Callback for kolab_storage_cache to get words to index for fulltext search - * - * @return array List of words to save in cache - */ - public function get_words() - { - $data = ''; - foreach (self::$fulltext_cols as $colname) { - list($col, $field) = explode(':', $colname); - - if ($field) { - $a = array(); - foreach ((array)$this->data[$col] as $attr) - $a[] = $attr[$field]; - $val = join(' ', $a); - } - else { - $val = is_array($this->data[$col]) ? join(' ', $this->data[$col]) : $this->data[$col]; - } - - if (strlen($val)) - $data .= $val . ' '; - } - - return array_unique(rcube_utils::normalize_string($data, true)); - } - - /** - * Callback for kolab_storage_cache to get object specific tags to cache - * - * @return array List of tags to save in cache - */ - public function get_tags() - { - $tags = array(); - - if (!empty($this->data['birthday'])) { - $tags[] = 'x-has-birthday'; - } - - return $tags; - } - - /** - * Helper method to copy contents of an Address vector to the contact data object - */ - private function read_addresses($addresses, &$object, $type = null) - { - $adrtypes = array_flip($this->addresstypes); - - for ($i=0; $i < $addresses->size(); $i++) { - $adr = $addresses->get($i); - $object['address'][] = array( - 'type' => $type ? $type : ($adrtypes[$adr->types()] ? $adrtypes[$adr->types()] : ''), /*$adr->label()),*/ - 'street' => $adr->street(), - 'code' => $adr->code(), - 'locality' => $adr->locality(), - 'region' => $adr->region(), - 'country' => $adr->country() - ); - } - } - - /** - * Helper method to map contents of a Related vector to the contact data object - */ - private function read_relateds($rels, &$object, $catchall = null) - { - $typemap = array_flip($this->relatedmap); - - for ($i=0; $i < $rels->size(); $i++) { - $rel = $rels->get($i); - if ($rel->type() != Related::Text) // we can't handle UID relations yet - continue; - - $known = false; - $types = $rel->relationTypes(); - foreach ($typemap as $t => $field) { - if ($types & $t) { - $object[$field][] = $rel->text(); - $known = true; - break; - } - } - - if (!$known && $catchall) { - $object[$catchall][] = $rel->text(); - } - } - } -} diff --git a/lib/drivers/kolab/plugins/libkolab/lib/kolab_format_distributionlist.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_format_distributionlist.php deleted file mode 100644 index 88c6f7b..0000000 --- a/lib/drivers/kolab/plugins/libkolab/lib/kolab_format_distributionlist.php +++ /dev/null @@ -1,125 +0,0 @@ - - * - * Copyright (C) 2012, Kolab Systems AG - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -class kolab_format_distributionlist extends kolab_format -{ - public $CTYPE = 'application/vcard+xml'; - public $CTYPEv2 = 'application/x-vnd.kolab.distribution-list'; - - protected $objclass = 'DistList'; - protected $read_func = 'readDistlist'; - protected $write_func = 'writeDistlist'; - - - /** - * Set properties to the kolabformat object - * - * @param array Object data as hash array - */ - public function set(&$object) - { - // set common object properties - parent::set($object); - - $this->obj->setName($object['name']); - - $seen = array(); - $members = new vectorcontactref; - foreach ((array)$object['member'] as $i => $member) { - if ($member['uid']) { - $key = 'uid:' . $member['uid']; - $m = new ContactReference(ContactReference::UidReference, $member['uid']); - } - else if ($member['email']) { - $key = 'mailto:' . $member['email']; - $m = new ContactReference(ContactReference::EmailReference, $member['email']); - $m->setName($member['name']); - } - else { - continue; - } - - if (!$seen[$key]++) { - $members->push($m); - } - else { - // remove dupes for caching - unset($object['member'][$i]); - } - } - - $this->obj->setMembers($members); - - // set type property for proper caching - $object['_type'] = 'distribution-list'; - - // cache this data - $this->data = $object; - unset($this->data['_formatobj']); - } - - public function is_valid() - { - return !$this->formaterror && ($this->data || (is_object($this->obj) && $this->obj->isValid())); - } - - /** - * Convert the Distlist object into a hash array data structure - * - * @param array Additional data for merge - * - * @return array Distribution list data as hash array - */ - public function to_array($data = array()) - { - // return cached result - if (!empty($this->data)) - return $this->data; - - // read common object props into local data object - $object = parent::to_array($data); - - // add object properties - $object += array( - 'name' => $this->obj->name(), - 'member' => array(), - '_type' => 'distribution-list', - ); - - $members = $this->obj->members(); - for ($i=0; $i < $members->size(); $i++) { - $member = $members->get($i); -// if ($member->type() == ContactReference::UidReference && ($uid = $member->uid())) - $object['member'][] = array( - 'uid' => $member->uid(), - 'email' => $member->email(), - 'name' => $member->name(), - ); - } - - $this->data = $object; - return $this->data; - } - -} diff --git a/lib/drivers/kolab/plugins/libkolab/lib/kolab_format_event.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_format_event.php deleted file mode 100644 index feea60d..0000000 --- a/lib/drivers/kolab/plugins/libkolab/lib/kolab_format_event.php +++ /dev/null @@ -1,337 +0,0 @@ - - * - * Copyright (C) 2012, Kolab Systems AG - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -class kolab_format_event extends kolab_format_xcal -{ - public $CTYPEv2 = 'application/x-vnd.kolab.event'; - - public static $scheduling_properties = array('start', 'end', 'allday', 'recurrence', 'location', 'status', 'cancelled'); - - protected $objclass = 'Event'; - protected $read_func = 'readEvent'; - protected $write_func = 'writeEvent'; - - /** - * Default constructor - */ - function __construct($data = null, $version = 3.0) - { - parent::__construct(is_string($data) ? $data : null, $version); - - // got an Event object as argument - if (is_object($data) && is_a($data, $this->objclass)) { - $this->obj = $data; - $this->loaded = true; - } - - // copy static property overriden by this class - $this->_scheduling_properties = self::$scheduling_properties; - } - - /** - * Clones into an instance of libcalendaring's extended EventCal class - * - * @return mixed EventCal object or false on failure - */ - public function to_libcal() - { - static $error_logged = false; - - if (class_exists('kolabcalendaring')) { - return new EventCal($this->obj); - } - else if (!$error_logged) { - $error_logged = true; - rcube::raise_error(array( - 'code' => 900, 'type' => 'php', - 'message' => "required kolabcalendaring module not found" - ), true); - } - - return false; - } - - /** - * Set event properties to the kolabformat object - * - * @param array Event data as hash array - */ - public function set(&$object) - { - // set common xcal properties - parent::set($object); - - // do the hard work of setting object values - $this->obj->setStart(self::get_datetime($object['start'], null, $object['allday'])); - $this->obj->setEnd(self::get_datetime($object['end'], null, $object['allday'])); - $this->obj->setTransparency($object['free_busy'] == 'free'); - - $status = kolabformat::StatusUndefined; - if ($object['free_busy'] == 'tentative') - $status = kolabformat::StatusTentative; - if ($object['cancelled']) - $status = kolabformat::StatusCancelled; - else if ($object['status'] && array_key_exists($object['status'], $this->status_map)) - $status = $this->status_map[$object['status']]; - $this->obj->setStatus($status); - - // save (recurrence) exceptions - if (is_array($object['recurrence']) && is_array($object['recurrence']['EXCEPTIONS']) && !isset($object['exceptions'])) { - $object['exceptions'] = $object['recurrence']['EXCEPTIONS']; - } - - if (is_array($object['exceptions'])) { - $recurrence_id_format = libkolab::recurrence_id_format($object); - $vexceptions = new vectorevent; - foreach ($object['exceptions'] as $i => $exception) { - $exevent = new kolab_format_event; - $exevent->set(($compacted = $this->compact_exception($exception, $object))); // only save differing values - - // get value for recurrence-id - $recurrence_id = null; - if (!empty($exception['recurrence_date']) && is_a($exception['recurrence_date'], 'DateTime')) { - $recurrence_id = $exception['recurrence_date']; - $compacted['_instance'] = $recurrence_id->format($recurrence_id_format); - } - else if (!empty($exception['_instance']) && strlen($exception['_instance']) > 4) { - $recurrence_id = rcube_utils::anytodatetime($exception['_instance'], $object['start']->getTimezone()); - $compacted['recurrence_date'] = $recurrence_id; - } - - $exevent->obj->setRecurrenceID(self::get_datetime($recurrence_id ?: $exception['start'], null, $object['allday']), (bool)$exception['thisandfuture']); - - $vexceptions->push($exevent->obj); - - // write cleaned-up exception data back to memory/cache - $object['exceptions'][$i] = $this->expand_exception($exevent->data, $object); - } - $this->obj->setExceptions($vexceptions); - - // link with recurrence.EXCEPTIONS for compatibility - if (is_array($object['recurrence'])) { - $object['recurrence']['EXCEPTIONS'] = &$object['exceptions']; - } - } - - if ($object['recurrence_date'] && $object['recurrence_date'] instanceof DateTime) { - if ($object['recurrence']) { - // unset recurrence_date for master events with rrule - $object['recurrence_date'] = null; - } - $this->obj->setRecurrenceID(self::get_datetime($object['recurrence_date'], null, $object['allday']), (bool)$object['thisandfuture']); - } - - // cache this data - $this->data = $object; - unset($this->data['_formatobj']); - } - - /** - * - */ - public function is_valid() - { - return !$this->formaterror && (($this->data && !empty($this->data['start']) && !empty($this->data['end'])) || - (is_object($this->obj) && $this->obj->isValid() && $this->obj->uid())); - } - - /** - * Convert the Event object into a hash array data structure - * - * @param array Additional data for merge - * - * @return array Event data as hash array - */ - public function to_array($data = array()) - { - // return cached result - if (!empty($this->data)) - return $this->data; - - // read common xcal props - $object = parent::to_array($data); - - // read object properties - $object += array( - 'end' => self::php_datetime($this->obj->end()), - 'allday' => $this->obj->start()->isDateOnly(), - 'free_busy' => $this->obj->transparency() ? 'free' : 'busy', // TODO: transparency is only boolean - 'attendees' => array(), - ); - - // derive event end from duration (#1916) - if (!$object['end'] && $object['start'] && ($duration = $this->obj->duration()) && $duration->isValid()) { - $interval = new DateInterval('PT0S'); - $interval->d = $duration->weeks() * 7 + $duration->days(); - $interval->h = $duration->hours(); - $interval->i = $duration->minutes(); - $interval->s = $duration->seconds(); - $object['end'] = clone $object['start']; - $object['end']->add($interval); - } - - // organizer is part of the attendees list in Roundcube - if ($object['organizer']) { - $object['organizer']['role'] = 'ORGANIZER'; - array_unshift($object['attendees'], $object['organizer']); - } - - // status defines different event properties... - $status = $this->obj->status(); - if ($status == kolabformat::StatusTentative) - $object['free_busy'] = 'tentative'; - else if ($status == kolabformat::StatusCancelled) - $object['cancelled'] = true; - - // this is an exception object - if ($this->obj->recurrenceID()->isValid()) { - $object['thisandfuture'] = $this->obj->thisAndFuture(); - $object['recurrence_date'] = self::php_datetime($this->obj->recurrenceID()); - } - // read exception event objects - if (($exceptions = $this->obj->exceptions()) && is_object($exceptions) && $exceptions->size()) { - $recurrence_exceptions = array(); - $recurrence_id_format = libkolab::recurrence_id_format($object); - for ($i=0; $i < $exceptions->size(); $i++) { - if (($exobj = $exceptions->get($i))) { - $exception = new kolab_format_event($exobj); - if ($exception->is_valid()) { - $exdata = $exception->to_array(); - - // fix date-only recurrence ID saved by old versions - if ($exdata['recurrence_date'] && $exdata['recurrence_date']->_dateonly && !$object['allday']) { - $exdata['recurrence_date']->setTimezone($object['start']->getTimezone()); - $exdata['recurrence_date']->setTime($object['start']->format('G'), intval($object['start']->format('i')), intval($object['start']->format('s'))); - } - - $recurrence_id = $exdata['recurrence_date'] ?: $exdata['start']; - $exdata['_instance'] = $recurrence_id->format($recurrence_id_format); - $recurrence_exceptions[] = $this->expand_exception($exdata, $object); - } - } - } - $object['exceptions'] = $recurrence_exceptions; - - // also link with recurrence.EXCEPTIONS for compatibility - if (is_array($object['recurrence'])) { - $object['recurrence']['EXCEPTIONS'] = &$object['exceptions']; - } - } - - return $this->data = $object; - } - - /** - * Getter for a single instance from a recurrence series or stored subcomponents - * - * @param mixed The recurrence-id of the requested instance, either as string or a DateTime object - * @return array Event data as hash array or null if not found - */ - public function get_instance($recurrence_id) - { - $result = null; - $object = $this->to_array(); - - $recurrence_id_format = libkolab::recurrence_id_format($object); - $instance_id = $recurrence_id instanceof DateTime ? $recurrence_id->format($recurrence_id_format) : strval($recurrence_id); - - if ($object['recurrence_date'] instanceof DateTime) { - if ($object['recurrence_date']->format($recurrence_id_format) == $instance_id) { - $result = $object; - } - } - - if (!$result && is_array($object['exceptions'])) { - foreach ($object['exceptions'] as $exception) { - if ($exception['_instance'] == $instance_id) { - $result = $exception; - $result['isexception'] = 1; - break; - } - } - } - - // TODO: compute instances from recurrence rule and return the matching instance - // clone from plugins/calendar/drivers/kolab/kolab_calendar::get_recurring_events() - - return $result; - } - - /** - * Callback for kolab_storage_cache to get object specific tags to cache - * - * @return array List of tags to save in cache - */ - public function get_tags($obj = null) - { - $tags = parent::get_tags($obj); - $object = $obj ?: $this->data; - - foreach ((array)$object['categories'] as $cat) { - $tags[] = rcube_utils::normalize_string($cat); - } - - return array_unique($tags); - } - - /** - * Remove some attributes from the exception container - */ - private function compact_exception($exception, $master) - { - $forbidden = array('recurrence','exceptions','organizer','_attachments'); - - foreach ($forbidden as $prop) { - if (array_key_exists($prop, $exception)) { - unset($exception[$prop]); - } - } - - // preserve this property for date serialization - $exception['allday'] = $master['allday']; - - return $exception; - } - - /** - * Copy attributes not specified by the exception from the master event - */ - private function expand_exception($exception, $master) - { - $is_recurring = !empty($master['recurrence']); - - foreach ($master as $prop => $value) { - if (empty($exception[$prop]) && !empty($value) && $prop != 'exceptions' && $prop[0] != '_' - && ($is_recurring || in_array($prop, array('uid','organizer','_attachments')))) { - $exception[$prop] = $value; - if ($prop == 'recurrence') { - unset($exception[$prop]['EXCEPTIONS']); - } - } - } - - return $exception; - } - -} diff --git a/lib/drivers/kolab/plugins/libkolab/lib/kolab_format_file.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_format_file.php deleted file mode 100644 index 34c0ca6..0000000 --- a/lib/drivers/kolab/plugins/libkolab/lib/kolab_format_file.php +++ /dev/null @@ -1,156 +0,0 @@ - - * @author Aleksander Machniak - * - * Copyright (C) 2012, Kolab Systems AG - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -class kolab_format_file extends kolab_format -{ - public $CTYPE = 'application/vnd.kolab+xml'; - - protected $objclass = 'File'; - protected $read_func = 'kolabformat::readKolabFile'; - protected $write_func = 'kolabformat::writeKolabFile'; - - protected $sensitivity_map = array( - 'public' => kolabformat::ClassPublic, - 'private' => kolabformat::ClassPrivate, - 'confidential' => kolabformat::ClassConfidential, - ); - - /** - * Set properties to the kolabformat object - * - * @param array Object data as hash array - */ - public function set(&$object) - { - // set common object properties - parent::set($object); - - $this->obj->setClassification($this->sensitivity_map[$object['sensitivity']]); - $this->obj->setCategories(self::array2vector($object['categories'])); - - if (isset($object['notes'])) { - $this->obj->setNote($object['notes']); - } - - // Add file attachment - if (!empty($object['_attachments'])) { - $cid = key($object['_attachments']); - $attach_attr = $object['_attachments'][$cid]; - $attach = new Attachment; - - $attach->setLabel((string)$attach_attr['name']); - $attach->setUri('cid:' . $cid, $attach_attr['mimetype']); - $this->obj->setFile($attach); - - // make sure size is set, so object saved in cache contains this info - if (!isset($attach_attr['size'])) { - $size = 0; - - if (!empty($attach_attr['content'])) { - if (is_resource($attach_attr['content'])) { - $stat = fstat($attach_attr['content']); - $size = $stat ? $stat['size'] : 0; - } - else { - $size = strlen($attach_attr['content']); - } - } - else if (isset($attach_attr['path'])) { - $size = @filesize($attach_attr['path']); - } - - $object['_attachments'][$cid]['size'] = $size; - } - } - - // cache this data - $this->data = $object; - unset($this->data['_formatobj']); - } - - /** - * Check if object's data validity - */ - public function is_valid() - { - return !$this->formaterror && ($this->data || (is_object($this->obj) && $this->obj->isValid())); - } - - /** - * Convert the Configuration object into a hash array data structure - * - * @param array Additional data for merge - * - * @return array Config object data as hash array - */ - public function to_array($data = array()) - { - // return cached result - if (!empty($this->data)) { - return $this->data; - } - - // read common object props into local data object - $object = parent::to_array($data); - - $sensitivity_map = array_flip($this->sensitivity_map); - - // read object properties - $object += array( - 'sensitivity' => $sensitivity_map[$this->obj->classification()], - 'categories' => self::vector2array($this->obj->categories()), - 'notes' => $this->obj->note(), - ); - - return $this->data = $object; - } - - /** - * Callback for kolab_storage_cache to get object specific tags to cache - * - * @return array List of tags to save in cache - */ - public function get_tags() - { - $tags = array(); - - foreach ((array)$this->data['categories'] as $cat) { - $tags[] = rcube_utils::normalize_string($cat); - } - - // Add file mimetype to tags - if (!empty($this->data['_attachments'])) { - reset($this->data['_attachments']); - $key = key($this->data['_attachments']); - $attachment = $this->data['_attachments'][$key]; - - if ($attachment['mimetype']) { - $tags[] = $attachment['mimetype']; - } - } - - return $tags; - } -} diff --git a/lib/drivers/kolab/plugins/libkolab/lib/kolab_format_journal.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_format_journal.php deleted file mode 100644 index f7ccd31..0000000 --- a/lib/drivers/kolab/plugins/libkolab/lib/kolab_format_journal.php +++ /dev/null @@ -1,82 +0,0 @@ - - * - * Copyright (C) 2012, Kolab Systems AG - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -class kolab_format_journal extends kolab_format -{ - public $CTYPE = 'application/calendar+xml'; - public $CTYPEv2 = 'application/x-vnd.kolab.journal'; - - protected $objclass = 'Journal'; - protected $read_func = 'readJournal'; - protected $write_func = 'writeJournal'; - - - /** - * Set properties to the kolabformat object - * - * @param array Object data as hash array - */ - public function set(&$object) - { - // set common object properties - parent::set($object); - - // TODO: set object propeties - - // cache this data - $this->data = $object; - unset($this->data['_formatobj']); - } - - /** - * - */ - public function is_valid() - { - return !$this->formaterror && ($this->data || (is_object($this->obj) && $this->obj->isValid())); - } - - /** - * Convert the Configuration object into a hash array data structure - * - * @param array Additional data for merge - * - * @return array Config object data as hash array - */ - public function to_array($data = array()) - { - // return cached result - if (!empty($this->data)) - return $this->data; - - // read common object props into local data object - $object = parent::to_array($data); - - // TODO: read object properties - - $this->data = $object; - return $this->data; - } - -} diff --git a/lib/drivers/kolab/plugins/libkolab/lib/kolab_format_note.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_format_note.php deleted file mode 100644 index bca5156..0000000 --- a/lib/drivers/kolab/plugins/libkolab/lib/kolab_format_note.php +++ /dev/null @@ -1,153 +0,0 @@ - - * - * Copyright (C) 2012, Kolab Systems AG - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -class kolab_format_note extends kolab_format -{ - public $CTYPE = 'application/vnd.kolab+xml'; - public $CTYPEv2 = 'application/x-vnd.kolab.note'; - - public static $fulltext_cols = array('title', 'description', 'categories'); - - protected $objclass = 'Note'; - protected $read_func = 'readNote'; - protected $write_func = 'writeNote'; - - protected $sensitivity_map = array( - 'public' => kolabformat::ClassPublic, - 'private' => kolabformat::ClassPrivate, - 'confidential' => kolabformat::ClassConfidential, - ); - - /** - * Set properties to the kolabformat object - * - * @param array Object data as hash array - */ - public function set(&$object) - { - // set common object properties - parent::set($object); - - $this->obj->setSummary($object['title']); - $this->obj->setDescription($object['description']); - $this->obj->setClassification($this->sensitivity_map[$object['sensitivity']]); - $this->obj->setCategories(self::array2vector($object['categories'])); - - $this->set_attachments($object); - - // cache this data - $this->data = $object; - unset($this->data['_formatobj']); - } - - /** - * - */ - public function is_valid() - { - return !$this->formaterror && ($this->data || (is_object($this->obj) && $this->obj->isValid())); - } - - /** - * Convert the Configuration object into a hash array data structure - * - * @param array Additional data for merge - * - * @return array Config object data as hash array - */ - public function to_array($data = array()) - { - // return cached result - if (!empty($this->data)) - return $this->data; - - // read common object props into local data object - $object = parent::to_array($data); - - $sensitivity_map = array_flip($this->sensitivity_map); - - // read object properties - $object += array( - 'sensitivity' => $sensitivity_map[$this->obj->classification()], - 'categories' => self::vector2array($this->obj->categories()), - 'title' => $this->obj->summary(), - 'description' => $this->obj->description(), - ); - - $this->get_attachments($object); - - return $this->data = $object; - } - - /** - * Callback for kolab_storage_cache to get object specific tags to cache - * - * @return array List of tags to save in cache - */ - public function get_tags() - { - $tags = array(); - - foreach ((array)$this->data['categories'] as $cat) { - $tags[] = rcube_utils::normalize_string($cat); - } - - // add tag for message references - foreach ((array)$this->data['links'] as $link) { - $url = parse_url($link); - if ($url['scheme'] == 'imap') { - parse_str($url['query'], $param); - $tags[] = 'ref:' . trim($param['message-id'] ?: urldecode($url['fragment']), '<> '); - } - } - - return $tags; - } - - /** - * Callback for kolab_storage_cache to get words to index for fulltext search - * - * @return array List of words to save in cache - */ - public function get_words() - { - $data = ''; - foreach (self::$fulltext_cols as $col) { - // convert HTML content to plain text - if ($col == 'description' && preg_match('/<(html|body)(\s[a-z]|>)/', $this->data[$col], $m) && strpos($this->data[$col], '')) { - $converter = new rcube_html2text($this->data[$col], false, false, 0); - $val = $converter->get_text(); - } - else { - $val = is_array($this->data[$col]) ? join(' ', $this->data[$col]) : $this->data[$col]; - } - - if (strlen($val)) - $data .= $val . ' '; - } - - return array_filter(array_unique(rcube_utils::normalize_string($data, true))); - } - -} diff --git a/lib/drivers/kolab/plugins/libkolab/lib/kolab_format_task.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_format_task.php deleted file mode 100644 index cb35f98..0000000 --- a/lib/drivers/kolab/plugins/libkolab/lib/kolab_format_task.php +++ /dev/null @@ -1,155 +0,0 @@ - - * - * Copyright (C) 2012, Kolab Systems AG - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -class kolab_format_task extends kolab_format_xcal -{ - public $CTYPEv2 = 'application/x-vnd.kolab.task'; - - public static $scheduling_properties = array('start', 'due', 'summary', 'status'); - - protected $objclass = 'Todo'; - protected $read_func = 'readTodo'; - protected $write_func = 'writeTodo'; - - /** - * Default constructor - */ - function __construct($data = null, $version = 3.0) - { - parent::__construct(is_string($data) ? $data : null, $version); - - // copy static property overriden by this class - $this->_scheduling_properties = self::$scheduling_properties; - } - - /** - * Set properties to the kolabformat object - * - * @param array Object data as hash array - */ - public function set(&$object) - { - // set common xcal properties - parent::set($object); - - $this->obj->setPercentComplete(intval($object['complete'])); - - $status = kolabformat::StatusUndefined; - if ($object['complete'] == 100 && !array_key_exists('status', $object)) - $status = kolabformat::StatusCompleted; - else if ($object['status'] && array_key_exists($object['status'], $this->status_map)) - $status = $this->status_map[$object['status']]; - $this->obj->setStatus($status); - - $this->obj->setStart(self::get_datetime($object['start'], null, $object['start']->_dateonly)); - $this->obj->setDue(self::get_datetime($object['due'], null, $object['due']->_dateonly)); - - $related = new vectors; - if (!empty($object['parent_id'])) - $related->push($object['parent_id']); - $this->obj->setRelatedTo($related); - - // cache this data - $this->data = $object; - unset($this->data['_formatobj']); - } - - /** - * - */ - public function is_valid() - { - return !$this->formaterror && ($this->data || (is_object($this->obj) && $this->obj->isValid())); - } - - /** - * Convert the Configuration object into a hash array data structure - * - * @param array Additional data for merge - * - * @return array Config object data as hash array - */ - public function to_array($data = array()) - { - // return cached result - if (!empty($this->data)) - return $this->data; - - // read common xcal props - $object = parent::to_array($data); - - $object['complete'] = intval($this->obj->percentComplete()); - - // if due date is set - if ($due = $this->obj->due()) - $object['due'] = self::php_datetime($due); - - // related-to points to parent task; we only support one relation - $related = self::vector2array($this->obj->relatedTo()); - if (count($related)) - $object['parent_id'] = $related[0]; - - // TODO: map more properties - - $this->data = $object; - return $this->data; - } - - /** - * Return the reference date for recurrence and alarms - * - * @return mixed DateTime instance of null if no refdate is available - */ - public function get_reference_date() - { - if ($this->data['due'] && $this->data['due'] instanceof DateTime) { - return $this->data['due']; - } - - return self::php_datetime($this->obj->due()) ?: parent::get_reference_date(); - } - - /** - * Callback for kolab_storage_cache to get object specific tags to cache - * - * @return array List of tags to save in cache - */ - public function get_tags($obj = null) - { - $tags = parent::get_tags($obj); - $object = $obj ?: $this->data; - - if ($object['status'] == 'COMPLETED' || ($object['complete'] == 100 && empty($object['status']))) - $tags[] = 'x-complete'; - - if ($object['priority'] == 1) - $tags[] = 'x-flagged'; - - if ($object['parent_id']) - $tags[] = 'x-parent:' . $object['parent_id']; - - return array_unique($tags); - } - -} diff --git a/lib/drivers/kolab/plugins/libkolab/lib/kolab_format_xcal.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_format_xcal.php deleted file mode 100644 index a9dd70c..0000000 --- a/lib/drivers/kolab/plugins/libkolab/lib/kolab_format_xcal.php +++ /dev/null @@ -1,714 +0,0 @@ - - * - * Copyright (C) 2012, Kolab Systems AG - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -abstract class kolab_format_xcal extends kolab_format -{ - public $CTYPE = 'application/calendar+xml'; - - public static $fulltext_cols = array('title', 'description', 'location', 'attendees:name', 'attendees:email', 'categories'); - - public static $scheduling_properties = array('start', 'end', 'location'); - - protected $_scheduling_properties = null; - - protected $sensitivity_map = array( - 'public' => kolabformat::ClassPublic, - 'private' => kolabformat::ClassPrivate, - 'confidential' => kolabformat::ClassConfidential, - ); - - protected $role_map = array( - 'REQ-PARTICIPANT' => kolabformat::Required, - 'OPT-PARTICIPANT' => kolabformat::Optional, - 'NON-PARTICIPANT' => kolabformat::NonParticipant, - 'CHAIR' => kolabformat::Chair, - ); - - protected $cutype_map = array( - 'INDIVIDUAL' => kolabformat::CutypeIndividual, - 'GROUP' => kolabformat::CutypeGroup, - 'ROOM' => kolabformat::CutypeRoom, - 'RESOURCE' => kolabformat::CutypeResource, - 'UNKNOWN' => kolabformat::CutypeUnknown, - ); - - protected $rrule_type_map = array( - 'MINUTELY' => RecurrenceRule::Minutely, - 'HOURLY' => RecurrenceRule::Hourly, - 'DAILY' => RecurrenceRule::Daily, - 'WEEKLY' => RecurrenceRule::Weekly, - 'MONTHLY' => RecurrenceRule::Monthly, - 'YEARLY' => RecurrenceRule::Yearly, - ); - - protected $weekday_map = array( - 'MO' => kolabformat::Monday, - 'TU' => kolabformat::Tuesday, - 'WE' => kolabformat::Wednesday, - 'TH' => kolabformat::Thursday, - 'FR' => kolabformat::Friday, - 'SA' => kolabformat::Saturday, - 'SU' => kolabformat::Sunday, - ); - - protected $alarm_type_map = array( - 'DISPLAY' => Alarm::DisplayAlarm, - 'EMAIL' => Alarm::EMailAlarm, - 'AUDIO' => Alarm::AudioAlarm, - ); - - protected $status_map = array( - 'NEEDS-ACTION' => kolabformat::StatusNeedsAction, - 'IN-PROCESS' => kolabformat::StatusInProcess, - 'COMPLETED' => kolabformat::StatusCompleted, - 'CANCELLED' => kolabformat::StatusCancelled, - 'TENTATIVE' => kolabformat::StatusTentative, - 'CONFIRMED' => kolabformat::StatusConfirmed, - 'DRAFT' => kolabformat::StatusDraft, - 'FINAL' => kolabformat::StatusFinal, - ); - - protected $part_status_map = array( - 'UNKNOWN' => kolabformat::PartNeedsAction, - 'NEEDS-ACTION' => kolabformat::PartNeedsAction, - 'TENTATIVE' => kolabformat::PartTentative, - 'ACCEPTED' => kolabformat::PartAccepted, - 'DECLINED' => kolabformat::PartDeclined, - 'DELEGATED' => kolabformat::PartDelegated, - ); - - - /** - * Convert common xcard properties into a hash array data structure - * - * @param array Additional data for merge - * - * @return array Object data as hash array - */ - public function to_array($data = array()) - { - // read common object props - $object = parent::to_array($data); - - $status_map = array_flip($this->status_map); - $sensitivity_map = array_flip($this->sensitivity_map); - - $object += array( - 'sequence' => intval($this->obj->sequence()), - 'title' => $this->obj->summary(), - 'location' => $this->obj->location(), - 'description' => $this->obj->description(), - 'url' => $this->obj->url(), - 'status' => $status_map[$this->obj->status()], - 'sensitivity' => $sensitivity_map[$this->obj->classification()], - 'priority' => $this->obj->priority(), - 'categories' => self::vector2array($this->obj->categories()), - 'start' => self::php_datetime($this->obj->start()), - ); - - if (method_exists($this->obj, 'comment')) { - $object['comment'] = $this->obj->comment(); - } - - // read organizer and attendees - if (($organizer = $this->obj->organizer()) && ($organizer->email() || $organizer->name())) { - $object['organizer'] = array( - 'email' => $organizer->email(), - 'name' => $organizer->name(), - ); - } - - $role_map = array_flip($this->role_map); - $cutype_map = array_flip($this->cutype_map); - $part_status_map = array_flip($this->part_status_map); - $attvec = $this->obj->attendees(); - for ($i=0; $i < $attvec->size(); $i++) { - $attendee = $attvec->get($i); - $cr = $attendee->contact(); - if ($cr->email() != $object['organizer']['email']) { - $delegators = $delegatees = array(); - $vdelegators = $attendee->delegatedFrom(); - for ($j=0; $j < $vdelegators->size(); $j++) { - $delegators[] = $vdelegators->get($j)->email(); - } - $vdelegatees = $attendee->delegatedTo(); - for ($j=0; $j < $vdelegatees->size(); $j++) { - $delegatees[] = $vdelegatees->get($j)->email(); - } - - $object['attendees'][] = array( - 'role' => $role_map[$attendee->role()], - 'cutype' => $cutype_map[$attendee->cutype()], - 'status' => $part_status_map[$attendee->partStat()], - 'rsvp' => $attendee->rsvp(), - 'email' => $cr->email(), - 'name' => $cr->name(), - 'delegated-from' => $delegators, - 'delegated-to' => $delegatees, - ); - } - } - - // read recurrence rule - if (($rr = $this->obj->recurrenceRule()) && $rr->isValid()) { - $rrule_type_map = array_flip($this->rrule_type_map); - $object['recurrence'] = array('FREQ' => $rrule_type_map[$rr->frequency()]); - - if ($intvl = $rr->interval()) - $object['recurrence']['INTERVAL'] = $intvl; - - if (($count = $rr->count()) && $count > 0) { - $object['recurrence']['COUNT'] = $count; - } - else if ($until = self::php_datetime($rr->end())) { - $refdate = $this->get_reference_date(); - if ($refdate && $refdate instanceof DateTime && !$refdate->_dateonly) { - $until->setTime($refdate->format('G'), $refdate->format('i'), 0); - } - $object['recurrence']['UNTIL'] = $until; - } - - if (($byday = $rr->byday()) && $byday->size()) { - $weekday_map = array_flip($this->weekday_map); - $weekdays = array(); - for ($i=0; $i < $byday->size(); $i++) { - $daypos = $byday->get($i); - $prefix = $daypos->occurence(); - $weekdays[] = ($prefix ? $prefix : '') . $weekday_map[$daypos->weekday()]; - } - $object['recurrence']['BYDAY'] = join(',', $weekdays); - } - - if (($bymday = $rr->bymonthday()) && $bymday->size()) { - $object['recurrence']['BYMONTHDAY'] = join(',', self::vector2array($bymday)); - } - - if (($bymonth = $rr->bymonth()) && $bymonth->size()) { - $object['recurrence']['BYMONTH'] = join(',', self::vector2array($bymonth)); - } - - if ($exdates = $this->obj->exceptionDates()) { - for ($i=0; $i < $exdates->size(); $i++) { - if ($exdate = self::php_datetime($exdates->get($i))) - $object['recurrence']['EXDATE'][] = $exdate; - } - } - } - - if ($rdates = $this->obj->recurrenceDates()) { - for ($i=0; $i < $rdates->size(); $i++) { - if ($rdate = self::php_datetime($rdates->get($i))) - $object['recurrence']['RDATE'][] = $rdate; - } - } - - // read alarm - $valarms = $this->obj->alarms(); - $alarm_types = array_flip($this->alarm_type_map); - $object['valarms'] = array(); - for ($i=0; $i < $valarms->size(); $i++) { - $alarm = $valarms->get($i); - $type = $alarm_types[$alarm->type()]; - - if ($type == 'DISPLAY' || $type == 'EMAIL' || $type == 'AUDIO') { // only some alarms are supported - $valarm = array( - 'action' => $type, - 'summary' => $alarm->summary(), - 'description' => $alarm->description(), - ); - - if ($type == 'EMAIL') { - $valarm['attendees'] = array(); - $attvec = $alarm->attendees(); - for ($j=0; $j < $attvec->size(); $j++) { - $cr = $attvec->get($j); - $valarm['attendees'][] = $cr->email(); - } - } - else if ($type == 'AUDIO') { - $attach = $alarm->audioFile(); - $valarm['uri'] = $attach->uri(); - } - - if ($start = self::php_datetime($alarm->start())) { - $object['alarms'] = '@' . $start->format('U'); - $valarm['trigger'] = $start; - } - else if ($offset = $alarm->relativeStart()) { - $prefix = $offset->isNegative() ? '-' : '+'; - $value = ''; - $time = ''; - - if ($w = $offset->weeks()) $value .= $w . 'W'; - else if ($d = $offset->days()) $value .= $d . 'D'; - else if ($h = $offset->hours()) $time .= $h . 'H'; - else if ($m = $offset->minutes()) $time .= $m . 'M'; - else if ($s = $offset->seconds()) $time .= $s . 'S'; - - // assume 'at event time' - if (empty($value) && empty($time)) { - $prefix = ''; - $time = '0S'; - } - - $object['alarms'] = $prefix . $value . $time; - $valarm['trigger'] = $prefix . 'P' . $value . ($time ? 'T' . $time : ''); - - if ($alarm->relativeTo() == kolabformat::End) { - $valarm['related'] == 'END'; - } - } - - // read alarm duration and repeat properties - if (($duration = $alarm->duration()) && $duration->isValid()) { - $value = $time = ''; - - if ($w = $duration->weeks()) $value .= $w . 'W'; - else if ($d = $duration->days()) $value .= $d . 'D'; - else if ($h = $duration->hours()) $time .= $h . 'H'; - else if ($m = $duration->minutes()) $time .= $m . 'M'; - else if ($s = $duration->seconds()) $time .= $s . 'S'; - - $valarm['duration'] = 'P' . $value . ($time ? 'T' . $time : ''); - $valarm['repeat'] = $alarm->numrepeat(); - } - - $object['alarms'] .= ':' . $type; // legacy property - $object['valarms'][] = array_filter($valarm); - } - } - - $this->get_attachments($object); - - return $object; - } - - - /** - * Set common xcal properties to the kolabformat object - * - * @param array Event data as hash array - */ - public function set(&$object) - { - $this->init(); - - $is_new = !$this->obj->uid(); - $old_sequence = $this->obj->sequence(); - $reschedule = $is_new; - - // set common object properties - parent::set($object); - - // set sequence value - if (!isset($object['sequence'])) { - if ($is_new) { - $object['sequence'] = 0; - } - else { - $object['sequence'] = $old_sequence; - - // increment sequence when updating properties relevant for scheduling. - // RFC 5545: "It is incremented [...] each time the Organizer makes a significant revision to the calendar component." - if ($this->check_rescheduling($object)) { - $object['sequence']++; - } - } - } - $this->obj->setSequence(intval($object['sequence'])); - - if ($object['sequence'] > $old_sequence) { - $reschedule = true; - } - - $this->obj->setSummary($object['title']); - $this->obj->setLocation($object['location']); - $this->obj->setDescription($object['description']); - $this->obj->setPriority($object['priority']); - $this->obj->setClassification($this->sensitivity_map[$object['sensitivity']]); - $this->obj->setCategories(self::array2vector($object['categories'])); - $this->obj->setUrl(strval($object['url'])); - - if (method_exists($this->obj, 'setComment')) { - $this->obj->setComment($object['comment']); - } - - // process event attendees - $attendees = new vectorattendee; - foreach ((array)$object['attendees'] as $i => $attendee) { - if ($attendee['role'] == 'ORGANIZER') { - $object['organizer'] = $attendee; - } - else if ($attendee['email'] != $object['organizer']['email']) { - $cr = new ContactReference(ContactReference::EmailReference, $attendee['email']); - $cr->setName($attendee['name']); - - // set attendee RSVP if missing - if (!isset($attendee['rsvp'])) { - $object['attendees'][$i]['rsvp'] = $attendee['rsvp'] = $reschedule; - } - - $att = new Attendee; - $att->setContact($cr); - $att->setPartStat($this->part_status_map[$attendee['status']]); - $att->setRole($this->role_map[$attendee['role']] ? $this->role_map[$attendee['role']] : kolabformat::Required); - $att->setCutype($this->cutype_map[$attendee['cutype']] ? $this->cutype_map[$attendee['cutype']] : kolabformat::CutypeIndividual); - $att->setRSVP((bool)$attendee['rsvp']); - - if (!empty($attendee['delegated-from'])) { - $vdelegators = new vectorcontactref; - foreach ((array)$attendee['delegated-from'] as $delegator) { - $vdelegators->push(new ContactReference(ContactReference::EmailReference, $delegator)); - } - $att->setDelegatedFrom($vdelegators); - } - if (!empty($attendee['delegated-to'])) { - $vdelegatees = new vectorcontactref; - foreach ((array)$attendee['delegated-to'] as $delegatee) { - $vdelegatees->push(new ContactReference(ContactReference::EmailReference, $delegatee)); - } - $att->setDelegatedTo($vdelegatees); - } - - if ($att->isValid()) { - $attendees->push($att); - } - else { - rcube::raise_error(array( - 'code' => 600, 'type' => 'php', - 'file' => __FILE__, 'line' => __LINE__, - 'message' => "Invalid event attendee: " . json_encode($attendee), - ), true); - } - } - } - $this->obj->setAttendees($attendees); - - if ($object['organizer']) { - $organizer = new ContactReference(ContactReference::EmailReference, $object['organizer']['email']); - $organizer->setName($object['organizer']['name']); - $this->obj->setOrganizer($organizer); - } - - // save recurrence rule - $rr = new RecurrenceRule; - $rr->setFrequency(RecurrenceRule::FreqNone); - - if ($object['recurrence'] && !empty($object['recurrence']['FREQ'])) { - $rr->setFrequency($this->rrule_type_map[$object['recurrence']['FREQ']]); - - if ($object['recurrence']['INTERVAL']) - $rr->setInterval(intval($object['recurrence']['INTERVAL'])); - - if ($object['recurrence']['BYDAY']) { - $byday = new vectordaypos; - foreach (explode(',', $object['recurrence']['BYDAY']) as $day) { - $occurrence = 0; - if (preg_match('/^([\d-]+)([A-Z]+)$/', $day, $m)) { - $occurrence = intval($m[1]); - $day = $m[2]; - } - if (isset($this->weekday_map[$day])) - $byday->push(new DayPos($occurrence, $this->weekday_map[$day])); - } - $rr->setByday($byday); - } - - if ($object['recurrence']['BYMONTHDAY']) { - $bymday = new vectori; - foreach (explode(',', $object['recurrence']['BYMONTHDAY']) as $day) - $bymday->push(intval($day)); - $rr->setBymonthday($bymday); - } - - if ($object['recurrence']['BYMONTH']) { - $bymonth = new vectori; - foreach (explode(',', $object['recurrence']['BYMONTH']) as $month) - $bymonth->push(intval($month)); - $rr->setBymonth($bymonth); - } - - if ($object['recurrence']['COUNT']) - $rr->setCount(intval($object['recurrence']['COUNT'])); - else if ($object['recurrence']['UNTIL']) - $rr->setEnd(self::get_datetime($object['recurrence']['UNTIL'], null, true)); - - if ($rr->isValid()) { - // add exception dates (only if recurrence rule is valid) - $exdates = new vectordatetime; - foreach ((array)$object['recurrence']['EXDATE'] as $exdate) - $exdates->push(self::get_datetime($exdate, null, true)); - $this->obj->setExceptionDates($exdates); - } - else { - rcube::raise_error(array( - 'code' => 600, 'type' => 'php', - 'file' => __FILE__, 'line' => __LINE__, - 'message' => "Invalid event recurrence rule: " . json_encode($object['recurrence']), - ), true); - } - } - - $this->obj->setRecurrenceRule($rr); - - // save recurrence dates (aka RDATE) - if (!empty($object['recurrence']['RDATE'])) { - $rdates = new vectordatetime; - foreach ((array)$object['recurrence']['RDATE'] as $rdate) - $rdates->push(self::get_datetime($rdate, null, true)); - $this->obj->setRecurrenceDates($rdates); - } - - // save alarm - $valarms = new vectoralarm; - if ($object['valarms']) { - foreach ($object['valarms'] as $valarm) { - if (!array_key_exists($valarm['action'], $this->alarm_type_map)) { - continue; // skip unknown alarm types - } - - if ($valarm['action'] == 'EMAIL') { - $recipients = new vectorcontactref; - foreach (($valarm['attendees'] ?: array($object['_owner'])) as $email) { - $recipients->push(new ContactReference(ContactReference::EmailReference, $email)); - } - $alarm = new Alarm( - strval($valarm['summary'] ?: $object['title']), - strval($valarm['description'] ?: $object['description']), - $recipients - ); - } - else if ($valarm['action'] == 'AUDIO') { - $attach = new Attachment; - $attach->setUri($valarm['uri'] ?: 'null', 'unknown'); - $alarm = new Alarm($attach); - } - else { - // action == DISPLAY - $alarm = new Alarm(strval($valarm['summary'] ?: $object['title'])); - } - - if (is_object($valarm['trigger']) && $valarm['trigger'] instanceof DateTime) { - $alarm->setStart(self::get_datetime($valarm['trigger'], new DateTimeZone('UTC'))); - } - else { - try { - $period = new DateInterval(preg_replace('/[^0-9PTWDHMS]/', '', $valarm['trigger'])); - $duration = new Duration($period->d, $period->h, $period->i, $period->s, $valarm['trigger'][0] == '-'); - } - catch (Exception $e) { - // skip alarm with invalid trigger values - rcube::raise_error($e, true); - continue; - } - - $related = strtoupper($valarm['related']) == 'END' ? kolabformat::End : kolabformat::Start; - $alarm->setRelativeStart($duration, $related); - } - - if ($valarm['duration']) { - try { - $d = new DateInterval($valarm['duration']); - $duration = new Duration($d->d, $d->h, $d->i, $d->s); - $alarm->setDuration($duration, intval($valarm['repeat'])); - } - catch (Exception $e) { - // ignore - } - } - - $valarms->push($alarm); - } - } - // legacy support - else if ($object['alarms']) { - list($offset, $type) = explode(":", $object['alarms']); - - if ($type == 'EMAIL' && !empty($object['_owner'])) { // email alarms implicitly go to event owner - $recipients = new vectorcontactref; - $recipients->push(new ContactReference(ContactReference::EmailReference, $object['_owner'])); - $alarm = new Alarm($object['title'], strval($object['description']), $recipients); - } - else { // default: display alarm - $alarm = new Alarm($object['title']); - } - - if (preg_match('/^@(\d+)/', $offset, $d)) { - $alarm->setStart(self::get_datetime($d[1], new DateTimeZone('UTC'))); - } - else if (preg_match('/^([-+]?)P?T?(\d+)([SMHDW])/', $offset, $d)) { - $days = $hours = $minutes = $seconds = 0; - switch ($d[3]) { - case 'W': $days = 7*intval($d[2]); break; - case 'D': $days = intval($d[2]); break; - case 'H': $hours = intval($d[2]); break; - case 'M': $minutes = intval($d[2]); break; - case 'S': $seconds = intval($d[2]); break; - } - $alarm->setRelativeStart(new Duration($days, $hours, $minutes, $seconds, $d[1] == '-'), $d[1] == '-' ? kolabformat::Start : kolabformat::End); - } - - $valarms->push($alarm); - } - $this->obj->setAlarms($valarms); - - $this->set_attachments($object); - } - - /** - * Return the reference date for recurrence and alarms - * - * @return mixed DateTime instance of null if no refdate is available - */ - public function get_reference_date() - { - if ($this->data['start'] && $this->data['start'] instanceof DateTime) { - return $this->data['start']; - } - - return self::php_datetime($this->obj->start()); - } - - /** - * Callback for kolab_storage_cache to get words to index for fulltext search - * - * @return array List of words to save in cache - */ - public function get_words($obj = null) - { - $data = ''; - $object = $obj ?: $this->data; - - foreach (self::$fulltext_cols as $colname) { - list($col, $field) = explode(':', $colname); - - if ($field) { - $a = array(); - foreach ((array)$object[$col] as $attr) - $a[] = $attr[$field]; - $val = join(' ', $a); - } - else { - $val = is_array($object[$col]) ? join(' ', $object[$col]) : $object[$col]; - } - - if (strlen($val)) - $data .= $val . ' '; - } - - $words = rcube_utils::normalize_string($data, true); - - // collect words from recurrence exceptions - if (is_array($object['exceptions'])) { - foreach ($object['exceptions'] as $exception) { - $words = array_merge($words, $this->get_words($exception)); - } - } - - return array_unique($words); - } - - /** - * Callback for kolab_storage_cache to get object specific tags to cache - * - * @return array List of tags to save in cache - */ - public function get_tags($obj = null) - { - $tags = array(); - $object = $obj ?: $this->data; - - if (!empty($object['valarms'])) { - $tags[] = 'x-has-alarms'; - } - - // create tags reflecting participant status - if (is_array($object['attendees'])) { - foreach ($object['attendees'] as $attendee) { - if (!empty($attendee['email']) && !empty($attendee['status'])) - $tags[] = 'x-partstat:' . $attendee['email'] . ':' . strtolower($attendee['status']); - } - } - - // collect tags from recurrence exceptions - if (is_array($object['exceptions'])) { - foreach ($object['exceptions'] as $exception) { - $tags = array_merge($tags, $this->get_tags($exception)); - } - } - - if (!empty($object['status'])) { - $tags[] = 'x-status:' . strtolower($object['status']); - } - - return array_unique($tags); - } - - /** - * Identify changes considered relevant for scheduling - * - * @param array Hash array with NEW object properties - * @param array Hash array with OLD object properties - * - * @return boolean True if changes affect scheduling, False otherwise - */ - public function check_rescheduling($object, $old = null) - { - $reschedule = false; - - if (!is_array($old)) { - $old = $this->data['uid'] ? $this->data : $this->to_array(); - } - - foreach ($this->_scheduling_properties ?: self::$scheduling_properties as $prop) { - $a = $old[$prop]; - $b = $object[$prop]; - if ($object['allday'] && ($prop == 'start' || $prop == 'end') && $a instanceof DateTime && $b instanceof DateTime) { - $a = $a->format('Y-m-d'); - $b = $b->format('Y-m-d'); - } - if ($prop == 'recurrence' && is_array($a) && is_array($b)) { - unset($a['EXCEPTIONS'], $b['EXCEPTIONS']); - $a = array_filter($a); - $b = array_filter($b); - - // advanced rrule comparison: no rescheduling if series was shortened - if ($a['COUNT'] && $b['COUNT'] && $b['COUNT'] < $a['COUNT']) { - unset($a['COUNT'], $b['COUNT']); - } - else if ($a['UNTIL'] && $b['UNTIL'] && $b['UNTIL'] < $a['UNTIL']) { - unset($a['UNTIL'], $b['UNTIL']); - } - } - if ($a != $b) { - $reschedule = true; - break; - } - } - - return $reschedule; - } -} \ No newline at end of file diff --git a/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage.php deleted file mode 100644 index 9bafbe9..0000000 --- a/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage.php +++ /dev/null @@ -1,1603 +0,0 @@ - - * @author Aleksander Machniak - * - * 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 Affero 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -class kolab_storage -{ - const CTYPE_KEY = '/shared/vendor/kolab/folder-type'; - const CTYPE_KEY_PRIVATE = '/private/vendor/kolab/folder-type'; - const COLOR_KEY_SHARED = '/shared/vendor/kolab/color'; - const COLOR_KEY_PRIVATE = '/private/vendor/kolab/color'; - const NAME_KEY_SHARED = '/shared/vendor/kolab/displayname'; - const NAME_KEY_PRIVATE = '/private/vendor/kolab/displayname'; - const UID_KEY_SHARED = '/shared/vendor/kolab/uniqueid'; - const UID_KEY_CYRUS = '/shared/vendor/cmu/cyrus-imapd/uniqueid'; - - const ERROR_IMAP_CONN = 1; - const ERROR_CACHE_DB = 2; - const ERROR_NO_PERMISSION = 3; - const ERROR_INVALID_FOLDER = 4; - - public static $version = '3.0'; - public static $last_error; - public static $encode_ids = false; - - private static $ready = false; - private static $with_tempsubs = true; - private static $subscriptions; - private static $typedata = array(); - private static $states; - private static $config; - private static $imap; - private static $ldap; - - // Default folder names - private static $default_folders = array( - 'event' => 'Calendar', - 'contact' => 'Contacts', - 'task' => 'Tasks', - 'note' => 'Notes', - 'file' => 'Files', - 'configuration' => 'Configuration', - 'journal' => 'Journal', - 'mail.inbox' => 'INBOX', - 'mail.drafts' => 'Drafts', - 'mail.sentitems' => 'Sent', - 'mail.wastebasket' => 'Trash', - 'mail.outbox' => 'Outbox', - 'mail.junkemail' => 'Junk', - ); - - - /** - * Setup the environment needed by the libs - */ - public static function setup() - { - if (self::$ready) - return true; - - $rcmail = rcube::get_instance(); - self::$config = $rcmail->config; - self::$version = strval($rcmail->config->get('kolab_format_version', self::$version)); - self::$imap = $rcmail->get_storage(); - self::$ready = class_exists('kolabformat') && - (self::$imap->get_capability('METADATA') || self::$imap->get_capability('ANNOTATEMORE') || self::$imap->get_capability('ANNOTATEMORE2')); - - if (self::$ready) { - // set imap options - self::$imap->set_options(array( - 'skip_deleted' => true, - 'threading' => false, - )); - } - else if (!class_exists('kolabformat')) { - rcube::raise_error(array( - 'code' => 900, 'type' => 'php', - 'message' => "required kolabformat module not found" - ), true); - } - else { - rcube::raise_error(array( - 'code' => 900, 'type' => 'php', - 'message' => "IMAP server doesn't support METADATA or ANNOTATEMORE" - ), true); - } - - // adjust some configurable settings - if ($event_scheduling_prop = $rcmail->config->get('kolab_event_scheduling_properties', null)) { - kolab_format_event::$scheduling_properties = (array)$event_scheduling_prop; - } - // adjust some configurable settings - if ($task_scheduling_prop = $rcmail->config->get('kolab_task_scheduling_properties', null)) { - kolab_format_task::$scheduling_properties = (array)$task_scheduling_prop; - } - - return self::$ready; - } - - /** - * Initializes LDAP object to resolve Kolab users - */ - public static function ldap() - { - if (self::$ldap) { - return self::$ldap; - } - - self::setup(); - - $config = self::$config->get('kolab_users_directory', self::$config->get('kolab_auth_addressbook')); - - if (!is_array($config)) { - $ldap_config = (array)self::$config->get('ldap_public'); - $config = $ldap_config[$config]; - } - - if (empty($config)) { - return null; - } - - // overwrite filter option - if ($filter = self::$config->get('kolab_users_filter')) { - self::$config->set('kolab_auth_filter', $filter); - } - - // re-use the LDAP wrapper class from kolab_auth plugin - require_once rtrim(RCUBE_PLUGINS_DIR, '/') . '/kolab_auth/kolab_auth_ldap.php'; - - self::$ldap = new kolab_auth_ldap($config); - - return self::$ldap; - } - - /** - * Get a list of storage folders for the given data type - * - * @param string Data type to list folders for (contact,distribution-list,event,task,note) - * @param boolean Enable to return subscribed folders only (null to use configured subscription mode) - * - * @return array List of Kolab_Folder objects (folder names in UTF7-IMAP) - */ - public static function get_folders($type, $subscribed = null) - { - $folders = $folderdata = array(); - - if (self::setup()) { - foreach ((array)self::list_folders('', '*', $type, $subscribed, $folderdata) as $foldername) { - $folders[$foldername] = new kolab_storage_folder($foldername, $type, $folderdata[$foldername]); - } - } - - return $folders; - } - - /** - * Getter for the storage folder for the given type - * - * @param string Data type to list folders for (contact,distribution-list,event,task,note) - * @return object kolab_storage_folder The folder object - */ - public static function get_default_folder($type) - { - if (self::setup()) { - foreach ((array)self::list_folders('', '*', $type . '.default', false, $folderdata) as $foldername) { - return new kolab_storage_folder($foldername, $type, $folderdata[$foldername]); - } - } - - return null; - } - - /** - * Getter for a specific storage folder - * - * @param string IMAP folder to access (UTF7-IMAP) - * @param string Expected folder type - * - * @return object kolab_storage_folder The folder object - */ - public static function get_folder($folder, $type = null) - { - return self::setup() ? new kolab_storage_folder($folder, $type) : null; - } - - /** - * Getter for a single Kolab object, identified by its UID. - * This will search all folders storing objects of the given type. - * - * @param string Object UID - * @param string Object type (contact,event,task,journal,file,note,configuration) - * @return array The Kolab object represented as hash array or false if not found - */ - public static function get_object($uid, $type) - { - self::setup(); - $folder = null; - foreach ((array)self::list_folders('', '*', $type, null, $folderdata) as $foldername) { - if (!$folder) - $folder = new kolab_storage_folder($foldername, $type, $folderdata[$foldername]); - else - $folder->set_folder($foldername, $type, $folderdata[$foldername]); - - if ($object = $folder->get_object($uid, '*')) - return $object; - } - - return false; - } - - /** - * Execute cross-folder searches with the given query. - * - * @param array Pseudo-SQL query as list of filter parameter triplets - * @param string Object type (contact,event,task,journal,file,note,configuration) - * @return array List of Kolab data objects (each represented as hash array) - * @see kolab_storage_format::select() - */ - public static function select($query, $type) - { - self::setup(); - $folder = null; - $result = array(); - - foreach ((array)self::list_folders('', '*', $type, null, $folderdata) as $foldername) { - if (!$folder) - $folder = new kolab_storage_folder($foldername, $type, $folderdata[$foldername]); - else - $folder->set_folder($foldername, $type, $folderdata[$foldername]); - - foreach ($folder->select($query, '*') as $object) { - $result[] = $object; - } - } - - return $result; - } - - /** - * Returns Free-busy server URL - */ - public static function get_freebusy_server() - { - self::setup(); - - $url = 'https://' . $_SESSION['imap_host'] . '/freebusy'; - $url = self::$config->get('kolab_freebusy_server', $url); - $url = rcube_utils::resolve_url($url); - - return unslashify($url); - } - - /** - * Compose an URL to query the free/busy status for the given user - * - * @param string Email address of the user to get free/busy data for - * @param object DateTime Start of the query range (optional) - * @param object DateTime End of the query range (optional) - * - * @return string Fully qualified URL to query free/busy data - */ - public static function get_freebusy_url($email, $start = null, $end = null) - { - $query = ''; - $param = array(); - $utc = new \DateTimeZone('UTC'); - - if ($start instanceof \DateTime) { - $start->setTimezone($utc); - $param['dtstart'] = $start->format('Ymd\THis\Z'); - } - if ($end instanceof \DateTime) { - $end->setTimezone($utc); - $param['dtend'] = $end->format('Ymd\THis\Z'); - } - if (!empty($param)) { - $query = '?' . http_build_query($param); - } - - return self::get_freebusy_server() . '/' . $email . '.ifb' . $query; - } - - /** - * Creates folder ID from folder name - * - * @param string $folder Folder name (UTF7-IMAP) - * @param boolean $enc Use lossless encoding - * @return string Folder ID string - */ - public static function folder_id($folder, $enc = null) - { - return $enc == true || ($enc === null && self::$encode_ids) ? - self::id_encode($folder) : - asciiwords(strtr($folder, '/.-', '___')); - } - - /** - * Encode the given ID to a safe ascii representation - * - * @param string $id Arbitrary identifier string - * - * @return string Ascii representation - */ - public static function id_encode($id) - { - return rtrim(strtr(base64_encode($id), '+/', '-_'), '='); - } - - /** - * Convert the given identifier back to it's raw value - * - * @param string $id Ascii identifier - * @return string Raw identifier string - */ - public static function id_decode($id) - { - return base64_decode(str_pad(strtr($id, '-_', '+/'), strlen($id) % 4, '=', STR_PAD_RIGHT)); - } - - /** - * Return the (first) path of the requested IMAP namespace - * - * @param string Namespace name (personal, shared, other) - * @return string IMAP root path for that namespace - */ - public static function namespace_root($name) - { - foreach ((array)self::$imap->get_namespace($name) as $paths) { - if (strlen($paths[0]) > 1) { - return $paths[0]; - } - } - - return ''; - } - - - /** - * Deletes IMAP folder - * - * @param string $name Folder name (UTF7-IMAP) - * - * @return bool True on success, false on failure - */ - public static function folder_delete($name) - { - // clear cached entries first - if ($folder = self::get_folder($name)) - $folder->cache->purge(); - - $rcmail = rcube::get_instance(); - $plugin = $rcmail->plugins->exec_hook('folder_delete', array('name' => $name)); - - $success = self::$imap->delete_folder($name); - self::$last_error = self::$imap->get_error_str(); - - return $success; - } - - /** - * Creates IMAP folder - * - * @param string $name Folder name (UTF7-IMAP) - * @param string $type Folder type - * @param bool $subscribed Sets folder subscription - * @param bool $active Sets folder state (client-side subscription) - * - * @return bool True on success, false on failure - */ - public static function folder_create($name, $type = null, $subscribed = false, $active = false) - { - self::setup(); - - $rcmail = rcube::get_instance(); - $plugin = $rcmail->plugins->exec_hook('folder_create', array('record' => array( - 'name' => $name, - 'subscribe' => $subscribed, - ))); - - if ($saved = self::$imap->create_folder($name, $subscribed)) { - // set metadata for folder type - if ($type) { - $saved = self::set_folder_type($name, $type); - - // revert if metadata could not be set - if (!$saved) { - self::$imap->delete_folder($name); - } - // activate folder - else if ($active) { - self::set_state($name, true); - } - } - } - - if ($saved) { - return true; - } - - self::$last_error = self::$imap->get_error_str(); - return false; - } - - - /** - * Renames IMAP folder - * - * @param string $oldname Old folder name (UTF7-IMAP) - * @param string $newname New folder name (UTF7-IMAP) - * - * @return bool True on success, false on failure - */ - public static function folder_rename($oldname, $newname) - { - self::setup(); - - $rcmail = rcube::get_instance(); - $plugin = $rcmail->plugins->exec_hook('folder_rename', array( - 'oldname' => $oldname, 'newname' => $newname)); - - $oldfolder = self::get_folder($oldname); - $active = self::folder_is_active($oldname); - $success = self::$imap->rename_folder($oldname, $newname); - self::$last_error = self::$imap->get_error_str(); - - // pass active state to new folder name - if ($success && $active) { - self::set_state($oldname, false); - self::set_state($newname, true); - } - - // assign existing cache entries to new resource uri - if ($success && $oldfolder) { - $oldfolder->cache->rename($newname); - } - - return $success; - } - - - /** - * Rename or Create a new IMAP folder. - * - * Does additional checks for permissions and folder name restrictions - * - * @param array Hash array with folder properties and metadata - * - name: Folder name - * - oldname: Old folder name when changed - * - parent: Parent folder to create the new one in - * - type: Folder type to create - * - subscribed: Subscribed flag (IMAP subscription) - * - active: Activation flag (client-side subscription) - * @return mixed New folder name or False on failure - */ - public static function folder_update(&$prop) - { - self::setup(); - - $folder = rcube_charset::convert($prop['name'], RCUBE_CHARSET, 'UTF7-IMAP'); - $oldfolder = $prop['oldname']; // UTF7 - $parent = $prop['parent']; // UTF7 - $delimiter = self::$imap->get_hierarchy_delimiter(); - - if (strlen($oldfolder)) { - $options = self::$imap->folder_info($oldfolder); - } - - if (!empty($options) && ($options['norename'] || $options['protected'])) { - } - // sanity checks (from steps/settings/save_folder.inc) - else if (!strlen($folder)) { - self::$last_error = 'cannotbeempty'; - return false; - } - else if (strlen($folder) > 128) { - self::$last_error = 'nametoolong'; - return false; - } - else { - // these characters are problematic e.g. when used in LIST/LSUB - foreach (array($delimiter, '%', '*') as $char) { - if (strpos($folder, $char) !== false) { - self::$last_error = 'forbiddencharacter'; - return false; - } - } - } - - if (!empty($options) && ($options['protected'] || $options['norename'])) { - $folder = $oldfolder; - } - else if (strlen($parent)) { - $folder = $parent . $delimiter . $folder; - } - else { - // add namespace prefix (when needed) - $folder = self::$imap->mod_folder($folder, 'in'); - } - - // Check access rights to the parent folder - if (strlen($parent) && (!strlen($oldfolder) || $oldfolder != $folder)) { - $parent_opts = self::$imap->folder_info($parent); - if ($parent_opts['namespace'] != 'personal' - && (empty($parent_opts['rights']) || !preg_match('/[ck]/', implode($parent_opts['rights']))) - ) { - self::$last_error = 'No permission to create folder'; - return false; - } - } - - // update the folder name - if (strlen($oldfolder)) { - if ($oldfolder != $folder) { - $result = self::folder_rename($oldfolder, $folder); - } - else - $result = true; - } - // create new folder - else { - $result = self::folder_create($folder, $prop['type'], $prop['subscribed'], $prop['active']); - } - - if ($result) { - self::set_folder_props($folder, $prop); - } - - return $result ? $folder : false; - } - - - /** - * Getter for human-readable name of Kolab object (folder) - * See http://wiki.kolab.org/UI-Concepts/Folder-Listing for reference - * - * @param string $folder IMAP folder name (UTF7-IMAP) - * @param string $folder_ns Will be set to namespace name of the folder - * - * @return string Name of the folder-object - */ - public static function object_name($folder, &$folder_ns=null) - { - self::setup(); - - // find custom display name in folder METADATA - if ($name = self::custom_displayname($folder)) { - return $name; - } - - $found = false; - $namespace = self::$imap->get_namespace(); - - if (!empty($namespace['shared'])) { - foreach ($namespace['shared'] as $ns) { - if (strlen($ns[0]) && strpos($folder, $ns[0]) === 0) { - $prefix = ''; - $folder = substr($folder, strlen($ns[0])); - $delim = $ns[1]; - $found = true; - $folder_ns = 'shared'; - break; - } - } - } - if (!$found && !empty($namespace['other'])) { - foreach ($namespace['other'] as $ns) { - if (strlen($ns[0]) && strpos($folder, $ns[0]) === 0) { - // remove namespace prefix - $folder = substr($folder, strlen($ns[0])); - $delim = $ns[1]; - // get username - $pos = strpos($folder, $delim); - if ($pos) { - $prefix = '('.substr($folder, 0, $pos).')'; - $folder = substr($folder, $pos+1); - } - else { - $prefix = '('.$folder.')'; - $folder = ''; - } - - $found = true; - $folder_ns = 'other'; - break; - } - } - } - if (!$found && !empty($namespace['personal'])) { - foreach ($namespace['personal'] as $ns) { - if (strlen($ns[0]) && strpos($folder, $ns[0]) === 0) { - // remove namespace prefix - $folder = substr($folder, strlen($ns[0])); - $prefix = ''; - $delim = $ns[1]; - $found = true; - break; - } - } - } - - if (empty($delim)) - $delim = self::$imap->get_hierarchy_delimiter(); - - $folder = rcube_charset::convert($folder, 'UTF7-IMAP'); - $folder = html::quote($folder); - $folder = str_replace(html::quote($delim), ' » ', $folder); - - if ($prefix) - $folder = html::quote($prefix) . ($folder !== '' ? ' ' . $folder : ''); - - if (!$folder_ns) - $folder_ns = 'personal'; - - return $folder; - } - - /** - * Get custom display name (saved in metadata) for the given folder - */ - public static function custom_displayname($folder) - { - // find custom display name in folder METADATA - if (self::$config->get('kolab_custom_display_names', true)) { - $metadata = self::$imap->get_metadata($folder, array(self::NAME_KEY_PRIVATE, self::NAME_KEY_SHARED)); - if (($name = $metadata[$folder][self::NAME_KEY_PRIVATE]) || ($name = $metadata[$folder][self::NAME_KEY_SHARED])) { - return $name; - } - } - - return false; - } - - /** - * Helper method to generate a truncated folder name to display. - * Note: $origname is a string returned by self::object_name() - */ - public static function folder_displayname($origname, &$names) - { - $name = $origname; - - // find folder prefix to truncate - for ($i = count($names)-1; $i >= 0; $i--) { - if (strpos($name, $names[$i] . ' » ') === 0) { - $length = strlen($names[$i] . ' » '); - $prefix = substr($name, 0, $length); - $count = count(explode(' » ', $prefix)); - $diff = 1; - - // check if prefix folder is in other users namespace - for ($n = count($names)-1; $n >= 0; $n--) { - if (strpos($prefix, '(' . $names[$n] . ') ') === 0) { - $diff = 0; - break; - } - } - - $name = str_repeat('   ', $count - $diff) . '» ' . substr($name, $length); - break; - } - // other users namespace and parent folder exists - else if (strpos($name, '(' . $names[$i] . ') ') === 0) { - $length = strlen('(' . $names[$i] . ') '); - $prefix = substr($name, 0, $length); - $count = count(explode(' » ', $prefix)); - $name = str_repeat('   ', $count) . '» ' . substr($name, $length); - break; - } - } - - $names[] = $origname; - - return $name; - } - - - /** - * Creates a SELECT field with folders list - * - * @param string $type Folder type - * @param array $attrs SELECT field attributes (e.g. name) - * @param string $current The name of current folder (to skip it) - * - * @return html_select SELECT object - */ - public static function folder_selector($type, $attrs, $current = '') - { - // get all folders of specified type (sorted) - $folders = self::get_folders($type, true); - - $delim = self::$imap->get_hierarchy_delimiter(); - $names = array(); - $len = strlen($current); - - if ($len && ($rpos = strrpos($current, $delim))) { - $parent = substr($current, 0, $rpos); - $p_len = strlen($parent); - } - - // Filter folders list - foreach ($folders as $c_folder) { - $name = $c_folder->name; - - // skip current folder and it's subfolders - if ($len) { - if ($name == $current) { - // Make sure parent folder is listed (might be skipped e.g. if it's namespace root) - if ($p_len && !isset($names[$parent])) { - $names[$parent] = self::object_name($parent); - } - continue; - } - if (strpos($name, $current.$delim) === 0) { - continue; - } - } - - // always show the parent of current folder - if ($p_len && $name == $parent) { - } - // skip folders where user have no rights to create subfolders - else if ($c_folder->get_owner() != $_SESSION['username']) { - $rights = $c_folder->get_myrights(); - if (!preg_match('/[ck]/', $rights)) { - continue; - } - } - - $names[$name] = self::object_name($name); - } - - // Build SELECT field of parent folder - $attrs['is_escaped'] = true; - $select = new html_select($attrs); - $select->add('---', ''); - - $listnames = array(); - foreach (array_keys($names) as $imap_name) { - $name = $origname = $names[$imap_name]; - - // find folder prefix to truncate - for ($i = count($listnames)-1; $i >= 0; $i--) { - if (strpos($name, $listnames[$i].' » ') === 0) { - $length = strlen($listnames[$i].' » '); - $prefix = substr($name, 0, $length); - $count = count(explode(' » ', $prefix)); - $name = str_repeat('  ', $count-1) . '» ' . substr($name, $length); - break; - } - } - - $listnames[] = $origname; - $select->add($name, $imap_name); - } - - return $select; - } - - - /** - * Returns a list of folder names - * - * @param string Optional root folder - * @param string Optional name pattern - * @param string Data type to list folders for (contact,event,task,journal,file,note,mail,configuration) - * @param boolean Enable to return subscribed folders only (null to use configured subscription mode) - * @param array Will be filled with folder-types data - * - * @return array List of folders - */ - public static function list_folders($root = '', $mbox = '*', $filter = null, $subscribed = null, &$folderdata = array()) - { - if (!self::setup()) { - return null; - } - - // use IMAP subscriptions - if ($subscribed === null && self::$config->get('kolab_use_subscriptions')) { - $subscribed = true; - } - - if (!$filter) { - // Get ALL folders list, standard way - if ($subscribed) { - $folders = self::$imap->list_folders_subscribed($root, $mbox); - // add temporarily subscribed folders - if (self::$with_tempsubs && is_array($_SESSION['kolab_subscribed_folders'])) { - $folders = array_unique(array_merge($folders, $_SESSION['kolab_subscribed_folders'])); - } - } - else { - $folders = self::_imap_list_folders($root, $mbox); - } - - return $folders; - } - $prefix = $root . $mbox; - $regexp = '/^' . preg_quote($filter, '/') . '(\..+)?$/'; - - // get folders types for all folders - if (!$subscribed || $prefix == '*' || !self::$config->get('kolab_skip_namespace')) { - $folderdata = self::folders_typedata($prefix); - } - else { - // fetch folder types for the effective list of (subscribed) folders when post-filtering - $folderdata = array(); - } - - if (!is_array($folderdata)) { - return array(); - } - - // In some conditions we can skip LIST command (?) - if (!$subscribed && $filter != 'mail' && $prefix == '*') { - foreach ($folderdata as $folder => $type) { - if (!preg_match($regexp, $type)) { - unset($folderdata[$folder]); - } - } - - return self::$imap->sort_folder_list(array_keys($folderdata), true); - } - - // Get folders list - if ($subscribed) { - $folders = self::$imap->list_folders_subscribed($root, $mbox); - - // add temporarily subscribed folders - if (self::$with_tempsubs && is_array($_SESSION['kolab_subscribed_folders'])) { - $folders = array_unique(array_merge($folders, $_SESSION['kolab_subscribed_folders'])); - } - } - else { - $folders = self::_imap_list_folders($root, $mbox); - } - - // In case of an error, return empty list (?) - if (!is_array($folders)) { - return array(); - } - - // Filter folders list - foreach ($folders as $idx => $folder) { - // lookup folder type - if (!array_key_exists($folder, $folderdata)) { - $folderdata[$folder] = self::folder_type($folder); - } - - $type = $folderdata[$folder]; - - if ($filter == 'mail' && empty($type)) { - continue; - } - if (empty($type) || !preg_match($regexp, $type)) { - unset($folders[$idx]); - } - } - - return $folders; - } - - /** - * Wrapper for rcube_imap::list_folders() with optional post-filtering - */ - protected static function _imap_list_folders($root, $mbox) - { - $postfilter = null; - - // compose a post-filter expression for the excluded namespaces - if ($root . $mbox == '*' && ($skip_ns = self::$config->get('kolab_skip_namespace'))) { - $excludes = array(); - foreach ((array)$skip_ns as $ns) { - if ($ns_root = self::namespace_root($ns)) { - $excludes[] = $ns_root; - } - } - - if (count($excludes)) { - $postfilter = '!^(' . join(')|(', array_map('preg_quote', $excludes)) . ')!'; - } - } - - // use normal LIST command to return all folders, it's fast enough - $folders = self::$imap->list_folders($root, $mbox, null, null, !empty($postfilter)); - - if (!empty($postfilter)) { - $folders = array_filter($folders, function($folder) use ($postfilter) { return !preg_match($postfilter, $folder); }); - $folders = self::$imap->sort_folder_list($folders); - } - - return $folders; - } - - - /** - * Search for shared or otherwise not listed groupware folders the user has access - * - * @param string Folder type of folders to search for - * @param string Search string - * @param array Namespace(s) to exclude results from - * - * @return array List of matching kolab_storage_folder objects - */ - public static function search_folders($type, $query, $exclude_ns = array()) - { - if (!self::setup()) { - return array(); - } - - $folders = array(); - $query = str_replace('*', '', $query); - - // find unsubscribed IMAP folders of the given type - foreach ((array)self::list_folders('', '*', $type, false, $folderdata) as $foldername) { - // FIXME: only consider the last part of the folder path for searching? - $realname = strtolower(rcube_charset::convert($foldername, 'UTF7-IMAP')); - if (($query == '' || strpos($realname, $query) !== false) && - !self::folder_is_subscribed($foldername, true) && - !in_array(self::$imap->folder_namespace($foldername), (array)$exclude_ns) - ) { - $folders[] = new kolab_storage_folder($foldername, $type, $folderdata[$foldername]); - } - } - - return $folders; - } - - - /** - * Sort the given list of kolab folders by namespace/name - * - * @param array List of kolab_storage_folder objects - * @return array Sorted list of folders - */ - public static function sort_folders($folders) - { - $pad = ' '; - $out = array(); - $nsnames = array('personal' => array(), 'shared' => array(), 'other' => array()); - - foreach ($folders as $folder) { - $folders[$folder->name] = $folder; - $ns = $folder->get_namespace(); - $nsnames[$ns][$folder->name] = strtolower(html_entity_decode(self::object_name($folder->name, $ns), ENT_COMPAT, RCUBE_CHARSET)) . $pad; // decode » - } - - // $folders is a result of get_folders() we can assume folders were already sorted - foreach (array_keys($nsnames) as $ns) { - asort($nsnames[$ns], SORT_LOCALE_STRING); - foreach (array_keys($nsnames[$ns]) as $utf7name) { - $out[] = $folders[$utf7name]; - } - } - - return $out; - } - - - /** - * Check the folder tree and add the missing parents as virtual folders - * - * @param array $folders Folders list - * @param object $tree Reference to the root node of the folder tree - * - * @return array Flat folders list - */ - public static function folder_hierarchy($folders, &$tree = null) - { - $_folders = array(); - $delim = self::$imap->get_hierarchy_delimiter(); - $other_ns = rtrim(self::namespace_root('other'), $delim); - $tree = new kolab_storage_folder_virtual('', '', ''); // create tree root - $refs = array('' => $tree); - - foreach ($folders as $idx => $folder) { - $path = explode($delim, $folder->name); - array_pop($path); - $folder->parent = join($delim, $path); - $folder->children = array(); // reset list - - // skip top folders or ones with a custom displayname - if (count($path) < 1 || kolab_storage::custom_displayname($folder->name)) { - $tree->children[] = $folder; - } - else { - $parents = array(); - $depth = $folder->get_namespace() == 'personal' ? 1 : 2; - - while (count($path) >= $depth && ($parent = join($delim, $path))) { - array_pop($path); - $parent_parent = join($delim, $path); - if (!$refs[$parent]) { - if ($folder->type && self::folder_type($parent) == $folder->type) { - $refs[$parent] = new kolab_storage_folder($parent, $folder->type, $folder->type); - $refs[$parent]->parent = $parent_parent; - } - else if ($parent_parent == $other_ns) { - $refs[$parent] = new kolab_storage_folder_user($parent, $parent_parent); - } - else { - $name = kolab_storage::object_name($parent, $folder->get_namespace()); - $refs[$parent] = new kolab_storage_folder_virtual($parent, $name, $folder->get_namespace(), $parent_parent); - } - $parents[] = $refs[$parent]; - } - } - - if (!empty($parents)) { - $parents = array_reverse($parents); - foreach ($parents as $parent) { - $parent_node = $refs[$parent->parent] ?: $tree; - $parent_node->children[] = $parent; - $_folders[] = $parent; - } - } - - $parent_node = $refs[$folder->parent] ?: $tree; - $parent_node->children[] = $folder; - } - - $refs[$folder->name] = $folder; - $_folders[] = $folder; - unset($folders[$idx]); - } - - return $_folders; - } - - - /** - * Returns folder types indexed by folder name - * - * @param string $prefix Folder prefix (Default '*' for all folders) - * - * @return array|bool List of folders, False on failure - */ - public static function folders_typedata($prefix = '*') - { - if (!self::setup()) { - return false; - } - - // return cached result - if (is_array(self::$typedata[$prefix])) { - return self::$typedata[$prefix]; - } - - $type_keys = array(self::CTYPE_KEY, self::CTYPE_KEY_PRIVATE); - - // fetch metadata from *some* folders only - if (($prefix == '*' || $prefix == '') && ($skip_ns = self::$config->get('kolab_skip_namespace'))) { - $delimiter = self::$imap->get_hierarchy_delimiter(); - $folderdata = $blacklist = array(); - foreach ((array)$skip_ns as $ns) { - if ($ns_root = rtrim(self::namespace_root($ns), $delimiter)) { - $blacklist[] = $ns_root; - } - } - foreach (array('personal','other','shared') as $ns) { - if (!in_array($ns, (array)$skip_ns)) { - $ns_root = rtrim(self::namespace_root($ns), $delimiter); - - // list top-level folders and their childs one by one - // GETMETADATA "%" doesn't list shared or other namespace folders but "*" would - if ($ns_root == '') { - foreach ((array)self::$imap->get_metadata('%', $type_keys) as $folder => $metadata) { - if (!in_array($folder, $blacklist)) { - $folderdata[$folder] = $metadata; - $opts = self::$imap->folder_attributes($folder); - if (!in_array('\\HasNoChildren', $opts) && ($data = self::$imap->get_metadata($folder.$delimiter.'*', $type_keys))) { - $folderdata += $data; - } - } - } - } - else if ($data = self::$imap->get_metadata($ns_root.$delimiter.'*', $type_keys)) { - $folderdata += $data; - } - } - } - } - else { - $folderdata = self::$imap->get_metadata($prefix, $type_keys); - } - - if (!is_array($folderdata)) { - return false; - } - - // keep list in memory - self::$typedata[$prefix] = array_map(array('kolab_storage', 'folder_select_metadata'), $folderdata); - - return self::$typedata[$prefix]; - } - - - /** - * Callback for array_map to select the correct annotation value - */ - public static function folder_select_metadata($types) - { - if (!empty($types[self::CTYPE_KEY_PRIVATE])) { - return $types[self::CTYPE_KEY_PRIVATE]; - } - else if (!empty($types[self::CTYPE_KEY])) { - list($ctype, ) = explode('.', $types[self::CTYPE_KEY]); - return $ctype; - } - return null; - } - - - /** - * Returns type of IMAP folder - * - * @param string $folder Folder name (UTF7-IMAP) - * - * @return string Folder type - */ - public static function folder_type($folder) - { - self::setup(); - - // return in-memory cached result - foreach (self::$typedata as $typedata) { - if (array_key_exists($folder, $typedata)) { - return $typedata[$folder]; - } - } - - $metadata = self::$imap->get_metadata($folder, array(self::CTYPE_KEY, self::CTYPE_KEY_PRIVATE)); - - if (!is_array($metadata)) { - return null; - } - - if (!empty($metadata[$folder])) { - return self::folder_select_metadata($metadata[$folder]); - } - - return 'mail'; - } - - - /** - * Sets folder content-type. - * - * @param string $folder Folder name - * @param string $type Content type - * - * @return boolean True on success - */ - public static function set_folder_type($folder, $type='mail') - { - self::setup(); - - list($ctype, $subtype) = explode('.', $type); - - $success = self::$imap->set_metadata($folder, array(self::CTYPE_KEY => $ctype, self::CTYPE_KEY_PRIVATE => $subtype ? $type : null)); - - if (!$success) // fallback: only set private annotation - $success |= self::$imap->set_metadata($folder, array(self::CTYPE_KEY_PRIVATE => $type)); - - return $success; - } - - - /** - * Check subscription status of this folder - * - * @param string $folder Folder name - * @param boolean $temp Include temporary/session subscriptions - * - * @return boolean True if subscribed, false if not - */ - public static function folder_is_subscribed($folder, $temp = false) - { - if (self::$subscriptions === null) { - self::setup(); - self::$with_tempsubs = false; - self::$subscriptions = self::$imap->list_folders_subscribed(); - self::$with_tempsubs = true; - } - - return in_array($folder, self::$subscriptions) || - ($temp && in_array($folder, (array)$_SESSION['kolab_subscribed_folders'])); - } - - - /** - * Change subscription status of this folder - * - * @param string $folder Folder name - * @param boolean $temp Only subscribe temporarily for the current session - * - * @return True on success, false on error - */ - public static function folder_subscribe($folder, $temp = false) - { - self::setup(); - - // temporary/session subscription - if ($temp) { - if (self::folder_is_subscribed($folder)) { - return true; - } - else if (!is_array($_SESSION['kolab_subscribed_folders']) || !in_array($folder, $_SESSION['kolab_subscribed_folders'])) { - $_SESSION['kolab_subscribed_folders'][] = $folder; - return true; - } - } - else if (self::$imap->subscribe($folder)) { - self::$subscriptions = null; - return true; - } - - return false; - } - - - /** - * Change subscription status of this folder - * - * @param string $folder Folder name - * @param boolean $temp Only remove temporary subscription - * - * @return True on success, false on error - */ - public static function folder_unsubscribe($folder, $temp = false) - { - self::setup(); - - // temporary/session subscription - if ($temp) { - if (is_array($_SESSION['kolab_subscribed_folders']) && ($i = array_search($folder, $_SESSION['kolab_subscribed_folders'])) !== false) { - unset($_SESSION['kolab_subscribed_folders'][$i]); - } - return true; - } - else if (self::$imap->unsubscribe($folder)) { - self::$subscriptions = null; - return true; - } - - return false; - } - - - /** - * Check activation status of this folder - * - * @param string $folder Folder name - * - * @return boolean True if active, false if not - */ - public static function folder_is_active($folder) - { - $active_folders = self::get_states(); - - return in_array($folder, $active_folders); - } - - - /** - * Change activation status of this folder - * - * @param string $folder Folder name - * - * @return True on success, false on error - */ - public static function folder_activate($folder) - { - // activation implies temporary subscription - self::folder_subscribe($folder, true); - return self::set_state($folder, true); - } - - - /** - * Change activation status of this folder - * - * @param string $folder Folder name - * - * @return True on success, false on error - */ - public static function folder_deactivate($folder) - { - // remove from temp subscriptions, really? - self::folder_unsubscribe($folder, true); - - return self::set_state($folder, false); - } - - - /** - * Return list of active folders - */ - private static function get_states() - { - if (self::$states !== null) { - return self::$states; - } - - $rcube = rcube::get_instance(); - $folders = $rcube->config->get('kolab_active_folders'); - - if ($folders !== null) { - self::$states = !empty($folders) ? explode('**', $folders) : array(); - } - // for backward-compatibility copy server-side subscriptions to activation states - else { - self::setup(); - if (self::$subscriptions === null) { - self::$with_tempsubs = false; - self::$subscriptions = self::$imap->list_folders_subscribed(); - self::$with_tempsubs = true; - } - self::$states = self::$subscriptions; - $folders = implode(self::$states, '**'); - $rcube->user->save_prefs(array('kolab_active_folders' => $folders)); - } - - return self::$states; - } - - - /** - * Update list of active folders - */ - private static function set_state($folder, $state) - { - self::get_states(); - - // update in-memory list - $idx = array_search($folder, self::$states); - if ($state && $idx === false) { - self::$states[] = $folder; - } - else if (!$state && $idx !== false) { - unset(self::$states[$idx]); - } - - // update user preferences - $folders = implode(self::$states, '**'); - $rcube = rcube::get_instance(); - return $rcube->user->save_prefs(array('kolab_active_folders' => $folders)); - } - - /** - * Creates default folder of specified type - * To be run when none of subscribed folders (of specified type) is found - * - * @param string $type Folder type - * @param string $props Folder properties (color, etc) - * - * @return string Folder name - */ - public static function create_default_folder($type, $props = array()) - { - if (!self::setup()) { - return; - } - - $folders = self::$imap->get_metadata('*', array(kolab_storage::CTYPE_KEY_PRIVATE)); - - // from kolab_folders config - $folder_type = strpos($type, '.') ? str_replace('.', '_', $type) : $type . '_default'; - $default_name = self::$config->get('kolab_folders_' . $folder_type); - $folder_type = str_replace('_', '.', $folder_type); - - // check if we have any folder in personal namespace - // folder(s) may exist but not subscribed - foreach ((array)$folders as $f => $data) { - if (strpos($data[self::CTYPE_KEY_PRIVATE], $type) === 0) { - $folder = $f; - break; - } - } - - if (!$folder) { - if (!$default_name) { - $default_name = self::$default_folders[$type]; - } - - if (!$default_name) { - return; - } - - $folder = rcube_charset::convert($default_name, RCUBE_CHARSET, 'UTF7-IMAP'); - $prefix = self::$imap->get_namespace('prefix'); - - // add personal namespace prefix if needed - if ($prefix && strpos($folder, $prefix) !== 0 && $folder != 'INBOX') { - $folder = $prefix . $folder; - } - - if (!self::$imap->folder_exists($folder)) { - if (!self::$imap->create_folder($folder)) { - return; - } - } - - self::set_folder_type($folder, $folder_type); - } - - self::folder_subscribe($folder); - - if ($props['active']) { - self::set_state($folder, true); - } - - if (!empty($props)) { - self::set_folder_props($folder, $props); - } - - return $folder; - } - - /** - * Sets folder metadata properties - * - * @param string $folder Folder name - * @param array $prop Folder properties - */ - public static function set_folder_props($folder, &$prop) - { - if (!self::setup()) { - return; - } - - // TODO: also save 'showalarams' and other properties here - $ns = self::$imap->folder_namespace($folder); - $supported = array( - 'color' => array(self::COLOR_KEY_SHARED, self::COLOR_KEY_PRIVATE), - 'displayname' => array(self::NAME_KEY_SHARED, self::NAME_KEY_PRIVATE), - ); - - foreach ($supported as $key => $metakeys) { - if (array_key_exists($key, $prop)) { - $meta_saved = false; - if ($ns == 'personal') // save in shared namespace for personal folders - $meta_saved = self::$imap->set_metadata($folder, array($metakeys[0] => $prop[$key])); - if (!$meta_saved) // try in private namespace - $meta_saved = self::$imap->set_metadata($folder, array($metakeys[1] => $prop[$key])); - if ($meta_saved) - unset($prop[$key]); // unsetting will prevent fallback to local user prefs - } - } - } - - - /** - * - * @param mixed $query Search value (or array of field => value pairs) - * @param int $mode Matching mode: 0 - partial (*abc*), 1 - strict (=), 2 - prefix (abc*) - * @param array $required List of fields that shall ot be empty - * @param int $limit Maximum number of records - * @param int $count Returns the number of records found - * - * @return array List or false on error - */ - public static function search_users($query, $mode = 1, $required = array(), $limit = 0, &$count = 0) - { - $query = str_replace('*', '', $query); - - // requires a working LDAP setup - if (!self::ldap() || strlen($query) == 0) { - return array(); - } - - // search users using the configured attributes - $results = self::$ldap->dosearch(self::$config->get('kolab_users_search_attrib', array('cn','mail','alias')), $query, $mode, $required, $limit, $count); - - // exclude myself - if ($_SESSION['kolab_dn']) { - unset($results[$_SESSION['kolab_dn']]); - } - - // resolve to IMAP folder name - $root = self::namespace_root('other'); - $user_attrib = self::$config->get('kolab_users_id_attrib', self::$config->get('kolab_auth_login', 'mail')); - - array_walk($results, function(&$user, $dn) use ($root, $user_attrib) { - list($localpart, ) = explode('@', $user[$user_attrib]); - $user['kolabtargetfolder'] = $root . $localpart; - }); - - return $results; - } - - - /** - * Returns a list of IMAP folders shared by the given user - * - * @param array User entry from LDAP - * @param string Data type to list folders for (contact,event,task,journal,file,note,mail,configuration) - * @param boolean Return subscribed folders only (null to use configured subscription mode) - * @param array Will be filled with folder-types data - * - * @return array List of folders - */ - public static function list_user_folders($user, $type, $subscribed = null, &$folderdata = array()) - { - self::setup(); - - $folders = array(); - - // use localpart of user attribute as root for folder listing - $user_attrib = self::$config->get('kolab_users_id_attrib', self::$config->get('kolab_auth_login', 'mail')); - if (!empty($user[$user_attrib])) { - list($mbox) = explode('@', $user[$user_attrib]); - - $delimiter = self::$imap->get_hierarchy_delimiter(); - $other_ns = self::namespace_root('other'); - $folders = self::list_folders($other_ns . $mbox . $delimiter, '*', $type, $subscribed, $folderdata); - } - - return $folders; - } - - - /** - * Get a list of (virtual) top-level folders from the other users namespace - * - * @param string Data type to list folders for (contact,event,task,journal,file,note,mail,configuration) - * @param boolean Enable to return subscribed folders only (null to use configured subscription mode) - * - * @return array List of kolab_storage_folder_user objects - */ - public static function get_user_folders($type, $subscribed) - { - $folders = $folderdata = array(); - - if (self::setup()) { - $delimiter = self::$imap->get_hierarchy_delimiter(); - $other_ns = rtrim(self::namespace_root('other'), $delimiter); - $path_len = count(explode($delimiter, $other_ns)); - - foreach ((array)self::list_folders($other_ns . $delimiter, '*', '', $subscribed) as $foldername) { - if ($foldername == 'INBOX') // skip INBOX which is added by default - continue; - - $path = explode($delimiter, $foldername); - - // compare folder type if a subfolder is listed - if ($type && count($path) > $path_len + 1 && $type != self::folder_type($foldername)) { - continue; - } - - // truncate folder path to top-level folders of the 'other' namespace - $foldername = join($delimiter, array_slice($path, 0, $path_len + 1)); - - if (!$folders[$foldername]) { - $folders[$foldername] = new kolab_storage_folder_user($foldername, $other_ns); - } - } - - // for every (subscribed) user folder, list all (unsubscribed) subfolders - foreach ($folders as $userfolder) { - foreach ((array)self::list_folders($userfolder->name . $delimiter, '*', $type, false, $folderdata) as $foldername) { - if (!$folders[$foldername]) { - $folders[$foldername] = new kolab_storage_folder($foldername, $type, $folderdata[$foldername]); - $userfolder->children[] = $folders[$foldername]; - } - } - } - } - - return $folders; - } - - - /** - * Handler for user_delete plugin hooks - * - * Remove all cache data from the local database related to the given user. - */ - public static function delete_user_folders($args) - { - $db = rcmail::get_instance()->get_dbh(); - $prefix = 'imap://' . urlencode($args['username']) . '@' . $args['host'] . '/%'; - $db->query("DELETE FROM " . $db->table_name('kolab_folders', true) . " WHERE `resource` LIKE ?", $prefix); - } -} diff --git a/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_cache.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_cache.php deleted file mode 100644 index 162c220..0000000 --- a/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_cache.php +++ /dev/null @@ -1,1171 +0,0 @@ - - * - * Copyright (C) 2012-2013, Kolab Systems AG - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -class kolab_storage_cache -{ - const DB_DATE_FORMAT = 'Y-m-d H:i:s'; - - public $sync_complete = false; - - protected $db; - protected $imap; - protected $folder; - protected $uid2msg; - protected $objects; - protected $metadata = array(); - protected $folder_id; - protected $resource_uri; - protected $enabled = true; - protected $synched = false; - protected $synclock = false; - protected $ready = false; - protected $cache_table; - protected $cache_refresh = 3600; - protected $folders_table; - protected $max_sql_packet; - protected $max_sync_lock_time = 600; - protected $binary_items = array(); - protected $extra_cols = array(); - protected $order_by = null; - protected $limit = null; - protected $error = 0; - protected $server_timezone; - - - /** - * Factory constructor - */ - public static function factory(kolab_storage_folder $storage_folder) - { - $subclass = 'kolab_storage_cache_' . $storage_folder->type; - if (class_exists($subclass)) { - return new $subclass($storage_folder); - } - else { - rcube::raise_error(array( - 'code' => 900, - 'type' => 'php', - 'message' => "No kolab_storage_cache class found for folder '$storage_folder->name' of type '$storage_folder->type'" - ), true); - - return new kolab_storage_cache($storage_folder); - } - } - - - /** - * Default constructor - */ - public function __construct(kolab_storage_folder $storage_folder = null) - { - $rcmail = rcube::get_instance(); - $this->db = $rcmail->get_dbh(); - $this->imap = $rcmail->get_storage(); - $this->enabled = $rcmail->config->get('kolab_cache', false); - $this->folders_table = $this->db->table_name('kolab_folders'); - $this->cache_refresh = get_offset_sec($rcmail->config->get('kolab_cache_refresh', '12h')); - $this->server_timezone = new DateTimeZone(date_default_timezone_get()); - - if ($this->enabled) { - // always read folder cache and lock state from DB master - $this->db->set_table_dsn('kolab_folders', 'w'); - // remove sync-lock on script termination - $rcmail->add_shutdown_function(array($this, '_sync_unlock')); - } - - if ($storage_folder) - $this->set_folder($storage_folder); - } - - /** - * Direct access to cache by folder_id - * (only for internal use) - */ - public function select_by_id($folder_id) - { - $sql_arr = $this->db->fetch_assoc($this->db->query("SELECT * FROM `{$this->folders_table}` WHERE `folder_id` = ?", $folder_id)); - if ($sql_arr) { - $this->metadata = $sql_arr; - $this->folder_id = $sql_arr['folder_id']; - $this->folder = new StdClass; - $this->folder->type = $sql_arr['type']; - $this->resource_uri = $sql_arr['resource']; - $this->cache_table = $this->db->table_name('kolab_cache_' . $sql_arr['type']); - $this->ready = true; - } - } - - /** - * Connect cache with a storage folder - * - * @param kolab_storage_folder The storage folder instance to connect with - */ - public function set_folder(kolab_storage_folder $storage_folder) - { - $this->folder = $storage_folder; - - if (empty($this->folder->name) || !$this->folder->valid) { - $this->ready = false; - return; - } - - // compose fully qualified ressource uri for this instance - $this->resource_uri = $this->folder->get_resource_uri(); - $this->cache_table = $this->db->table_name('kolab_cache_' . $this->folder->type); - $this->ready = $this->enabled && !empty($this->folder->type); - $this->folder_id = null; - } - - /** - * Returns true if this cache supports query by type - */ - public function has_type_col() - { - return in_array('type', $this->extra_cols); - } - - /** - * Getter for the numeric ID used in cache tables - */ - public function get_folder_id() - { - $this->_read_folder_data(); - return $this->folder_id; - } - - /** - * Returns code of last error - * - * @return int Error code - */ - public function get_error() - { - return $this->error; - } - - /** - * Synchronize local cache data with remote - */ - public function synchronize() - { - // only sync once per request cycle - if ($this->synched) - return; - - // increase time limit - @set_time_limit($this->max_sync_lock_time - 60); - - // get effective time limit we have for synchronization (~70% of the execution time) - $time_limit = ini_get('max_execution_time') * 0.7; - $sync_start = time(); - - // assume sync will be completed - $this->sync_complete = true; - - if (!$this->ready) { - // kolab cache is disabled, synchronize IMAP mailbox cache only - $this->imap->folder_sync($this->folder->name); - } - else { - // read cached folder metadata - $this->_read_folder_data(); - - // check cache status ($this->metadata is set in _read_folder_data()) - if ( empty($this->metadata['ctag']) || - empty($this->metadata['changed']) || - $this->metadata['objectcount'] === null || - $this->metadata['changed'] < date(self::DB_DATE_FORMAT, time() - $this->cache_refresh) || - $this->metadata['ctag'] != $this->folder->get_ctag() || - intval($this->metadata['objectcount']) !== $this->count() - ) { - // lock synchronization for this folder or wait if locked - $this->_sync_lock(); - - // disable messages cache if configured to do so - $this->bypass(true); - - // synchronize IMAP mailbox cache - $this->imap->folder_sync($this->folder->name); - - // compare IMAP index with object cache index - $imap_index = $this->imap->index($this->folder->name, null, null, true, true); - - // determine objects to fetch or to invalidate - if (!$imap_index->is_error()) { - $imap_index = $imap_index->get(); - - // read cache index - $sql_result = $this->db->query( - "SELECT `msguid`, `uid` FROM `{$this->cache_table}` WHERE `folder_id` = ?", - $this->folder_id - ); - - $old_index = array(); - while ($sql_arr = $this->db->fetch_assoc($sql_result)) { - $old_index[] = $sql_arr['msguid']; - } - - // fetch new objects from imap - $i = 0; - foreach (array_diff($imap_index, $old_index) as $msguid) { - if ($object = $this->folder->read_object($msguid, '*')) { - $this->_extended_insert($msguid, $object); - - // check time limit and abort sync if running too long - if (++$i % 50 == 0 && time() - $sync_start > $time_limit) { - $this->sync_complete = false; - break; - } - } - } - $this->_extended_insert(0, null); - - // delete invalid entries from local DB - $del_index = array_diff($old_index, $imap_index); - if (!empty($del_index)) { - $quoted_ids = join(',', array_map(array($this->db, 'quote'), $del_index)); - $this->db->query( - "DELETE FROM `{$this->cache_table}` WHERE `folder_id` = ? AND `msguid` IN ($quoted_ids)", - $this->folder_id - ); - } - - // update ctag value (will be written to database in _sync_unlock()) - if ($this->sync_complete) { - $this->metadata['ctag'] = $this->folder->get_ctag(); - $this->metadata['changed'] = date(self::DB_DATE_FORMAT, time()); - // remember the number of cache entries linked to this folder - $this->metadata['objectcount'] = $this->count(); - } - } - - $this->bypass(false); - - // remove lock - $this->_sync_unlock(); - } - } - - $this->check_error(); - $this->synched = time(); - } - - - /** - * Read a single entry from cache or from IMAP directly - * - * @param string Related IMAP message UID - * @param string Object type to read - * @param string IMAP folder name the entry relates to - * @param array Hash array with object properties or null if not found - */ - public function get($msguid, $type = null, $foldername = null) - { - // delegate to another cache instance - if ($foldername && $foldername != $this->folder->name) { - $success = false; - if ($targetfolder = kolab_storage::get_folder($foldername)) { - $success = $targetfolder->cache->get($msguid, $type); - $this->error = $targetfolder->cache->get_error(); - } - return $success; - } - - // load object if not in memory - if (!isset($this->objects[$msguid])) { - if ($this->ready) { - $this->_read_folder_data(); - - $sql_result = $this->db->query( - "SELECT * FROM `{$this->cache_table}` ". - "WHERE `folder_id` = ? AND `msguid` = ?", - $this->folder_id, - $msguid - ); - - if ($sql_arr = $this->db->fetch_assoc($sql_result)) { - $this->objects = array($msguid => $this->_unserialize($sql_arr)); // store only this object in memory (#2827) - } - } - - // fetch from IMAP if not present in cache - if (empty($this->objects[$msguid])) { - if ($object = $this->folder->read_object($msguid, $type ?: '*', $foldername)) { - $this->objects = array($msguid => $object); - $this->set($msguid, $object); - } - } - } - - $this->check_error(); - return $this->objects[$msguid]; - } - - - /** - * Insert/Update a cache entry - * - * @param string Related IMAP message UID - * @param mixed Hash array with object properties to save or false to delete the cache entry - * @param string IMAP folder name the entry relates to - */ - public function set($msguid, $object, $foldername = null) - { - if (!$msguid) { - return; - } - - // delegate to another cache instance - if ($foldername && $foldername != $this->folder->name) { - if ($targetfolder = kolab_storage::get_folder($foldername)) { - $targetfolder->cache->set($msguid, $object); - $this->error = $targetfolder->cache->get_error(); - } - return; - } - - // remove old entry - if ($this->ready) { - $this->_read_folder_data(); - $this->db->query("DELETE FROM `{$this->cache_table}` WHERE `folder_id` = ? AND `msguid` = ?", - $this->folder_id, $msguid); - } - - if ($object) { - // insert new object data... - $this->save($msguid, $object); - } - else { - // ...or set in-memory cache to false - $this->objects[$msguid] = $object; - } - - $this->check_error(); - } - - - /** - * Insert (or update) a cache entry - * - * @param int Related IMAP message UID - * @param mixed Hash array with object properties to save or false to delete the cache entry - * @param int Optional old message UID (for update) - */ - public function save($msguid, $object, $olduid = null) - { - // write to cache - if ($this->ready) { - $this->_read_folder_data(); - - $sql_data = $this->_serialize($object); - $sql_data['folder_id'] = $this->folder_id; - $sql_data['msguid'] = $msguid; - $sql_data['uid'] = $object['uid']; - - $args = array(); - $cols = array('folder_id', 'msguid', 'uid', 'changed', 'data', 'xml', 'tags', 'words'); - $cols = array_merge($cols, $this->extra_cols); - - foreach ($cols as $idx => $col) { - $cols[$idx] = $this->db->quote_identifier($col); - $args[] = $sql_data[$col]; - } - - if ($olduid) { - foreach ($cols as $idx => $col) { - $cols[$idx] = "$col = ?"; - } - - $query = "UPDATE `{$this->cache_table}` SET " . implode(', ', $cols) - . " WHERE `folder_id` = ? AND `msguid` = ?"; - $args[] = $this->folder_id; - $args[] = $olduid; - } - else { - $query = "INSERT INTO `{$this->cache_table}` (`created`, " . implode(', ', $cols) - . ") VALUES (" . $this->db->now() . str_repeat(', ?', count($cols)) . ")"; - } - - $result = $this->db->query($query, $args); - - if (!$this->db->affected_rows($result)) { - rcube::raise_error(array( - 'code' => 900, 'type' => 'php', - 'message' => "Failed to write to kolab cache" - ), true); - } - } - - // keep a copy in memory for fast access - $this->objects = array($msguid => $object); - $this->uid2msg = array($object['uid'] => $msguid); - - $this->check_error(); - } - - - /** - * Move an existing cache entry to a new resource - * - * @param string Entry's IMAP message UID - * @param string Entry's Object UID - * @param object kolab_storage_folder Target storage folder instance - */ - public function move($msguid, $uid, $target) - { - if ($this->ready) { - // clear cached uid mapping and force new lookup - unset($target->cache->uid2msg[$uid]); - - // resolve new message UID in target folder - if ($new_msguid = $target->cache->uid2msguid($uid)) { - $this->_read_folder_data(); - - $this->db->query( - "UPDATE `{$this->cache_table}` SET `folder_id` = ?, `msguid` = ? ". - "WHERE `folder_id` = ? AND `msguid` = ?", - $target->cache->get_folder_id(), - $new_msguid, - $this->folder_id, - $msguid - ); - - $result = $this->db->affected_rows(); - } - } - - if (empty($result)) { - // just clear cache entry - $this->set($msguid, false); - } - - unset($this->uid2msg[$uid]); - $this->check_error(); - } - - - /** - * Remove all objects from local cache - */ - public function purge() - { - if (!$this->ready) { - return true; - } - - $this->_read_folder_data(); - - $result = $this->db->query( - "DELETE FROM `{$this->cache_table}` WHERE `folder_id` = ?", - $this->folder_id - ); - - return $this->db->affected_rows($result); - } - - /** - * Update resource URI for existing cache entries - * - * @param string Target IMAP folder to move it to - */ - public function rename($new_folder) - { - if (!$this->ready) { - return; - } - - if ($target = kolab_storage::get_folder($new_folder)) { - // resolve new message UID in target folder - $this->db->query( - "UPDATE `{$this->folders_table}` SET `resource` = ? ". - "WHERE `resource` = ?", - $target->get_resource_uri(), - $this->resource_uri - ); - - $this->check_error(); - } - else { - $this->error = kolab_storage::ERROR_IMAP_CONN; - } - } - - /** - * Select Kolab objects filtered by the given query - * - * @param array Pseudo-SQL query as list of filter parameter triplets - * triplet: array('', '', '') - * @param boolean Set true to only return UIDs instead of complete objects - * @return array List of Kolab data objects (each represented as hash array) or UIDs - */ - public function select($query = array(), $uids = false) - { - $result = $uids ? array() : new kolab_storage_dataset($this); - - // read from local cache DB (assume it to be synchronized) - if ($this->ready) { - $this->_read_folder_data(); - - // fetch full object data on one query if a small result set is expected - $fetchall = !$uids && ($this->limit ? $this->limit[0] : ($count = $this->count($query))) < 500; - - // skip SELECT if we know it will return nothing - if ($count === 0) { - return $result; - } - - $sql_query = "SELECT " . ($fetchall ? '*' : "`msguid` AS `_msguid`, `uid`") - . " FROM `{$this->cache_table}` WHERE `folder_id` = ?" - . $this->_sql_where($query) - . (!empty($this->order_by) ? " ORDER BY " . $this->order_by : ''); - - $sql_result = $this->limit ? - $this->db->limitquery($sql_query, $this->limit[1], $this->limit[0], $this->folder_id) : - $this->db->query($sql_query, $this->folder_id); - - if ($this->db->is_error($sql_result)) { - if ($uids) { - return null; - } - $result->set_error(true); - return $result; - } - - while ($sql_arr = $this->db->fetch_assoc($sql_result)) { - if ($uids) { - $this->uid2msg[$sql_arr['uid']] = $sql_arr['_msguid']; - $result[] = $sql_arr['uid']; - } - else if ($fetchall && ($object = $this->_unserialize($sql_arr))) { - $result[] = $object; - } - else if (!$fetchall) { - // only add msguid to dataset index - $result[] = $sql_arr; - } - } - } - // use IMAP - else { - $filter = $this->_query2assoc($query); - - if ($filter['type']) { - $search = 'UNDELETED HEADER X-Kolab-Type ' . kolab_format::KTYPE_PREFIX . $filter['type']; - $index = $this->imap->search_once($this->folder->name, $search); - } - else { - $index = $this->imap->index($this->folder->name, null, null, true, true); - } - - if ($index->is_error()) { - $this->check_error(); - if ($uids) { - return null; - } - $result->set_error(true); - return $result; - } - - $index = $index->get(); - $result = $uids ? $index : $this->_fetch($index, $filter['type']); - - // TODO: post-filter result according to query - } - - // We don't want to cache big results in-memory, however - // if we select only one object here, there's a big chance we will need it later - if (!$uids && count($result) == 1) { - if ($msguid = $result[0]['_msguid']) { - $this->uid2msg[$result[0]['uid']] = $msguid; - $this->objects = array($msguid => $result[0]); - } - } - - $this->check_error(); - - return $result; - } - - - /** - * Get number of objects mathing the given query - * - * @param array $query Pseudo-SQL query as list of filter parameter triplets - * @return integer The number of objects of the given type - */ - public function count($query = array()) - { - // read from local cache DB (assume it to be synchronized) - if ($this->ready) { - $this->_read_folder_data(); - - $sql_result = $this->db->query( - "SELECT COUNT(*) AS `numrows` FROM `{$this->cache_table}` ". - "WHERE `folder_id` = ?" . $this->_sql_where($query), - $this->folder_id - ); - - if ($this->db->is_error($sql_result)) { - return null; - } - - $sql_arr = $this->db->fetch_assoc($sql_result); - $count = intval($sql_arr['numrows']); - } - // use IMAP - else { - $filter = $this->_query2assoc($query); - - if ($filter['type']) { - $search = 'UNDELETED HEADER X-Kolab-Type ' . kolab_format::KTYPE_PREFIX . $filter['type']; - $index = $this->imap->search_once($this->folder->name, $search); - } - else { - $index = $this->imap->index($this->folder->name, null, null, true, true); - } - - if ($index->is_error()) { - $this->check_error(); - return null; - } - - // TODO: post-filter result according to query - - $count = $index->count(); - } - - $this->check_error(); - return $count; - } - - /** - * Define ORDER BY clause for cache queries - */ - public function set_order_by($sortcols) - { - if (!empty($sortcols)) { - $this->order_by = '`' . join('`, `', (array)$sortcols) . '`'; - } - else { - $this->order_by = null; - } - } - - /** - * Define LIMIT clause for cache queries - */ - public function set_limit($length, $offset = 0) - { - $this->limit = array($length, $offset); - } - - /** - * Helper method to compose a valid SQL query from pseudo filter triplets - */ - protected function _sql_where($query) - { - $sql_where = ''; - foreach ((array) $query as $param) { - if (is_array($param[0])) { - $subq = array(); - foreach ($param[0] as $q) { - $subq[] = preg_replace('/^\s*AND\s+/i', '', $this->_sql_where(array($q))); - } - if (!empty($subq)) { - $sql_where .= ' AND (' . implode($param[1] == 'OR' ? ' OR ' : ' AND ', $subq) . ')'; - } - continue; - } - else if ($param[1] == '=' && is_array($param[2])) { - $qvalue = '(' . join(',', array_map(array($this->db, 'quote'), $param[2])) . ')'; - $param[1] = 'IN'; - } - else if ($param[1] == '~' || $param[1] == 'LIKE' || $param[1] == '!~' || $param[1] == '!LIKE') { - $not = ($param[1] == '!~' || $param[1] == '!LIKE') ? 'NOT ' : ''; - $param[1] = $not . 'LIKE'; - $qvalue = $this->db->quote('%'.preg_replace('/(^\^|\$$)/', ' ', $param[2]).'%'); - } - else if ($param[0] == 'tags') { - $param[1] = ($param[1] == '!=' ? 'NOT ' : '' ) . 'LIKE'; - $qvalue = $this->db->quote('% '.$param[2].' %'); - } - else { - $qvalue = $this->db->quote($param[2]); - } - - $sql_where .= sprintf(' AND %s %s %s', - $this->db->quote_identifier($param[0]), - $param[1], - $qvalue - ); - } - - return $sql_where; - } - - /** - * Helper method to convert the given pseudo-query triplets into - * an associative filter array with 'equals' values only - */ - protected function _query2assoc($query) - { - // extract object type from query parameter - $filter = array(); - foreach ($query as $param) { - if ($param[1] == '=') - $filter[$param[0]] = $param[2]; - } - return $filter; - } - - /** - * Fetch messages from IMAP - * - * @param array List of message UIDs to fetch - * @param string Requested object type or * for all - * @param string IMAP folder to read from - * @return array List of parsed Kolab objects - */ - protected function _fetch($index, $type = null, $folder = null) - { - $results = new kolab_storage_dataset($this); - foreach ((array)$index as $msguid) { - if ($object = $this->folder->read_object($msguid, $type, $folder)) { - $results[] = $object; - $this->set($msguid, $object); - } - } - - return $results; - } - - /** - * Helper method to convert the given Kolab object into a dataset to be written to cache - */ - protected function _serialize($object) - { - $sql_data = array('changed' => null, 'xml' => '', 'tags' => '', 'words' => ''); - - if ($object['changed']) { - $sql_data['changed'] = date(self::DB_DATE_FORMAT, is_object($object['changed']) ? $object['changed']->format('U') : $object['changed']); - } - - if ($object['_formatobj']) { - $sql_data['xml'] = preg_replace('!()[\n\r\t\s]+!ms', '$1', (string)$object['_formatobj']->write(3.0)); - $sql_data['tags'] = ' ' . join(' ', $object['_formatobj']->get_tags()) . ' '; // pad with spaces for strict/prefix search - $sql_data['words'] = ' ' . join(' ', $object['_formatobj']->get_words()) . ' '; - } - - // extract object data - $data = array(); - foreach ($object as $key => $val) { - // skip empty properties - if ($val === "" || $val === null) { - continue; - } - // mark binary data to be extracted from xml on unserialize() - if (isset($this->binary_items[$key])) { - $data[$key] = true; - } - else if ($key[0] != '_') { - $data[$key] = $val; - } - else if ($key == '_attachments') { - foreach ($val as $k => $att) { - unset($att['content'], $att['path']); - if ($att['id']) - $data[$key][$k] = $att; - } - } - } - - // use base64 encoding (Bug #1912, #2662) - $sql_data['data'] = base64_encode(serialize($data)); - - return $sql_data; - } - - /** - * Helper method to turn stored cache data into a valid storage object - */ - protected function _unserialize($sql_arr) - { - // check if data is a base64-encoded string, for backward compat. - if (strpos(substr($sql_arr['data'], 0, 64), ':') === false) { - $sql_arr['data'] = base64_decode($sql_arr['data']); - } - - $object = unserialize($sql_arr['data']); - - // de-serialization failed - if ($object === false) { - rcube::raise_error(array( - 'code' => 900, 'type' => 'php', - 'message' => "Malformed data for {$this->resource_uri}/{$sql_arr['msguid']} object." - ), true); - - return null; - } - - // decode binary properties - foreach ($this->binary_items as $key => $regexp) { - if (!empty($object[$key]) && preg_match($regexp, $sql_arr['xml'], $m)) { - $object[$key] = base64_decode($m[1]); - } - } - - $object_type = $sql_arr['type'] ?: $this->folder->type; - $format_type = $this->folder->type == 'configuration' ? 'configuration' : $object_type; - - // add meta data - $object['_type'] = $object_type; - $object['_msguid'] = $sql_arr['msguid']; - $object['_mailbox'] = $this->folder->name; - $object['_size'] = strlen($sql_arr['xml']); - $object['_formatobj'] = kolab_format::factory($format_type, 3.0, $sql_arr['xml']); - - return $object; - } - - /** - * Write records into cache using extended inserts to reduce the number of queries to be executed - * - * @param int Message UID. Set 0 to commit buffered inserts - * @param array Kolab object to cache - */ - protected function _extended_insert($msguid, $object) - { - static $buffer = ''; - - $line = ''; - if ($object) { - $sql_data = $this->_serialize($object); - - // Skip multifolder insert for Oracle, we can't put long data inline - if ($this->db->db_provider == 'oracle') { - $extra_cols = ''; - if ($this->extra_cols) { - $extra_cols = array_map(function($n) { return "`{$n}`"; }, $this->extra_cols); - $extra_cols = ', ' . join(', ', $extra_cols); - $extra_args = str_repeat(', ?', count($this->extra_cols)); - } - - $params = array($this->folder_id, $msguid, $object['uid'], $sql_data['changed'], - $sql_data['data'], $sql_data['xml'], $sql_data['tags'], $sql_data['words']); - - foreach ($this->extra_cols as $col) { - $params[] = $sql_data[$col]; - } - - $result = $this->db->query( - "INSERT INTO `{$this->cache_table}` " - . " (`folder_id`, `msguid`, `uid`, `created`, `changed`, `data`, `xml`, `tags`, `words` $extra_cols)" - . " VALUES (?, ?, ?, " . $this->db->now() . ", ?, ?, ?, ?, ? $extra_args)", - $params - ); - - if (!$this->db->affected_rows($result)) { - rcube::raise_error(array( - 'code' => 900, 'type' => 'php', - 'message' => "Failed to write to kolab cache" - ), true); - } - - return; - } - - $values = array( - $this->db->quote($this->folder_id), - $this->db->quote($msguid), - $this->db->quote($object['uid']), - $this->db->now(), - $this->db->quote($sql_data['changed']), - $this->db->quote($sql_data['data']), - $this->db->quote($sql_data['xml']), - $this->db->quote($sql_data['tags']), - $this->db->quote($sql_data['words']), - ); - foreach ($this->extra_cols as $col) { - $values[] = $this->db->quote($sql_data[$col]); - } - $line = '(' . join(',', $values) . ')'; - } - - if ($buffer && (!$msguid || (strlen($buffer) + strlen($line) > $this->max_sql_packet()))) { - $extra_cols = ''; - if ($this->extra_cols) { - $extra_cols = array_map(function($n) { return "`{$n}`"; }, $this->extra_cols); - $extra_cols = ', ' . join(', ', $extra_cols); - } - - $result = $this->db->query( - "INSERT INTO `{$this->cache_table}` ". - " (`folder_id`, `msguid`, `uid`, `created`, `changed`, `data`, `xml`, `tags`, `words` $extra_cols)". - " VALUES $buffer" - ); - - if (!$this->db->affected_rows($result)) { - rcube::raise_error(array( - 'code' => 900, 'type' => 'php', - 'message' => "Failed to write to kolab cache" - ), true); - } - - $buffer = ''; - } - - $buffer .= ($buffer ? ',' : '') . $line; - } - - /** - * Returns max_allowed_packet from mysql config - */ - protected function max_sql_packet() - { - if (!$this->max_sql_packet) { - // mysql limit or max 4 MB - $value = $this->db->get_variable('max_allowed_packet', 1048500); - $this->max_sql_packet = min($value, 4*1024*1024) - 2000; - } - - return $this->max_sql_packet; - } - - /** - * Read this folder's ID and cache metadata - */ - protected function _read_folder_data() - { - // already done - if (!empty($this->folder_id) || !$this->ready) - return; - - $sql_arr = $this->db->fetch_assoc($this->db->query( - "SELECT `folder_id`, `synclock`, `ctag`, `changed`, `objectcount`" - . " FROM `{$this->folders_table}` WHERE `resource` = ?", - $this->resource_uri - )); - - if ($sql_arr) { - $this->metadata = $sql_arr; - $this->folder_id = $sql_arr['folder_id']; - } - else { - $this->db->query("INSERT INTO `{$this->folders_table}` (`resource`, `type`)" - . " VALUES (?, ?)", $this->resource_uri, $this->folder->type); - - $this->folder_id = $this->db->insert_id('kolab_folders'); - $this->metadata = array(); - } - } - - /** - * Check lock record for this folder and wait if locked or set lock - */ - protected function _sync_lock() - { - if (!$this->ready) - return; - - $this->_read_folder_data(); - - // abort if database is not set-up - if ($this->db->is_error()) { - $this->check_error(); - $this->ready = false; - return; - } - - $read_query = "SELECT `synclock`, `ctag` FROM `{$this->folders_table}` WHERE `folder_id` = ?"; - $write_query = "UPDATE `{$this->folders_table}` SET `synclock` = ? WHERE `folder_id` = ? AND `synclock` = ?"; - - // wait if locked (expire locks after 10 minutes) ... - // ... or if setting lock fails (another process meanwhile set it) - while ( - (intval($this->metadata['synclock']) + $this->max_sync_lock_time > time()) || - (($res = $this->db->query($write_query, time(), $this->folder_id, intval($this->metadata['synclock']))) && - !($affected = $this->db->affected_rows($res))) - ) { - usleep(500000); - $this->metadata = $this->db->fetch_assoc($this->db->query($read_query, $this->folder_id)); - } - - $this->synclock = $affected > 0; - } - - /** - * Remove lock for this folder - */ - public function _sync_unlock() - { - if (!$this->ready || !$this->synclock) - return; - - $this->db->query( - "UPDATE `{$this->folders_table}` SET `synclock` = 0, `ctag` = ?, `changed` = ?, `objectcount` = ? WHERE `folder_id` = ?", - $this->metadata['ctag'], - $this->metadata['changed'], - $this->metadata['objectcount'], - $this->folder_id - ); - - $this->synclock = false; - } - - /** - * Check IMAP connection error state - */ - protected function check_error() - { - if (($err_code = $this->imap->get_error_code()) < 0) { - $this->error = kolab_storage::ERROR_IMAP_CONN; - if (($res_code = $this->imap->get_response_code()) !== 0 && in_array($res_code, array(rcube_storage::NOPERM, rcube_storage::READONLY))) { - $this->error = kolab_storage::ERROR_NO_PERMISSION; - } - } - else if ($this->db->is_error()) { - $this->error = kolab_storage::ERROR_CACHE_DB; - } - } - - /** - * Resolve an object UID into an IMAP message UID - * - * @param string Kolab object UID - * @param boolean Include deleted objects - * @return int The resolved IMAP message UID - */ - public function uid2msguid($uid, $deleted = false) - { - // query local database if available - if (!isset($this->uid2msg[$uid]) && $this->ready) { - $this->_read_folder_data(); - - $sql_result = $this->db->query( - "SELECT `msguid` FROM `{$this->cache_table}` ". - "WHERE `folder_id` = ? AND `uid` = ? ORDER BY `msguid` DESC", - $this->folder_id, - $uid - ); - - if ($sql_arr = $this->db->fetch_assoc($sql_result)) { - $this->uid2msg[$uid] = $sql_arr['msguid']; - } - } - - if (!isset($this->uid2msg[$uid])) { - // use IMAP SEARCH to get the right message - $index = $this->imap->search_once($this->folder->name, ($deleted ? '' : 'UNDELETED ') . - 'HEADER SUBJECT ' . rcube_imap_generic::escape($uid)); - $results = $index->get(); - $this->uid2msg[$uid] = end($results); - } - - return $this->uid2msg[$uid]; - } - - /** - * Getter for protected member variables - */ - public function __get($name) - { - if ($name == 'folder_id') { - $this->_read_folder_data(); - } - - return $this->$name; - } - - /** - * Bypass Roundcube messages cache. - * Roundcube cache duplicates information already stored in kolab_cache. - * - * @param bool $disable True disables, False enables messages cache - */ - public function bypass($disable = false) - { - // if kolab cache is disabled do nothing - if (!$this->enabled) { - return; - } - - static $messages_cache, $cache_bypass; - - if ($messages_cache === null) { - $rcmail = rcube::get_instance(); - $messages_cache = (bool) $rcmail->config->get('messages_cache'); - $cache_bypass = (int) $rcmail->config->get('kolab_messages_cache_bypass'); - } - - if ($messages_cache) { - // handle recurrent (multilevel) bypass() calls - if ($disable) { - $this->cache_bypassed += 1; - if ($this->cache_bypassed > 1) { - return; - } - } - else { - $this->cache_bypassed -= 1; - if ($this->cache_bypassed > 0) { - return; - } - } - - switch ($cache_bypass) { - case 2: - // Disable messages cache completely - $this->imap->set_messages_caching(!$disable); - break; - - case 1: - // We'll disable messages cache, but keep index cache. - // Default mode is both (MODE_INDEX | MODE_MESSAGE) - $mode = rcube_imap_cache::MODE_INDEX; - - if (!$disable) { - $mode |= rcube_imap_cache::MODE_MESSAGE; - } - - $this->imap->set_messages_caching(true, $mode); - } - } - } - - /** - * Converts DateTime or unix timestamp into sql date format - * using server timezone. - */ - protected function _convert_datetime($datetime) - { - if (is_object($datetime)) { - $dt = clone $datetime; - $dt->setTimeZone($this->server_timezone); - return $dt->format(self::DB_DATE_FORMAT); - } - else if ($datetime) { - return date(self::DB_DATE_FORMAT, $datetime); - } - } -} diff --git a/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_cache_configuration.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_cache_configuration.php deleted file mode 100644 index c3c7ac4..0000000 --- a/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_cache_configuration.php +++ /dev/null @@ -1,88 +0,0 @@ - - * - * Copyright (C) 2013, Kolab Systems AG - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -class kolab_storage_cache_configuration extends kolab_storage_cache -{ - protected $extra_cols = array('type'); - - /** - * Helper method to convert the given Kolab object into a dataset to be written to cache - * - * @override - */ - protected function _serialize($object) - { - $sql_data = parent::_serialize($object); - $sql_data['type'] = $object['type']; - - return $sql_data; - } - - /** - * Select Kolab objects filtered by the given query - * - * @param array Pseudo-SQL query as list of filter parameter triplets - * @param boolean Set true to only return UIDs instead of complete objects - * @return array List of Kolab data objects (each represented as hash array) or UIDs - */ - public function select($query = array(), $uids = false) - { - // modify query for IMAP search: query param 'type' is actually a subtype - if (!$this->ready) { - foreach ($query as $i => $tuple) { - if ($tuple[0] == 'type') { - $tuple[2] = 'configuration.' . $tuple[2]; - $query[$i] = $tuple; - } - } - } - - return parent::select($query, $uids); - } - - /** - * Helper method to compose a valid SQL query from pseudo filter triplets - */ - protected function _sql_where($query) - { - if (is_array($query)) { - foreach ($query as $idx => $param) { - // convert category filter - if ($param[0] == 'category') { - $param[2] = array_map(function($n) { return 'category:' . $n; }, (array) $param[2]); - - $query[$idx][0] = 'tags'; - $query[$idx][2] = count($param[2]) > 1 ? $param[2] : $param[2][0]; - } - // convert member filter (we support only = operator with single value) - else if ($param[0] == 'member') { - $query[$idx][0] = 'words'; - $query[$idx][1] = '~'; - $query[$idx][2] = '^' . $param[2] . '$'; - } - } - } - - return parent::_sql_where($query); - } -} diff --git a/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_cache_contact.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_cache_contact.php deleted file mode 100644 index d526a0e..0000000 --- a/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_cache_contact.php +++ /dev/null @@ -1,64 +0,0 @@ - - * - * Copyright (C) 2013, Kolab Systems AG - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -class kolab_storage_cache_contact extends kolab_storage_cache -{ - protected $extra_cols = array('type','name','firstname','surname','email'); - protected $binary_items = array( - 'photo' => '|[^;]+;base64,([^<]+)|i', - 'pgppublickey' => '|data:application/pgp-keys;base64,([^<]+)|i', - 'pkcs7publickey' => '|data:application/pkcs7-mime;base64,([^<]+)|i', - ); - - /** - * Helper method to convert the given Kolab object into a dataset to be written to cache - * - * @override - */ - protected function _serialize($object) - { - $sql_data = parent::_serialize($object); - $sql_data['type'] = $object['_type']; - - // columns for sorting - $sql_data['name'] = rcube_charset::clean($object['name'] . $object['prefix']); - $sql_data['firstname'] = rcube_charset::clean($object['firstname'] . $object['middlename'] . $object['surname']); - $sql_data['surname'] = rcube_charset::clean($object['surname'] . $object['firstname'] . $object['middlename']); - $sql_data['email'] = rcube_charset::clean(is_array($object['email']) ? $object['email'][0] : $object['email']); - - if (is_array($sql_data['email'])) { - $sql_data['email'] = $sql_data['email']['address']; - } - // avoid value being null - if (empty($sql_data['email'])) { - $sql_data['email'] = ''; - } - - // use organization if name is empty - if (empty($sql_data['name']) && !empty($object['organization'])) { - $sql_data['name'] = rcube_charset::clean($object['organization']); - } - - return $sql_data; - } -} \ No newline at end of file diff --git a/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_cache_event.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_cache_event.php deleted file mode 100644 index ae9c693..0000000 --- a/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_cache_event.php +++ /dev/null @@ -1,67 +0,0 @@ - - * - * Copyright (C) 2013, Kolab Systems AG - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -class kolab_storage_cache_event extends kolab_storage_cache -{ - protected $extra_cols = array('dtstart','dtend'); - - /** - * Helper method to convert the given Kolab object into a dataset to be written to cache - * - * @override - */ - protected function _serialize($object) - { - $sql_data = parent::_serialize($object); - - $sql_data['dtstart'] = $this->_convert_datetime($object['start']); - $sql_data['dtend'] = $this->_convert_datetime($object['end']); - - // extend date range for recurring events - if ($object['recurrence'] && $object['_formatobj']) { - $recurrence = new kolab_date_recurrence($object['_formatobj']); - $dtend = $recurrence->end() ?: new DateTime('now +10 years'); - $sql_data['dtend'] = $this->_convert_datetime($dtend); - } - - // extend start/end dates to spawn all exceptions - if (is_array($object['exceptions'])) { - foreach ($object['exceptions'] as $exception) { - if (is_a($exception['start'], 'DateTime')) { - $exstart = $this->_convert_datetime($exception['start']); - if ($exstart < $sql_data['dtstart']) { - $sql_data['dtstart'] = $exstart; - } - } - if (is_a($exception['end'], 'DateTime')) { - $exend = $this->_convert_datetime($exception['end']); - if ($exend > $sql_data['dtend']) { - $sql_data['dtend'] = $exend; - } - } - } - } - - return $sql_data; - } -} diff --git a/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_cache_file.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_cache_file.php deleted file mode 100644 index ea1823d..0000000 --- a/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_cache_file.php +++ /dev/null @@ -1,44 +0,0 @@ - - * - * Copyright (C) 2013, Kolab Systems AG - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -class kolab_storage_cache_file extends kolab_storage_cache -{ - protected $extra_cols = array('filename'); - - /** - * Helper method to convert the given Kolab object into a dataset to be written to cache - * - * @override - */ - protected function _serialize($object) - { - $sql_data = parent::_serialize($object); - - if (!empty($object['_attachments'])) { - reset($object['_attachments']); - $sql_data['filename'] = $object['_attachments'][key($object['_attachments'])]['name']; - } - - return $sql_data; - } -} \ No newline at end of file diff --git a/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_cache_freebusy.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_cache_freebusy.php deleted file mode 100644 index 50e4804..0000000 --- a/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_cache_freebusy.php +++ /dev/null @@ -1,42 +0,0 @@ - - * - * Copyright (C) 2013, Kolab Systems AG - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -class kolab_storage_cache_freebusy extends kolab_storage_cache -{ - protected $extra_cols = array('dtstart','dtend'); - - /** - * Helper method to convert the given Kolab object into a dataset to be written to cache - * - * @override - */ - protected function _serialize($object) - { - $sql_data = parent::_serialize($object) + array('dtstart' => null, 'dtend' => null); - - $sql_data['dtstart'] = $this->_convert_datetime($object['start']); - $sql_data['dtend'] = $this->_convert_datetime($object['end']); - - return $sql_data; - } -} diff --git a/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_cache_journal.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_cache_journal.php deleted file mode 100644 index 766f1da..0000000 --- a/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_cache_journal.php +++ /dev/null @@ -1,42 +0,0 @@ - - * - * Copyright (C) 2013, Kolab Systems AG - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -class kolab_storage_cache_journal extends kolab_storage_cache -{ - protected $extra_cols = array('dtstart','dtend'); - - /** - * Helper method to convert the given Kolab object into a dataset to be written to cache - * - * @override - */ - protected function _serialize($object) - { - $sql_data = parent::_serialize($object) + array('dtstart' => null, 'dtend' => null); - - $sql_data['dtstart'] = $this->_convert_datetime($object['start']); - $sql_data['dtend'] = $this->_convert_datetime($object['end']); - - return $sql_data; - } -} diff --git a/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_cache_mongodb.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_cache_mongodb.php deleted file mode 100644 index 8ae95e4..0000000 --- a/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_cache_mongodb.php +++ /dev/null @@ -1,561 +0,0 @@ - - * - * Copyright (C) 2012, Kolab Systems AG - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -class kolab_storage_cache_mongodb -{ - private $db; - private $imap; - private $folder; - private $uid2msg; - private $objects; - private $index = array(); - private $resource_uri; - private $enabled = true; - private $synched = false; - private $synclock = false; - private $ready = false; - private $max_sql_packet = 1046576; // 1 MB - 2000 bytes - private $binary_cols = array('photo','pgppublickey','pkcs7publickey'); - - - /** - * Default constructor - */ - public function __construct(kolab_storage_folder $storage_folder = null) - { - $rcmail = rcube::get_instance(); - $mongo = new Mongo(); - $this->db = $mongo->kolab_cache; - $this->imap = $rcmail->get_storage(); - $this->enabled = $rcmail->config->get('kolab_cache', false); - - if ($this->enabled) { - // remove sync-lock on script termination - $rcmail->add_shutdown_function(array($this, '_sync_unlock')); - } - - if ($storage_folder) - $this->set_folder($storage_folder); - } - - - /** - * Connect cache with a storage folder - * - * @param kolab_storage_folder The storage folder instance to connect with - */ - public function set_folder(kolab_storage_folder $storage_folder) - { - $this->folder = $storage_folder; - - if (empty($this->folder->name)) { - $this->ready = false; - return; - } - - // compose fully qualified ressource uri for this instance - $this->resource_uri = $this->folder->get_resource_uri(); - $this->ready = $this->enabled; - } - - - /** - * Synchronize local cache data with remote - */ - public function synchronize() - { - // only sync once per request cycle - if ($this->synched) - return; - - // increase time limit - @set_time_limit(500); - - // lock synchronization for this folder or wait if locked - $this->_sync_lock(); - - // synchronize IMAP mailbox cache - $this->imap->folder_sync($this->folder->name); - - // compare IMAP index with object cache index - $imap_index = $this->imap->index($this->folder->name); - $this->index = $imap_index->get(); - - // determine objects to fetch or to invalidate - if ($this->ready) { - // read cache index - $old_index = array(); - $cursor = $this->db->cache->find(array('resource' => $this->resource_uri), array('msguid' => 1, 'uid' => 1)); - foreach ($cursor as $doc) { - $old_index[] = $doc['msguid']; - $this->uid2msg[$doc['uid']] = $doc['msguid']; - } - - // fetch new objects from imap - foreach (array_diff($this->index, $old_index) as $msguid) { - if ($object = $this->folder->read_object($msguid, '*')) { - try { - $this->db->cache->insert($this->_serialize($object, $msguid)); - } - catch (Exception $e) { - rcmail::raise_error(array( - 'code' => 900, 'type' => 'php', - 'message' => "Failed to write to mongodb cache: " . $e->getMessage(), - ), true); - } - } - } - - // delete invalid entries from local DB - $del_index = array_diff($old_index, $this->index); - if (!empty($del_index)) { - $this->db->cache->remove(array('resource' => $this->resource_uri, 'msguid' => array('$in' => $del_index))); - } - } - - // remove lock - $this->_sync_unlock(); - - $this->synched = time(); - } - - - /** - * Read a single entry from cache or from IMAP directly - * - * @param string Related IMAP message UID - * @param string Object type to read - * @param string IMAP folder name the entry relates to - * @param array Hash array with object properties or null if not found - */ - public function get($msguid, $type = null, $foldername = null) - { - // delegate to another cache instance - if ($foldername && $foldername != $this->folder->name) { - return kolab_storage::get_folder($foldername)->cache->get($msguid, $object); - } - - // load object if not in memory - if (!isset($this->objects[$msguid])) { - if ($this->ready && ($doc = $this->db->cache->findOne(array('resource' => $this->resource_uri, 'msguid' => $msguid)))) - $this->objects[$msguid] = $this->_unserialize($doc); - - // fetch from IMAP if not present in cache - if (empty($this->objects[$msguid])) { - $result = $this->_fetch(array($msguid), $type, $foldername); - $this->objects[$msguid] = $result[0]; - } - } - - return $this->objects[$msguid]; - } - - - /** - * Insert/Update a cache entry - * - * @param string Related IMAP message UID - * @param mixed Hash array with object properties to save or false to delete the cache entry - * @param string IMAP folder name the entry relates to - */ - public function set($msguid, $object, $foldername = null) - { - // delegate to another cache instance - if ($foldername && $foldername != $this->folder->name) { - kolab_storage::get_folder($foldername)->cache->set($msguid, $object); - return; - } - - // write to cache - if ($this->ready) { - // remove old entry - $this->db->cache->remove(array('resource' => $this->resource_uri, 'msguid' => $msguid)); - - // write new object data if not false (wich means deleted) - if ($object) { - try { - $this->db->cache->insert($this->_serialize($object, $msguid)); - } - catch (Exception $e) { - rcmail::raise_error(array( - 'code' => 900, 'type' => 'php', - 'message' => "Failed to write to mongodb cache: " . $e->getMessage(), - ), true); - } - } - } - - // keep a copy in memory for fast access - $this->objects[$msguid] = $object; - - if ($object) - $this->uid2msg[$object['uid']] = $msguid; - } - - /** - * Move an existing cache entry to a new resource - * - * @param string Entry's IMAP message UID - * @param string Entry's Object UID - * @param string Target IMAP folder to move it to - */ - public function move($msguid, $objuid, $target_folder) - { - $target = kolab_storage::get_folder($target_folder); - - // resolve new message UID in target folder - if ($new_msguid = $target->cache->uid2msguid($objuid)) { -/* - $this->db->query( - "UPDATE kolab_cache SET resource=?, msguid=? ". - "WHERE resource=? AND msguid=? AND type<>?", - $target->get_resource_uri(), - $new_msguid, - $this->resource_uri, - $msguid, - 'lock' - ); -*/ - } - else { - // just clear cache entry - $this->set($msguid, false); - } - - unset($this->uid2msg[$uid]); - } - - - /** - * Remove all objects from local cache - */ - public function purge($type = null) - { - return $this->db->cache->remove(array(), array('safe' => true)); - } - - - /** - * Select Kolab objects filtered by the given query - * - * @param array Pseudo-SQL query as list of filter parameter triplets - * triplet: array('', '', '') - * @return array List of Kolab data objects (each represented as hash array) - */ - public function select($query = array()) - { - $result = array(); - - // read from local cache DB (assume it to be synchronized) - if ($this->ready) { - $cursor = $this->db->cache->find(array('resource' => $this->resource_uri) + $this->_mongo_filter($query)); - foreach ($cursor as $doc) { - if ($object = $this->_unserialize($doc)) - $result[] = $object; - } - } - else { - // extract object type from query parameter - $filter = $this->_query2assoc($query); - - // use 'list' for folder's default objects - if ($filter['type'] == $this->type) { - $index = $this->index; - } - else { // search by object type - $search = 'UNDELETED HEADER X-Kolab-Type ' . kolab_storage_folder::KTYPE_PREFIX . $filter['type']; - $index = $this->imap->search_once($this->folder->name, $search)->get(); - } - - // fetch all messages in $index from IMAP - $result = $this->_fetch($index, $filter['type']); - - // TODO: post-filter result according to query - } - - return $result; - } - - - /** - * Get number of objects mathing the given query - * - * @param array $query Pseudo-SQL query as list of filter parameter triplets - * @return integer The number of objects of the given type - */ - public function count($query = array()) - { - $count = 0; - - // cache is in sync, we can count records in local DB - if ($this->synched) { - $cursor = $this->db->cache->find(array('resource' => $this->resource_uri) + $this->_mongo_filter($query)); - $count = $cursor->valid() ? $cursor->count() : 0; - } - else { - // search IMAP by object type - $filter = $this->_query2assoc($query); - $ctype = kolab_storage_folder::KTYPE_PREFIX . $filter['type']; - $index = $this->imap->search_once($this->folder->name, 'UNDELETED HEADER X-Kolab-Type ' . $ctype); - $count = $index->count(); - } - - return $count; - } - - /** - * Helper method to convert the pseudo-SQL query into a valid mongodb filter - */ - private function _mongo_filter($query) - { - $filters = array(); - foreach ($query as $param) { - $filter = array(); - if ($param[1] == '=' && is_array($param[2])) { - $filter[$param[0]] = array('$in' => $param[2]); - $filters[] = $filter; - } - else if ($param[1] == '=') { - $filters[] = array($param[0] => $param[2]); - } - else if ($param[1] == 'LIKE' || $param[1] == '~') { - $filter[$param[0]] = array('$regex' => preg_quote($param[2]), '$options' => 'i'); - $filters[] = $filter; - } - else if ($param[1] == '!~' || $param[1] == '!LIKE') { - $filter[$param[0]] = array('$not' => '/' . preg_quote($param[2]) . '/i'); - $filters[] = $filter; - } - else { - $op = ''; - switch ($param[1]) { - case '>': $op = '$gt'; break; - case '>=': $op = '$gte'; break; - case '<': $op = '$lt'; break; - case '<=': $op = '$lte'; break; - case '!=': - case '<>': $op = '$gte'; break; - } - if ($op) { - $filter[$param[0]] = array($op => $param[2]); - $filters[] = $filter; - } - } - } - - return array('$and' => $filters); - } - - /** - * Helper method to convert the given pseudo-query triplets into - * an associative filter array with 'equals' values only - */ - private function _query2assoc($query) - { - // extract object type from query parameter - $filter = array(); - foreach ($query as $param) { - if ($param[1] == '=') - $filter[$param[0]] = $param[2]; - } - return $filter; - } - - /** - * Fetch messages from IMAP - * - * @param array List of message UIDs to fetch - * @return array List of parsed Kolab objects - */ - private function _fetch($index, $type = null, $folder = null) - { - $results = array(); - foreach ((array)$index as $msguid) { - if ($object = $this->folder->read_object($msguid, $type, $folder)) { - $results[] = $object; - $this->set($msguid, $object); - } - } - - return $results; - } - - - /** - * Helper method to convert the given Kolab object into a dataset to be written to cache - */ - private function _serialize($object, $msguid) - { - $bincols = array_flip($this->binary_cols); - $doc = array( - 'resource' => $this->resource_uri, - 'type' => $object['_type'] ? $object['_type'] : $this->folder->type, - 'msguid' => $msguid, - 'uid' => $object['uid'], - 'xml' => '', - 'tags' => array(), - 'words' => array(), - 'objcols' => array(), - ); - - // set type specific values - if ($this->folder->type == 'event') { - // database runs in server's timezone so using date() is what we want - $doc['dtstart'] = date('Y-m-d H:i:s', is_object($object['start']) ? $object['start']->format('U') : $object['start']); - $doc['dtend'] = date('Y-m-d H:i:s', is_object($object['end']) ? $object['end']->format('U') : $object['end']); - - // extend date range for recurring events - if ($object['recurrence']) { - $doc['dtend'] = date('Y-m-d H:i:s', $object['recurrence']['UNTIL'] ?: strtotime('now + 2 years')); - } - } - - if ($object['_formatobj']) { - $doc['xml'] = preg_replace('!()[\n\r\t\s]+!ms', '$1', (string)$object['_formatobj']->write()); - $doc['tags'] = $object['_formatobj']->get_tags(); - $doc['words'] = $object['_formatobj']->get_words(); - } - - // extract object data - $data = array(); - foreach ($object as $key => $val) { - if ($val === "" || $val === null) { - // skip empty properties - continue; - } - if (isset($bincols[$key])) { - $data[$key] = base64_encode($val); - } - else if (is_object($val)) { - if (is_a($val, 'DateTime')) { - $data[$key] = array('_class' => 'DateTime', 'date' => $val->format('Y-m-d H:i:s'), 'timezone' => $val->getTimezone()->getName()); - $doc['objcols'][] = $key; - } - } - else if ($key[0] != '_') { - $data[$key] = $val; - } - else if ($key == '_attachments') { - foreach ($val as $k => $att) { - unset($att['content'], $att['path']); - if ($att['id']) - $data[$key][$k] = $att; - } - } - } - - $doc['data'] = $data; - return $doc; - } - - /** - * Helper method to turn stored cache data into a valid storage object - */ - private function _unserialize($doc) - { - $object = $doc['data']; - - // decode binary properties - foreach ($this->binary_cols as $key) { - if (!empty($object[$key])) - $object[$key] = base64_decode($object[$key]); - } - - // restore serialized objects - foreach ((array)$doc['objcols'] as $key) { - switch ($object[$key]['_class']) { - case 'DateTime': - $val = new DateTime($object[$key]['date'], new DateTimeZone($object[$key]['timezone'])); - $object[$key] = $val; - break; - } - } - - // add meta data - $object['_type'] = $doc['type']; - $object['_msguid'] = $doc['msguid']; - $object['_mailbox'] = $this->folder->name; - $object['_formatobj'] = kolab_format::factory($doc['type'], $doc['xml']); - - return $object; - } - - /** - * Check lock record for this folder and wait if locked or set lock - */ - private function _sync_lock() - { - if (!$this->ready) - return; - - $this->synclock = true; - $lock = $this->db->locks->findOne(array('resource' => $this->resource_uri)); - - // create lock record if not exists - if (!$lock) { - $this->db->locks->insert(array('resource' => $this->resource_uri, 'created' => time())); - } - // wait if locked (expire locks after 10 minutes) - else if ((time() - $lock['created']) < 600) { - usleep(500000); - return $this->_sync_lock(); - } - // set lock - else { - $lock['created'] = time(); - $this->db->locks->update(array('_id' => $lock['_id']), $lock, array('safe' => true)); - } - } - - /** - * Remove lock for this folder - */ - public function _sync_unlock() - { - if (!$this->ready || !$this->synclock) - return; - - $this->db->locks->remove(array('resource' => $this->resource_uri)); - } - - /** - * Resolve an object UID into an IMAP message UID - * - * @param string Kolab object UID - * @param boolean Include deleted objects - * @return int The resolved IMAP message UID - */ - public function uid2msguid($uid, $deleted = false) - { - if (!isset($this->uid2msg[$uid])) { - // use IMAP SEARCH to get the right message - $index = $this->imap->search_once($this->folder->name, ($deleted ? '' : 'UNDELETED ') . 'HEADER SUBJECT ' . $uid); - $results = $index->get(); - $this->uid2msg[$uid] = $results[0]; - } - - return $this->uid2msg[$uid]; - } - -} diff --git a/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_cache_note.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_cache_note.php deleted file mode 100644 index 8546927..0000000 --- a/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_cache_note.php +++ /dev/null @@ -1,27 +0,0 @@ - - * - * Copyright (C) 2013, Kolab Systems AG - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -class kolab_storage_cache_note extends kolab_storage_cache -{ - -} \ No newline at end of file diff --git a/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_cache_task.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_cache_task.php deleted file mode 100644 index 8b714e6..0000000 --- a/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_cache_task.php +++ /dev/null @@ -1,42 +0,0 @@ - - * - * Copyright (C) 2013, Kolab Systems AG - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -class kolab_storage_cache_task extends kolab_storage_cache -{ - protected $extra_cols = array('dtstart','dtend'); - - /** - * Helper method to convert the given Kolab object into a dataset to be written to cache - * - * @override - */ - protected function _serialize($object) - { - $sql_data = parent::_serialize($object); - - $sql_data['dtstart'] = $this->_convert_datetime($object['start']); - $sql_data['dtend'] = $this->_convert_datetime($object['due']); - - return $sql_data; - } -} diff --git a/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_config.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_config.php deleted file mode 100644 index e843e97..0000000 --- a/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_config.php +++ /dev/null @@ -1,918 +0,0 @@ - - * @author Aleksander Machniak - * - * 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 Affero 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -class kolab_storage_config -{ - const FOLDER_TYPE = 'configuration'; - - - /** - * Singleton instace of kolab_storage_config - * - * @var kolab_storage_config - */ - static protected $instance; - - private $folders; - private $default; - private $enabled; - private $tags; - - - /** - * This implements the 'singleton' design pattern - * - * @return kolab_storage_config The one and only instance - */ - static function get_instance() - { - if (!self::$instance) { - self::$instance = new kolab_storage_config(); - } - - return self::$instance; - } - - /** - * Private constructor (finds default configuration folder as a config source) - */ - private function __construct() - { - // get all configuration folders - $this->folders = kolab_storage::get_folders(self::FOLDER_TYPE, false); - - foreach ($this->folders as $folder) { - if ($folder->default) { - $this->default = $folder; - break; - } - } - - // if no folder is set as default, choose the first one - if (!$this->default) { - $this->default = reset($this->folders); - } - - // attempt to create a default folder if it does not exist - if (!$this->default) { - $folder_name = 'Configuration'; - $folder_type = self::FOLDER_TYPE . '.default'; - - if (kolab_storage::folder_create($folder_name, $folder_type, true)) { - $this->default = new kolab_storage_folder($folder_name, $folder_type); - } - } - - // check if configuration folder exist - if ($this->default && $this->default->name) { - $this->enabled = true; - } - } - - /** - * Check wether any configuration storage (folder) exists - * - * @return bool - */ - public function is_enabled() - { - return $this->enabled; - } - - /** - * Get configuration objects - * - * @param array $filter Search filter - * @param bool $default Enable to get objects only from default folder - * @param int $limit Max. number of records (per-folder) - * - * @return array List of objects - */ - public function get_objects($filter = array(), $default = false, $limit = 0) - { - $list = array(); - - foreach ($this->folders as $folder) { - // we only want to read from default folder - if ($default && !$folder->default) { - continue; - } - - // for better performance it's good to assume max. number of records - if ($limit) { - $folder->set_order_and_limit(null, $limit); - } - - foreach ($folder->select($filter) as $object) { - unset($object['_formatobj']); - $list[] = $object; - } - } - - return $list; - } - - /** - * Get configuration object - * - * @param string $uid Object UID - * @param bool $default Enable to get objects only from default folder - * - * @return array Object data - */ - public function get_object($uid, $default = false) - { - foreach ($this->folders as $folder) { - // we only want to read from default folder - if ($default && !$folder->default) { - continue; - } - - if ($object = $folder->get_object($uid)) { - return $object; - } - } - } - - /** - * Create/update configuration object - * - * @param array $object Object data - * @param string $type Object type - * - * @return bool True on success, False on failure - */ - public function save(&$object, $type) - { - if (!$this->enabled) { - return false; - } - - $folder = $this->find_folder($object); - - if ($type) { - $object['type'] = $type; - } - - $status = $folder->save($object, self::FOLDER_TYPE . '.' . $object['type'], $object['uid']); - - // on success, update cached tags list - if ($status && is_array($this->tags)) { - $found = false; - unset($object['_formatobj']); // we don't need it anymore - - foreach ($this->tags as $idx => $tag) { - if ($tag['uid'] == $object['uid']) { - $found = true; - $this->tags[$idx] = $object; - } - } - - if (!$found) { - $this->tags[] = $object; - } - } - - return !empty($status); - } - - /** - * Remove configuration object - * - * @param string $uid Object UID - * - * @return bool True on success, False on failure - */ - public function delete($uid) - { - if (!$this->enabled) { - return false; - } - - // fetch the object to find folder - $object = $this->get_object($uid); - - if (!$object) { - return false; - } - - $folder = $this->find_folder($object); - $status = $folder->delete($uid); - - // on success, update cached tags list - if ($status && is_array($this->tags)) { - foreach ($this->tags as $idx => $tag) { - if ($tag['uid'] == $uid) { - unset($this->tags[$idx]); - break; - } - } - } - - return $status; - } - - /** - * Find folder - */ - public function find_folder($object = array()) - { - // find folder object - if ($object['_mailbox']) { - foreach ($this->folders as $folder) { - if ($folder->name == $object['_mailbox']) { - break; - } - } - } - else { - $folder = $this->default; - } - - return $folder; - } - - /** - * Builds relation member URI - * - * @param string|array Object UUID or Message folder, UID, Search headers (Message-Id, Date) - * - * @return string $url Member URI - */ - public static function build_member_url($params) - { - // param is object UUID - if (is_string($params) && !empty($params)) { - return 'urn:uuid:' . $params; - } - - if (empty($params) || !strlen($params['folder'])) { - return null; - } - - $rcube = rcube::get_instance(); - $storage = $rcube->get_storage(); - list($username, $domain) = explode('@', $rcube->get_user_name()); - - if (strlen($domain)) { - $domain = '@' . $domain; - } - - // modify folder spec. according to namespace - $folder = $params['folder']; - $ns = $storage->folder_namespace($folder); - - if ($ns == 'shared') { - // Note: this assumes there's only one shared namespace root - if ($ns = $storage->get_namespace('shared')) { - if ($prefix = $ns[0][0]) { - $folder = substr($folder, strlen($prefix)); - } - } - } - else { - if ($ns == 'other') { - // Note: this assumes there's only one other users namespace root - if ($ns = $storage->get_namespace('other')) { - if ($prefix = $ns[0][0]) { - list($otheruser, $path) = explode('/', substr($folder, strlen($prefix)), 2); - $folder = 'user/' . $otheruser . $domain . '/' . $path; - } - } - } - else { - $folder = 'user/' . $username . $domain . '/' . $folder; - } - } - - $folder = implode('/', array_map('rawurlencode', explode('/', $folder))); - - // build URI - $url = 'imap:///' . $folder; - - // UID is optional here because sometimes we want - // to build just a member uri prefix - if ($params['uid']) { - $url .= '/' . $params['uid']; - } - - unset($params['folder']); - unset($params['uid']); - - if (!empty($params)) { - $url .= '?' . http_build_query($params, '', '&'); - } - - return $url; - } - - /** - * Parses relation member string - * - * @param string $url Member URI - * - * @return array Message folder, UID, Search headers (Message-Id, Date) - */ - public static function parse_member_url($url) - { - // Look for IMAP URI: - // imap:///(user/username@domain|shared)//? - if (strpos($url, 'imap:///') === 0) { - $rcube = rcube::get_instance(); - $storage = $rcube->get_storage(); - - // parse_url does not work with imap:/// prefix - $url = parse_url(substr($url, 8)); - $path = explode('/', $url['path']); - parse_str($url['query'], $params); - - $uid = array_pop($path); - $ns = array_shift($path); - $path = array_map('rawurldecode', $path); - - // resolve folder name - if ($ns == 'user') { - $username = array_shift($path); - $folder = implode('/', $path); - - if ($username != $rcube->get_user_name()) { - list($user, $domain) = explode('@', $username); - - // Note: this assumes there's only one other users namespace root - if ($ns = $storage->get_namespace('other')) { - if ($prefix = $ns[0][0]) { - $folder = $prefix . $user . '/' . $folder; - } - } - } - else if (!strlen($folder)) { - $folder = 'INBOX'; - } - } - else { - $folder = $ns . '/' . implode('/', $path); - // Note: this assumes there's only one shared namespace root - if ($ns = $storage->get_namespace('shared')) { - if ($prefix = $ns[0][0]) { - $folder = $prefix . $folder; - } - } - } - - return array( - 'folder' => $folder, - 'uid' => $uid, - 'params' => $params, - ); - } - - return false; - } - - /** - * Build array of member URIs from set of messages - * - * @param string $folder Folder name - * @param array $messages Array of rcube_message objects - * - * @return array List of members (IMAP URIs) - */ - public static function build_members($folder, $messages) - { - $members = array(); - - foreach ((array) $messages as $msg) { - $params = array( - 'folder' => $folder, - 'uid' => $msg->uid, - ); - - // add search parameters: - // we don't want to build "invalid" searches e.g. that - // will return false positives (more or wrong messages) - if (($messageid = $msg->get('message-id', false)) && ($date = $msg->get('date', false))) { - $params['message-id'] = $messageid; - $params['date'] = $date; - - if ($subject = $msg->get('subject', false)) { - $params['subject'] = substr($subject, 0, 256); - } - } - - $members[] = self::build_member_url($params); - } - - return $members; - } - - /** - * Resolve/validate/update members (which are IMAP URIs) of relation object. - * - * @param array $tag Tag object - * @param bool $force Force members list update - * - * @return array Folder/UIDs list - */ - public static function resolve_members(&$tag, $force = true) - { - $result = array(); - - foreach ((array) $tag['members'] as $member) { - // IMAP URI members - if ($url = self::parse_member_url($member)) { - $folder = $url['folder']; - - if (!$force) { - $result[$folder][] = $url['uid']; - } - else { - $result[$folder]['uid'][] = $url['uid']; - $result[$folder]['params'][] = $url['params']; - $result[$folder]['member'][] = $member; - } - } - } - - if (empty($result) || !$force) { - return $result; - } - - $rcube = rcube::get_instance(); - $storage = $rcube->get_storage(); - $search = array(); - $missing = array(); - - // first we search messages by Folder+UID - foreach ($result as $folder => $data) { - // @FIXME: maybe better use index() which is cached? - // @TODO: consider skip_deleted option - $index = $storage->search_once($folder, 'UID ' . rcube_imap_generic::compressMessageSet($data['uid'])); - $uids = $index->get(); - - // messages that were not found need to be searched by search parameters - $not_found = array_diff($data['uid'], $uids); - if (!empty($not_found)) { - foreach ($not_found as $uid) { - $idx = array_search($uid, $data['uid']); - - if ($p = $data['params'][$idx]) { - $search[] = $p; - } - - $missing[] = $result[$folder]['member'][$idx]; - - unset($result[$folder]['uid'][$idx]); - unset($result[$folder]['params'][$idx]); - unset($result[$folder]['member'][$idx]); - } - } - - $result[$folder] = $uids; - } - - // search in all subscribed mail folders using search parameters - if (!empty($search)) { - // remove not found members from the members list - $tag['members'] = array_diff($tag['members'], $missing); - - // get subscribed folders - $folders = $storage->list_folders_subscribed('', '*', 'mail', null, true); - - // @TODO: do this search in chunks (for e.g. 10 messages)? - $search_str = ''; - - foreach ($search as $p) { - $search_params = array(); - foreach ($p as $key => $val) { - $key = strtoupper($key); - // don't search by subject, we don't want false-positives - if ($key != 'SUBJECT') { - $search_params[] = 'HEADER ' . $key . ' ' . rcube_imap_generic::escape($val); - } - } - - $search_str .= ' (' . implode(' ', $search_params) . ')'; - } - - $search_str = trim(str_repeat(' OR', count($search)-1) . $search_str); - - // search - $search = $storage->search_once($folders, $search_str); - - // handle search result - $folders = (array) $search->get_parameters('MAILBOX'); - - foreach ($folders as $folder) { - $set = $search->get_set($folder); - $uids = $set->get(); - - if (!empty($uids)) { - $msgs = $storage->fetch_headers($folder, $uids, false); - $members = self::build_members($folder, $msgs); - - // merge new members into the tag members list - $tag['members'] = array_merge($tag['members'], $members); - - // add UIDs into the result - $result[$folder] = array_unique(array_merge((array)$result[$folder], $uids)); - } - } - - // update tag object with new members list - $tag['members'] = array_unique($tag['members']); - kolab_storage_config::get_instance()->save($tag, 'relation', false); - } - - return $result; - } - - /** - * Assign tags to kolab objects - * - * @param array $records List of kolab objects - * - * @return array List of tags - */ - public function apply_tags(&$records) - { - // first convert categories into tags - foreach ($records as $i => $rec) { - if (!empty($rec['categories'])) { - $folder = new kolab_storage_folder($rec['_mailbox']); - if ($object = $folder->get_object($rec['uid'])) { - $tags = $rec['categories']; - - unset($object['categories']); - unset($records[$i]['categories']); - - $this->save_tags($rec['uid'], $tags); - $folder->save($object, $rec['_type'], $rec['uid']); - } - } - } - - $tags = array(); - - // assign tags to objects - foreach ($this->get_tags() as $tag) { - foreach ($records as $idx => $rec) { - $uid = self::build_member_url($rec['uid']); - if (in_array($uid, (array) $tag['members'])) { - $records[$idx]['tags'][] = $tag['name']; - } - } - - $tags[] = $tag['name']; - } - - $tags = array_unique($tags); - - return $tags; - } - - /** - * Update object tags - * - * @param string $uid Kolab object UID - * @param array $tags List of tag names - */ - public function save_tags($uid, $tags) - { - $url = self::build_member_url($uid); - $relations = $this->get_tags(); - - foreach ($relations as $idx => $relation) { - $selected = !empty($tags) && in_array($relation['name'], $tags); - $found = !empty($relation['members']) && in_array($url, $relation['members']); - $update = false; - - // remove member from the relation - if ($found && !$selected) { - $relation['members'] = array_diff($relation['members'], (array) $url); - $update = true; - } - // add member to the relation - else if (!$found && $selected) { - $relation['members'][] = $url; - $update = true; - } - - if ($update) { - if ($this->save($relation, 'relation')) { - $this->tags[$idx] = $relation; // update in-memory cache - } - } - - if ($selected) { - $tags = array_diff($tags, (array)$relation['name']); - } - } - - // create new relations - if (!empty($tags)) { - foreach ($tags as $tag) { - $relation = array( - 'name' => $tag, - 'members' => (array) $url, - 'category' => 'tag', - ); - - if ($this->save($relation, 'relation')) { - $this->tags[] = $relation; // update in-memory cache - } - } - } - } - - /** - * Get tags (all or referring to specified object) - * - * @param string $member Optional object UID or mail message-id - * @param int $limit Max. number of records (per-folder) - * Used when searching by member - * - * @return array List of Relation objects - */ - public function get_tags($member = '*', $limit = 0) - { - if (!isset($this->tags)) { - $default = true; - $filter = array( - array('type', '=', 'relation'), - array('category', '=', 'tag') - ); - - // use faster method - if ($member && $member != '*') { - $filter[] = array('member', '=', $member); - $tags = $this->get_objects($filter, $default, $limit); - } - else { - $this->tags = $tags = $this->get_objects($filter, $default); - } - } - else { - $tags = $this->tags; - } - - if ($member === '*') { - return $tags; - } - - $result = array(); - - if ($member[0] == '<') { - $search_msg = urlencode($member); - } - else { - $search_uid = self::build_member_url($member); - } - - foreach ($tags as $tag) { - if ($search_uid && in_array($search_uid, (array) $tag['members'])) { - $result[] = $tag; - } - else if ($search_msg) { - foreach ($tag['members'] as $m) { - if (strpos($m, $search_msg) !== false) { - $result[] = $tag; - break; - } - } - } - } - - return $result; - } - - /** - * Find objects linked with the given groupware object through a relation - * - * @param string Object UUID - * @param array List of related URIs - */ - public function get_object_links($uid) - { - $links = array(); - $object_uri = self::build_member_url($uid); - - foreach ($this->get_relations_for_member($uid) as $relation) { - if (in_array($object_uri, (array) $relation['members'])) { - // make relation members up-to-date - kolab_storage_config::resolve_members($relation); - - foreach ($relation['members'] as $member) { - if ($member != $object_uri) { - $links[] = $member; - } - } - } - } - - return array_unique($links); - } - - /** - * - */ - public function save_object_links($uid, $links, $remove = array()) - { - $object_uri = self::build_member_url($uid); - $relations = $this->get_relations_for_member($uid); - $done = false; - - foreach ($relations as $relation) { - // make relation members up-to-date - kolab_storage_config::resolve_members($relation); - - // remove and add links - $members = array_diff($relation['members'], (array)$remove); - $members = array_unique(array_merge($members, $links)); - - // make sure the object_uri is still a member - if (!in_array($object_uri, $members)) { - $members[$object_uri]; - } - - // remove relation if no other members remain - if (count($members) <= 1) { - $done = $this->delete($relation['uid']); - } - // update relation object if members changed - else if (count(array_diff($members, $relation['members'])) || count(array_diff($relation['members'], $members))) { - $relation['members'] = $members; - $done = $this->save($relation, 'relation'); - $links = array(); - } - // no changes, we're happy - else { - $done = true; - $links = array(); - } - } - - // create a new relation - if (!$done && !empty($links)) { - $relation = array( - 'members' => array_merge($links, array($object_uri)), - 'category' => 'generic', - ); - - $ret = $this->save($relation, 'relation'); - } - - return $ret; - } - - /** - * Find relation objects referring to specified note - */ - public function get_relations_for_member($uid, $reltype = 'generic') - { - $default = true; - $filter = array( - array('type', '=', 'relation'), - array('category', '=', $reltype), - array('member', '=', $uid), - ); - - return $this->get_objects($filter, $default, 100); - } - - /** - * Find kolab objects assigned to specified e-mail message - * - * @param rcube_message $message E-mail message - * @param string $folder Folder name - * @param string $type Result objects type - * - * @return array List of kolab objects - */ - public function get_message_relations($message, $folder, $type) - { - static $_cache = array(); - - $result = array(); - $uids = array(); - $default = true; - $uri = self::get_message_uri($message, $folder); - $filter = array( - array('type', '=', 'relation'), - array('category', '=', 'generic'), - ); - - // query by message-id - $member_id = $message->get('message-id', false); - if (empty($member_id)) { - // derive message identifier from URI - $member_id = md5($uri); - } - $filter[] = array('member', '=', $member_id); - - if (!isset($_cache[$uri])) { - // get UIDs of related groupware objects - foreach ($this->get_objects($filter, $default) as $relation) { - // we don't need to update members if the URI is found - if (!in_array($uri, $relation['members'])) { - // update members... - $messages = kolab_storage_config::resolve_members($relation); - // ...and check again - if (empty($messages[$folder]) || !in_array($message->uid, $messages[$folder])) { - continue; - } - } - - // find groupware object UID(s) - foreach ($relation['members'] as $member) { - if (strpos($member, 'urn:uuid:') === 0) { - $uids[] = substr($member, 9); - } - } - } - - // remember this lookup - $_cache[$uri] = $uids; - } - else { - $uids = $_cache[$uri]; - } - - // get kolab objects of specified type - if (!empty($uids)) { - $query = array(array('uid', '=', array_unique($uids))); - $result = kolab_storage::select($query, $type); - } - - return $result; - } - - /** - * Build a URI representing the given message reference - */ - public static function get_message_uri($headers, $folder) - { - $params = array( - 'folder' => $headers->folder ?: $folder, - 'uid' => $headers->uid, - ); - - if (($messageid = $headers->get('message-id', false)) && ($date = $headers->get('date', false))) { - $params['message-id'] = $messageid; - $params['date'] = $date; - - if ($subject = $headers->get('subject')) { - $params['subject'] = $subject; - } - } - - return self::build_member_url($params); - } - - /** - * Resolve the email message reference from the given URI - */ - public function get_message_reference($uri, $rel = null) - { - if ($linkref = self::parse_member_url($uri)) { - $linkref['subject'] = $linkref['params']['subject']; - $linkref['uri'] = $uri; - - $rcmail = rcube::get_instance(); - if (method_exists($rcmail, 'url')) { - $linkref['mailurl'] = $rcmail->url(array( - 'task' => 'mail', - 'action' => 'show', - 'mbox' => $linkref['folder'], - 'uid' => $linkref['uid'], - 'rel' => $rel, - )); - } - - unset($linkref['params']); - } - - return $linkref; - } -} diff --git a/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_dataset.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_dataset.php deleted file mode 100644 index 9ddf3f9..0000000 --- a/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_dataset.php +++ /dev/null @@ -1,154 +0,0 @@ - - * - * Copyright (C) 2014, Kolab Systems AG - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -class kolab_storage_dataset implements Iterator, ArrayAccess, Countable -{ - private $cache; // kolab_storage_cache instance to use for fetching data - private $memlimit = 0; - private $buffer = false; - private $index = array(); - private $data = array(); - private $iteratorkey = 0; - private $error = null; - - /** - * Default constructor - * - * @param object kolab_storage_cache instance to be used for fetching objects upon access - */ - public function __construct($cache) - { - $this->cache = $cache; - - // enable in-memory buffering up until 1/5 of the available memory - if (function_exists('memory_get_usage')) { - $this->memlimit = parse_bytes(ini_get('memory_limit')) / 5; - $this->buffer = true; - } - } - - /** - * Return error state - */ - public function is_error() - { - return !empty($this->error); - } - - /** - * Set error state - */ - public function set_error($err) - { - $this->error = $err; - } - - - /*** Implement PHP Countable interface ***/ - - public function count() - { - return count($this->index); - } - - - /*** Implement PHP ArrayAccess interface ***/ - - public function offsetSet($offset, $value) - { - $uid = $value['_msguid']; - - if (is_null($offset)) { - $offset = count($this->index); - $this->index[] = $uid; - } - else { - $this->index[$offset] = $uid; - } - - // keep full payload data in memory if possible - if ($this->memlimit && $this->buffer && isset($value['_mailbox'])) { - $this->data[$offset] = $value; - - // check memory usage and stop buffering - if ($offset % 10 == 0) { - $this->buffer = memory_get_usage() < $this->memlimit; - } - } - } - - public function offsetExists($offset) - { - return isset($this->index[$offset]); - } - - public function offsetUnset($offset) - { - unset($this->index[$offset]); - } - - public function offsetGet($offset) - { - if (isset($this->data[$offset])) { - return $this->data[$offset]; - } - else if ($msguid = $this->index[$offset]) { - return $this->cache->get($msguid); - } - - return null; - } - - - /*** Implement PHP Iterator interface ***/ - - public function current() - { - return $this->offsetGet($this->iteratorkey); - } - - public function key() - { - return $this->iteratorkey; - } - - public function next() - { - $this->iteratorkey++; - return $this->valid(); - } - - public function rewind() - { - $this->iteratorkey = 0; - } - - public function valid() - { - return !empty($this->index[$this->iteratorkey]); - } - -} diff --git a/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_folder.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_folder.php deleted file mode 100644 index 8d86d70..0000000 --- a/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_folder.php +++ /dev/null @@ -1,1173 +0,0 @@ - - * @author Aleksander Machniak - * - * Copyright (C) 2012-2013, Kolab Systems AG - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -class kolab_storage_folder extends kolab_storage_folder_api -{ - /** - * The kolab_storage_cache instance for caching operations - * @var object - */ - public $cache; - - /** - * Indicate validity status - * @var boolean - */ - public $valid = false; - - protected $error = 0; - - protected $resource_uri; - - - /** - * Default constructor - * - * @param string The folder name/path - * @param string Expected folder type - */ - function __construct($name, $type = null, $type_annotation = null) - { - parent::__construct($name); - $this->imap->set_options(array('skip_deleted' => true)); - $this->set_folder($name, $type, $type_annotation); - } - - - /** - * Set the IMAP folder this instance connects to - * - * @param string The folder name/path - * @param string Expected folder type - * @param string Optional folder type if known - */ - public function set_folder($name, $type = null, $type_annotation = null) - { - if (empty($type_annotation)) { - $type_annotation = kolab_storage::folder_type($name); - } - - $oldtype = $this->type; - list($this->type, $suffix) = explode('.', $type_annotation); - $this->default = $suffix == 'default'; - $this->subtype = $this->default ? '' : $suffix; - $this->name = $name; - $this->id = kolab_storage::folder_id($name); - $this->valid = !empty($this->type) && $this->type != 'mail' && (!$type || $this->type == $type); - - if (!$this->valid) { - $this->error = $this->imap->get_error_code() < 0 ? kolab_storage::ERROR_IMAP_CONN : kolab_storage::ERROR_INVALID_FOLDER; - } - - // reset cached object properties - $this->owner = $this->namespace = $this->resource_uri = $this->info = $this->idata = null; - - // get a new cache instance if folder type changed - if (!$this->cache || $this->type != $oldtype) - $this->cache = kolab_storage_cache::factory($this); - else - $this->cache->set_folder($this); - - $this->imap->set_folder($this->name); - } - - /** - * Returns code of last error - * - * @return int Error code - */ - public function get_error() - { - return $this->error ?: $this->cache->get_error(); - } - - /** - * Check IMAP connection error state - */ - public function check_error() - { - if (($err_code = $this->imap->get_error_code()) < 0) { - $this->error = kolab_storage::ERROR_IMAP_CONN; - if (($res_code = $this->imap->get_response_code()) !== 0 && in_array($res_code, array(rcube_storage::NOPERM, rcube_storage::READONLY))) { - $this->error = kolab_storage::ERROR_NO_PERMISSION; - } - } - - return $this->error; - } - - /** - * Compose a unique resource URI for this IMAP folder - */ - public function get_resource_uri() - { - if (!empty($this->resource_uri)) { - return $this->resource_uri; - } - - // strip namespace prefix from folder name - $ns = $this->get_namespace(); - $nsdata = $this->imap->get_namespace($ns); - - if (is_array($nsdata[0]) && strlen($nsdata[0][0]) && strpos($this->name, $nsdata[0][0]) === 0) { - $subpath = substr($this->name, strlen($nsdata[0][0])); - if ($ns == 'other') { - list($user, $suffix) = explode($nsdata[0][1], $subpath, 2); - $subpath = $suffix; - } - } - else { - $subpath = $this->name; - } - - // compose fully qualified ressource uri for this instance - $this->resource_uri = 'imap://' . urlencode($this->get_owner(true)) . '@' . $this->imap->options['host'] . '/' . $subpath; - return $this->resource_uri; - } - - /** - * Helper method to extract folder UID metadata - * - * @return string Folder's UID - */ - public function get_uid() - { - // UID is defined in folder METADATA - $metakeys = array(kolab_storage::UID_KEY_SHARED, kolab_storage::UID_KEY_CYRUS); - $metadata = $this->get_metadata($metakeys); - - if ($metadata !== null) { - foreach ($metakeys as $key) { - if ($uid = $metadata[$key]) { - return $uid; - } - } - - // generate a folder UID and set it to IMAP - $uid = rtrim(chunk_split(md5($this->name . $this->get_owner() . uniqid('-', true)), 12, '-'), '-'); - if ($this->set_uid($uid)) { - return $uid; - } - } - - $this->check_error(); - - // create hash from folder name if we can't write the UID metadata - return md5($this->name . $this->get_owner()); - } - - /** - * Helper method to set an UID value to the given IMAP folder instance - * - * @param string Folder's UID - * @return boolean True on succes, False on failure - */ - public function set_uid($uid) - { - $success = $this->set_metadata(array(kolab_storage::UID_KEY_SHARED => $uid)); - - $this->check_error(); - return $success; - } - - /** - * Compose a folder Etag identifier - */ - public function get_ctag() - { - $fdata = $this->get_imap_data(); - $this->check_error(); - return sprintf('%d-%d-%d', $fdata['UIDVALIDITY'], $fdata['HIGHESTMODSEQ'], $fdata['UIDNEXT']); - } - - /** - * Check activation status of this folder - * - * @return boolean True if enabled, false if not - */ - public function is_active() - { - return kolab_storage::folder_is_active($this->name); - } - - /** - * Change activation status of this folder - * - * @param boolean The desired subscription status: true = active, false = not active - * - * @return True on success, false on error - */ - public function activate($active) - { - return $active ? kolab_storage::folder_activate($this->name) : kolab_storage::folder_deactivate($this->name); - } - - /** - * Check subscription status of this folder - * - * @return boolean True if subscribed, false if not - */ - public function is_subscribed() - { - return kolab_storage::folder_is_subscribed($this->name); - } - - /** - * Change subscription status of this folder - * - * @param boolean The desired subscription status: true = subscribed, false = not subscribed - * - * @return True on success, false on error - */ - public function subscribe($subscribed) - { - return $subscribed ? kolab_storage::folder_subscribe($this->name) : kolab_storage::folder_unsubscribe($this->name); - } - - /** - * Get number of objects stored in this folder - * - * @param mixed Pseudo-SQL query as list of filter parameter triplets - * or string with object type (e.g. contact, event, todo, journal, note, configuration) - * @return integer The number of objects of the given type - * @see self::select() - */ - public function count($query = null) - { - if (!$this->valid) { - return 0; - } - - // synchronize cache first - $this->cache->synchronize(); - - return $this->cache->count($this->_prepare_query($query)); - } - - - /** - * List all Kolab objects of the given type - * - * @param string $type Object type (e.g. contact, event, todo, journal, note, configuration) - * @return array List of Kolab data objects (each represented as hash array) - */ - public function get_objects($type = null) - { - if (!$type) $type = $this->type; - - if (!$this->valid) { - return array(); - } - - // synchronize caches - $this->cache->synchronize(); - - // fetch objects from cache - return $this->cache->select($this->_prepare_query($type)); - } - - - /** - * Select *some* Kolab objects matching the given query - * - * @param array Pseudo-SQL query as list of filter parameter triplets - * triplet: array('', '', '') - * @return array List of Kolab data objects (each represented as hash array) - */ - public function select($query = array()) - { - if (!$this->valid) { - return array(); - } - - // check query argument - if (empty($query)) { - return $this->get_objects(); - } - - // synchronize caches - $this->cache->synchronize(); - - // fetch objects from cache - return $this->cache->select($this->_prepare_query($query)); - } - - - /** - * Getter for object UIDs only - * - * @param array Pseudo-SQL query as list of filter parameter triplets - * @return array List of Kolab object UIDs - */ - public function get_uids($query = array()) - { - if (!$this->valid) { - return array(); - } - - // synchronize caches - $this->cache->synchronize(); - - // fetch UIDs from cache - return $this->cache->select($this->_prepare_query($query), true); - } - - /** - * Setter for ORDER BY and LIMIT parameters for cache queries - * - * @param array List of columns to order by - * @param integer Limit result set to this length - * @param integer Offset row - */ - public function set_order_and_limit($sortcols, $length = null, $offset = 0) - { - $this->cache->set_order_by($sortcols); - - if ($length !== null) { - $this->cache->set_limit($length, $offset); - } - } - - /** - * Helper method to sanitize query arguments - */ - private function _prepare_query($query) - { - // string equals type query - // FIXME: should not be called this way! - if (is_string($query)) { - return $this->cache->has_type_col() && !empty($query) ? array(array('type','=',$query)) : array(); - } - - foreach ((array)$query as $i => $param) { - if ($param[0] == 'type' && !$this->cache->has_type_col()) { - unset($query[$i]); - } - else if (($param[0] == 'dtstart' || $param[0] == 'dtend' || $param[0] == 'changed')) { - if (is_object($param[2]) && is_a($param[2], 'DateTime')) - $param[2] = $param[2]->format('U'); - if (is_numeric($param[2])) - $query[$i][2] = date('Y-m-d H:i:s', $param[2]); - } - } - - return $query; - } - - - /** - * Getter for a single Kolab object, identified by its UID - * - * @param string $uid Object UID - * @param string $type Object type (e.g. contact, event, todo, journal, note, configuration) - * Defaults to folder type - * - * @return array The Kolab object represented as hash array - */ - public function get_object($uid, $type = null) - { - if (!$this->valid) { - return false; - } - - // synchronize caches - $this->cache->synchronize(); - - $msguid = $this->cache->uid2msguid($uid); - - if ($msguid && ($object = $this->cache->get($msguid, $type))) { - return $object; - } - - return false; - } - - - /** - * Fetch a Kolab object attachment which is stored in a separate part - * of the mail MIME message that represents the Kolab record. - * - * @param string Object's UID - * @param string The attachment's mime number - * @param string IMAP folder where message is stored; - * If set, that also implies that the given UID is an IMAP UID - * @param bool True to print the part content - * @param resource File pointer to save the message part - * @param boolean Disables charset conversion - * - * @return mixed The attachment content as binary string - */ - public function get_attachment($uid, $part, $mailbox = null, $print = false, $fp = null, $skip_charset_conv = false) - { - if ($this->valid && ($msguid = ($mailbox ? $uid : $this->cache->uid2msguid($uid)))) { - $this->imap->set_folder($mailbox ? $mailbox : $this->name); - - if (substr($part, 0, 2) == 'i:') { - // attachment data is stored in XML - if ($object = $this->cache->get($msguid)) { - // load data from XML (attachment content is not stored in cache) - if ($object['_formatobj'] && isset($object['_size'])) { - $object['_attachments'] = array(); - $object['_formatobj']->get_attachments($object); - } - - foreach ($object['_attachments'] as $attach) { - if ($attach['id'] == $part) { - if ($print) echo $attach['content']; - else if ($fp) fwrite($fp, $attach['content']); - else return $attach['content']; - return true; - } - } - } - } - else { - // return message part from IMAP directly - return $this->imap->get_message_part($msguid, $part, null, $print, $fp, $skip_charset_conv); - } - } - - return null; - } - - - /** - * Fetch the mime message from the storage server and extract - * the Kolab groupware object from it - * - * @param string The IMAP message UID to fetch - * @param string The object type expected (use wildcard '*' to accept all types) - * @param string The folder name where the message is stored - * - * @return mixed Hash array representing the Kolab object, a kolab_format instance or false if not found - */ - public function read_object($msguid, $type = null, $folder = null) - { - if (!$this->valid) { - return false; - } - - if (!$type) $type = $this->type; - if (!$folder) $folder = $this->name; - - $this->imap->set_folder($folder); - - $this->cache->bypass(true); - $message = new rcube_message($msguid); - $this->cache->bypass(false); - - // Message doesn't exist? - if (empty($message->headers)) { - return false; - } - - // extract the X-Kolab-Type header from the XML attachment part if missing - if (empty($message->headers->others['x-kolab-type'])) { - foreach ((array)$message->attachments as $part) { - if (strpos($part->mimetype, kolab_format::KTYPE_PREFIX) === 0) { - $message->headers->others['x-kolab-type'] = $part->mimetype; - break; - } - } - } - // fix buggy messages stating the X-Kolab-Type header twice - else if (is_array($message->headers->others['x-kolab-type'])) { - $message->headers->others['x-kolab-type'] = reset($message->headers->others['x-kolab-type']); - } - - // no object type header found: abort - if (empty($message->headers->others['x-kolab-type'])) { - rcube::raise_error(array( - 'code' => 600, - 'type' => 'php', - 'file' => __FILE__, - 'line' => __LINE__, - 'message' => "No X-Kolab-Type information found in message $msguid ($this->name).", - ), true); - return false; - } - - $object_type = kolab_format::mime2object_type($message->headers->others['x-kolab-type']); - $content_type = kolab_format::KTYPE_PREFIX . $object_type; - - // check object type header and abort on mismatch - if ($type != '*' && $object_type != $type) - return false; - - $attachments = array(); - - // get XML part - foreach ((array)$message->attachments as $part) { - if (!$xml && ($part->mimetype == $content_type || preg_match('!application/([a-z.]+\+)?xml!', $part->mimetype))) { - $xml = $message->get_part_body($part->mime_id, true); - } - else if ($part->filename || $part->content_id) { - $key = $part->content_id ? trim($part->content_id, '<>') : $part->filename; - $size = null; - - // Use Content-Disposition 'size' as for the Kolab Format spec. - if (isset($part->d_parameters['size'])) { - $size = $part->d_parameters['size']; - } - // we can trust part size only if it's not encoded - else if ($part->encoding == 'binary' || $part->encoding == '7bit' || $part->encoding == '8bit') { - $size = $part->size; - } - - $attachments[$key] = array( - 'id' => $part->mime_id, - 'name' => $part->filename, - 'mimetype' => $part->mimetype, - 'size' => $size, - ); - } - } - - if (!$xml) { - rcube::raise_error(array( - 'code' => 600, - 'type' => 'php', - 'file' => __FILE__, - 'line' => __LINE__, - 'message' => "Could not find Kolab data part in message $msguid ($this->name).", - ), true); - return false; - } - - // check kolab format version - $format_version = $message->headers->others['x-kolab-mime-version']; - if (empty($format_version)) { - list($xmltype, $subtype) = explode('.', $object_type); - $xmlhead = substr($xml, 0, 512); - - // detect old Kolab 2.0 format - if (strpos($xmlhead, '<' . $xmltype) !== false && strpos($xmlhead, 'xmlns=') === false) - $format_version = '2.0'; - else - $format_version = '3.0'; // assume 3.0 - } - - // get Kolab format handler for the given type - $format = kolab_format::factory($object_type, $format_version); - - if (is_a($format, 'PEAR_Error')) - return false; - - // load Kolab object from XML part - $format->load($xml); - - if ($format->is_valid()) { - $object = $format->to_array(array('_attachments' => $attachments)); - $object['_type'] = $object_type; - $object['_msguid'] = $msguid; - $object['_mailbox'] = $this->name; - $object['_formatobj'] = $format; - - return $object; - } - else { - // try to extract object UID from XML block - if (preg_match('!(.+)!Uims', $xml, $m)) - $msgadd = " UID = " . trim(strip_tags($m[1])); - - rcube::raise_error(array( - 'code' => 600, - 'type' => 'php', - 'file' => __FILE__, - 'line' => __LINE__, - 'message' => "Could not parse Kolab object data in message $msguid ($this->name)." . $msgadd, - ), true); - } - - return false; - } - - /** - * Save an object in this folder. - * - * @param array $object The array that holds the data of the object. - * @param string $type The type of the kolab object. - * @param string $uid The UID of the old object if it existed before - * - * @return mixed False on error or IMAP message UID on success - */ - public function save(&$object, $type = null, $uid = null) - { - if (!$this->valid) { - return false; - } - - if (!$type) - $type = $this->type; - - // copy attachments from old message - $copyfrom = $object['_copyfrom'] ?: $object['_msguid']; - if (!empty($copyfrom) && ($old = $this->cache->get($copyfrom, $type, $object['_mailbox']))) { - foreach ((array)$old['_attachments'] as $key => $att) { - if (!isset($object['_attachments'][$key])) { - $object['_attachments'][$key] = $old['_attachments'][$key]; - } - // unset deleted attachment entries - if ($object['_attachments'][$key] == false) { - unset($object['_attachments'][$key]); - } - // load photo.attachment from old Kolab2 format to be directly embedded in xcard block - else if ($type == 'contact' && ($key == 'photo.attachment' || $key == 'kolab-picture.png') && $att['id']) { - if (!isset($object['photo'])) - $object['photo'] = $this->get_attachment($copyfrom, $att['id'], $object['_mailbox']); - unset($object['_attachments'][$key]); - } - } - } - - // save contact photo to attachment for Kolab2 format - if (kolab_storage::$version == '2.0' && $object['photo']) { - $attkey = 'kolab-picture.png'; // this file name is hard-coded in libkolab/kolabformatV2/contact.cpp - $object['_attachments'][$attkey] = array( - 'mimetype'=> rcube_mime::image_content_type($object['photo']), - 'content' => preg_match('![^a-z0-9/=+-]!i', $object['photo']) ? $object['photo'] : base64_decode($object['photo']), - ); - } - - // process attachments - if (is_array($object['_attachments'])) { - $numatt = count($object['_attachments']); - foreach ($object['_attachments'] as $key => $attachment) { - // FIXME: kolab_storage and Roundcube attachment hooks use different fields! - if (empty($attachment['content']) && !empty($attachment['data'])) { - $attachment['content'] = $attachment['data']; - unset($attachment['data'], $object['_attachments'][$key]['data']); - } - - // make sure size is set, so object saved in cache contains this info - if (!isset($attachment['size'])) { - if (!empty($attachment['content'])) { - if (is_resource($attachment['content'])) { - // this need to be a seekable resource, otherwise - // fstat() failes and we're unable to determine size - // here nor in rcube_imap_generic before IMAP APPEND - $stat = fstat($attachment['content']); - $attachment['size'] = $stat ? $stat['size'] : 0; - } - else { - $attachment['size'] = strlen($attachment['content']); - } - } - else if (!empty($attachment['path'])) { - $attachment['size'] = filesize($attachment['path']); - } - $object['_attachments'][$key] = $attachment; - } - - // generate unique keys (used as content-id) for attachments - if (is_numeric($key) && $key < $numatt) { - // derrive content-id from attachment file name - $ext = preg_match('/(\.[a-z0-9]{1,6})$/i', $attachment['name'], $m) ? $m[1] : null; - $basename = preg_replace('/[^a-z0-9_.-]/i', '', basename($attachment['name'], $ext)); // to 7bit ascii - if (!$basename) $basename = 'noname'; - $cid = $basename . '.' . microtime(true) . $key . $ext; - - $object['_attachments'][$cid] = $attachment; - unset($object['_attachments'][$key]); - } - } - } - - // save recurrence exceptions as individual objects due to lack of support in Kolab v2 format - if (kolab_storage::$version == '2.0' && $object['recurrence']['EXCEPTIONS']) { - $this->save_recurrence_exceptions($object, $type); - } - - // check IMAP BINARY extension support for 'file' objects - // allow configuration to workaround bug in Cyrus < 2.4.17 - $rcmail = rcube::get_instance(); - $binary = $type == 'file' && !$rcmail->config->get('kolab_binary_disable') && $this->imap->get_capability('BINARY'); - - // generate and save object message - if ($raw_msg = $this->build_message($object, $type, $binary, $body_file)) { - // resolve old msguid before saving - if ($uid && empty($object['_msguid']) && ($msguid = $this->cache->uid2msguid($uid))) { - $object['_msguid'] = $msguid; - $object['_mailbox'] = $this->name; - } - - $result = $this->imap->save_message($this->name, $raw_msg, null, false, null, null, $binary); - - // update cache with new UID - if ($result) { - $old_uid = $object['_msguid']; - - $object['_msguid'] = $result; - $object['_mailbox'] = $this->name; - - if ($old_uid) { - // delete old message - $this->cache->bypass(true); - $this->imap->delete_message($old_uid, $object['_mailbox']); - $this->cache->bypass(false); - } - - // insert/update message in cache - $this->cache->save($result, $object, $old_uid); - } - - // remove temp file - if ($body_file) { - @unlink($body_file); - } - } - - return $result; - } - - /** - * Save recurrence exceptions as individual objects. - * The Kolab v2 format doesn't allow us to save fully embedded exception objects. - * - * @param array Hash array with event properties - * @param string Object type - */ - private function save_recurrence_exceptions(&$object, $type = null) - { - if ($object['recurrence']['EXCEPTIONS']) { - $exdates = array(); - foreach ((array)$object['recurrence']['EXDATE'] as $exdate) { - $key = is_a($exdate, 'DateTime') ? $exdate->format('Y-m-d') : strval($exdate); - $exdates[$key] = 1; - } - - // save every exception as individual object - foreach((array)$object['recurrence']['EXCEPTIONS'] as $exception) { - $exception['uid'] = self::recurrence_exception_uid($object['uid'], $exception['start']->format('Ymd')); - $exception['sequence'] = $object['sequence'] + 1; - - if ($exception['thisandfuture']) { - $exception['recurrence'] = $object['recurrence']; - - // adjust the recurrence duration of the exception - if ($object['recurrence']['COUNT']) { - $recurrence = new kolab_date_recurrence($object['_formatobj']); - if ($end = $recurrence->end()) { - unset($exception['recurrence']['COUNT']); - $exception['recurrence']['UNTIL'] = $end; - } - } - - // set UNTIL date if we have a thisandfuture exception - $untildate = clone $exception['start']; - $untildate->sub(new DateInterval('P1D')); - $object['recurrence']['UNTIL'] = $untildate; - unset($object['recurrence']['COUNT']); - } - else { - if (!$exdates[$exception['start']->format('Y-m-d')]) - $object['recurrence']['EXDATE'][] = clone $exception['start']; - unset($exception['recurrence']); - } - - unset($exception['recurrence']['EXCEPTIONS'], $exception['_formatobj'], $exception['_msguid']); - $this->save($exception, $type, $exception['uid']); - } - - unset($object['recurrence']['EXCEPTIONS']); - } - } - - /** - * Generate an object UID with the given recurrence-ID in a way that it is - * unique (the original UID is not a substring) but still recoverable. - */ - private static function recurrence_exception_uid($uid, $recurrence_id) - { - $offset = -2; - return substr($uid, 0, $offset) . '-' . $recurrence_id . '-' . substr($uid, $offset); - } - - /** - * Delete the specified object from this folder. - * - * @param mixed $object The Kolab object to delete or object UID - * @param boolean $expunge Should the folder be expunged? - * - * @return boolean True if successful, false on error - */ - public function delete($object, $expunge = true) - { - if (!$this->valid) { - return false; - } - - $msguid = is_array($object) ? $object['_msguid'] : $this->cache->uid2msguid($object); - $success = false; - - $this->cache->bypass(true); - - if ($msguid && $expunge) { - $success = $this->imap->delete_message($msguid, $this->name); - } - else if ($msguid) { - $success = $this->imap->set_flag($msguid, 'DELETED', $this->name); - } - - $this->cache->bypass(false); - - if ($success) { - $this->cache->set($msguid, false); - } - - return $success; - } - - - /** - * - */ - public function delete_all() - { - if (!$this->valid) { - return false; - } - - $this->cache->purge(); - $this->cache->bypass(true); - $result = $this->imap->clear_folder($this->name); - $this->cache->bypass(false); - - return $result; - } - - - /** - * Restore a previously deleted object - * - * @param string Object UID - * @return mixed Message UID on success, false on error - */ - public function undelete($uid) - { - if (!$this->valid) { - return false; - } - - if ($msguid = $this->cache->uid2msguid($uid, true)) { - $this->cache->bypass(true); - $result = $this->imap->set_flag($msguid, 'UNDELETED', $this->name); - $this->cache->bypass(false); - - if ($result) { - return $msguid; - } - } - - return false; - } - - - /** - * Move a Kolab object message to another IMAP folder - * - * @param string Object UID - * @param string IMAP folder to move object to - * @return boolean True on success, false on failure - */ - public function move($uid, $target_folder) - { - if (!$this->valid) { - return false; - } - - if (is_string($target_folder)) - $target_folder = kolab_storage::get_folder($target_folder); - - if ($msguid = $this->cache->uid2msguid($uid)) { - $this->cache->bypass(true); - $result = $this->imap->move_message($msguid, $target_folder->name, $this->name); - $this->cache->bypass(false); - - if ($result) { - $this->cache->move($msguid, $uid, $target_folder); - return true; - } - else { - rcube::raise_error(array( - 'code' => 600, 'type' => 'php', - 'file' => __FILE__, 'line' => __LINE__, - 'message' => "Failed to move message $msguid to $target_folder: " . $this->imap->get_error_str(), - ), true); - } - } - - return false; - } - - - /** - * Creates source of the configuration object message - * - * @param array $object The array that holds the data of the object. - * @param string $type The type of the kolab object. - * @param bool $binary Enables use of binary encoding of attachment(s) - * @param string $body_file Reference to filename of message body - * - * @return mixed Message as string or array with two elements - * (one for message file path, second for message headers) - */ - private function build_message(&$object, $type, $binary, &$body_file) - { - // load old object to preserve data we don't understand/process - if (is_object($object['_formatobj'])) - $format = $object['_formatobj']; - else if ($object['_msguid'] && ($old = $this->cache->get($object['_msguid'], $type, $object['_mailbox']))) - $format = $old['_formatobj']; - - // create new kolab_format instance - if (!$format) - $format = kolab_format::factory($type, kolab_storage::$version); - - if (PEAR::isError($format)) - return false; - - $format->set($object); - $xml = $format->write(kolab_storage::$version); - $object['uid'] = $format->uid; // read UID from format - $object['_formatobj'] = $format; - - if (empty($xml) || !$format->is_valid() || empty($object['uid'])) { - return false; - } - - $mime = new Mail_mime("\r\n"); - $rcmail = rcube::get_instance(); - $headers = array(); - $files = array(); - $part_id = 1; - $encoding = $binary ? 'binary' : 'base64'; - - if ($user_email = $rcmail->get_user_email()) { - $headers['From'] = $user_email; - $headers['To'] = $user_email; - } - $headers['Date'] = date('r'); - $headers['X-Kolab-Type'] = kolab_format::KTYPE_PREFIX . $type; - $headers['X-Kolab-Mime-Version'] = kolab_storage::$version; - $headers['Subject'] = $object['uid']; -// $headers['Message-ID'] = $rcmail->gen_message_id(); - $headers['User-Agent'] = $rcmail->config->get('useragent'); - - // Check if we have enough memory to handle the message in it - // It's faster than using files, so we'll do this if we only can - if (!empty($object['_attachments']) && ($mem_limit = parse_bytes(ini_get('memory_limit'))) > 0) { - $memory = function_exists('memory_get_usage') ? memory_get_usage() : 16*1024*1024; // safe value: 16MB - - foreach ($object['_attachments'] as $attachment) { - $memory += $attachment['size']; - } - - // 1.33 is for base64, we need at least 4x more memory than the message size - if ($memory * ($binary ? 1 : 1.33) * 4 > $mem_limit) { - $marker = '%%%~~~' . md5(microtime(true) . $memory) . '~~~%%%'; - $is_file = true; - $temp_dir = unslashify($rcmail->config->get('temp_dir')); - $mime->setParam('delay_file_io', true); - } - } - - $mime->headers($headers); - $mime->setTXTBody("This is a Kolab Groupware object. " - . "To view this object you will need an email client that understands the Kolab Groupware format. " - . "For a list of such email clients please visit http://www.kolab.org/\n\n"); - - $ctype = kolab_storage::$version == '2.0' ? $format->CTYPEv2 : $format->CTYPE; - // Convert new lines to \r\n, to wrokaround "NO Message contains bare newlines" - // when APPENDing from temp file - $xml = preg_replace('/\r?\n/', "\r\n", $xml); - - $mime->addAttachment($xml, // file - $ctype, // content-type - 'kolab.xml', // filename - false, // is_file - '8bit', // encoding - 'attachment', // disposition - RCUBE_CHARSET // charset - ); - $part_id++; - - // save object attachments as separate parts - foreach ((array)$object['_attachments'] as $key => $att) { - if (empty($att['content']) && !empty($att['id'])) { - // @TODO: use IMAP CATENATE to skip attachment fetch+push operation - $msguid = $object['_copyfrom'] ?: ($object['_msguid'] ?: $object['uid']); - if ($is_file) { - $att['path'] = tempnam($temp_dir, 'rcmAttmnt'); - if (($fp = fopen($att['path'], 'w')) && $this->get_attachment($msguid, $att['id'], $object['_mailbox'], false, $fp, true)) { - fclose($fp); - } - else { - return false; - } - } - else { - $att['content'] = $this->get_attachment($msguid, $att['id'], $object['_mailbox'], false, null, true); - } - } - - $headers = array('Content-ID' => Mail_mimePart::encodeHeader('Content-ID', '<' . $key . '>', RCUBE_CHARSET, 'quoted-printable')); - $name = !empty($att['name']) ? $att['name'] : $key; - - // To store binary files we can use faster method - // without writting full message content to a temporary file but - // directly to IMAP, see rcube_imap_generic::append(). - // I.e. use file handles where possible - if (!empty($att['path'])) { - if ($is_file && $binary) { - $files[] = fopen($att['path'], 'r'); - $mime->addAttachment($marker, $att['mimetype'], $name, false, $encoding, 'attachment', '', '', '', null, null, '', RCUBE_CHARSET, $headers); - } - else { - $mime->addAttachment($att['path'], $att['mimetype'], $name, true, $encoding, 'attachment', '', '', '', null, null, '', RCUBE_CHARSET, $headers); - } - } - else { - if (is_resource($att['content']) && $is_file && $binary) { - $files[] = $att['content']; - $mime->addAttachment($marker, $att['mimetype'], $name, false, $encoding, 'attachment', '', '', '', null, null, '', RCUBE_CHARSET, $headers); - } - else { - if (is_resource($att['content'])) { - @rewind($att['content']); - $att['content'] = stream_get_contents($att['content']); - } - $mime->addAttachment($att['content'], $att['mimetype'], $name, false, $encoding, 'attachment', '', '', '', null, null, '', RCUBE_CHARSET, $headers); - } - } - - $object['_attachments'][$key]['id'] = ++$part_id; - } - - if (!$is_file || !empty($files)) { - $message = $mime->getMessage(); - } - - // parse message and build message array with - // attachment file pointers in place of file markers - if (!empty($files)) { - $message = explode($marker, $message); - $tmp = array(); - - foreach ($message as $msg_part) { - $tmp[] = $msg_part; - if ($file = array_shift($files)) { - $tmp[] = $file; - } - } - $message = $tmp; - } - // write complete message body into temp file - else if ($is_file) { - // use common temp dir - $body_file = tempnam($temp_dir, 'rcmMsg'); - - if (PEAR::isError($mime_result = $mime->saveMessageBody($body_file))) { - rcube::raise_error(array('code' => 650, 'type' => 'php', - 'file' => __FILE__, 'line' => __LINE__, - 'message' => "Could not create message: ".$mime_result->getMessage()), - true, false); - return false; - } - - $message = array(trim($mime->txtHeaders()) . "\r\n\r\n", fopen($body_file, 'r')); - } - - return $message; - } - - - /** - * Triggers any required updates after changes within the - * folder. This is currently only required for handling free/busy - * information with Kolab. - * - * @return boolean|PEAR_Error True if successfull. - */ - public function trigger() - { - $owner = $this->get_owner(); - $result = false; - - switch($this->type) { - case 'event': - if ($this->get_namespace() == 'personal') { - $result = $this->trigger_url( - sprintf('%s/trigger/%s/%s.pfb', - kolab_storage::get_freebusy_server(), - urlencode($owner), - urlencode($this->imap->mod_folder($this->name)) - ), - $this->imap->options['user'], - $this->imap->options['password'] - ); - } - break; - - default: - return true; - } - - if ($result && is_object($result) && is_a($result, 'PEAR_Error')) { - return PEAR::raiseError(sprintf("Failed triggering folder %s. Error was: %s", - $this->name, $result->getMessage())); - } - - return $result; - } - - /** - * Triggers a URL. - * - * @param string $url The URL to be triggered. - * @param string $auth_user Username to authenticate with - * @param string $auth_passwd Password for basic auth - * @return boolean|PEAR_Error True if successfull. - */ - private function trigger_url($url, $auth_user = null, $auth_passwd = null) - { - try { - $request = libkolab::http_request($url); - - // set authentication credentials - if ($auth_user && $auth_passwd) - $request->setAuth($auth_user, $auth_passwd); - - $result = $request->send(); - // rcube::write_log('trigger', $result->getBody()); - } - catch (Exception $e) { - return PEAR::raiseError($e->getMessage()); - } - - return true; - } - -} - diff --git a/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_folder_api.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_folder_api.php deleted file mode 100644 index 0b9091f..0000000 --- a/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_folder_api.php +++ /dev/null @@ -1,361 +0,0 @@ - - * - * Copyright (C) 2014, Kolab Systems AG - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -abstract class kolab_storage_folder_api -{ - /** - * Folder identifier - * @var string - */ - public $id; - - /** - * The folder name. - * @var string - */ - public $name; - - /** - * The type of this folder. - * @var string - */ - public $type; - - /** - * The subtype of this folder. - * @var string - */ - public $subtype; - - /** - * Is this folder set to be the default for its type - * @var boolean - */ - public $default = false; - - /** - * List of direct child folders - * @var array - */ - public $children = array(); - - /** - * Name of the parent folder - * @var string - */ - public $parent = ''; - - protected $imap; - protected $owner; - protected $info; - protected $idata; - protected $namespace; - - - /** - * Private constructor - */ - protected function __construct($name) - { - $this->name = $name; - $this->id = kolab_storage::folder_id($name); - $this->imap = rcube::get_instance()->get_storage(); - } - - - /** - * Returns the owner of the folder. - * - * @param boolean Return a fully qualified owner name (i.e. including domain for shared folders) - * @return string The owner of this folder. - */ - public function get_owner($fully_qualified = false) - { - // return cached value - if (isset($this->owner)) - return $this->owner; - - $info = $this->get_folder_info(); - $rcmail = rcube::get_instance(); - - switch ($info['namespace']) { - case 'personal': - $this->owner = $rcmail->get_user_name(); - break; - - case 'shared': - $this->owner = 'anonymous'; - break; - - default: - list($prefix, $this->owner) = explode($this->imap->get_hierarchy_delimiter(), $info['name']); - $fully_qualified = true; // enforce email addresses (backwards compatibility) - break; - } - - if ($fully_qualified && strpos($this->owner, '@') === false) { - // extract domain from current user name - $domain = strstr($rcmail->get_user_name(), '@'); - // fall back to mail_domain config option - if (empty($domain) && ($mdomain = $rcmail->config->mail_domain($this->imap->options['host']))) { - $domain = '@' . $mdomain; - } - $this->owner .= $domain; - } - - return $this->owner; - } - - - /** - * Getter for the name of the namespace to which the IMAP folder belongs - * - * @return string Name of the namespace (personal, other, shared) - */ - public function get_namespace() - { - if (!isset($this->namespace)) - $this->namespace = $this->imap->folder_namespace($this->name); - return $this->namespace; - } - - - /** - * Get the display name value of this folder - * - * @return string Folder name - */ - public function get_name() - { - return kolab_storage::object_name($this->name, $this->get_namespace()); - } - - - /** - * Getter for the top-end folder name (not the entire path) - * - * @return string Name of this folder - */ - public function get_foldername() - { - $parts = explode('/', $this->name); - return rcube_charset::convert(end($parts), 'UTF7-IMAP'); - } - - /** - * Getter for parent folder path - * - * @return string Full path to parent folder - */ - public function get_parent() - { - $path = explode('/', $this->name); - array_pop($path); - - // don't list top-level namespace folder - if (count($path) == 1 && in_array($this->get_namespace(), array('other', 'shared'))) { - $path = array(); - } - - return join('/', $path); - } - - /** - * Getter for the Cyrus mailbox identifier corresponding to this folder - * (e.g. user/john.doe/Calendar/Personal@example.org) - * - * @return string Mailbox ID - */ - public function get_mailbox_id() - { - $info = $this->get_folder_info(); - $owner = $this->get_owner(); - list($user, $domain) = explode('@', $owner); - - switch ($info['namespace']) { - case 'personal': - return sprintf('user/%s/%s@%s', $user, $this->name, $domain); - - case 'shared': - $ns = $this->imap->get_namespace('shared'); - $prefix = is_array($ns) ? $ns[0][0] : ''; - list(, $domain) = explode('@', rcube::get_instance()->get_user_name()); - return substr($this->name, strlen($prefix)) . '@' . $domain; - - default: - $ns = $this->imap->get_namespace('other'); - $prefix = is_array($ns) ? $ns[0][0] : ''; - list($user, $folder) = explode($this->imap->get_hierarchy_delimiter(), substr($info['name'], strlen($prefix)), 2); - if (strpos($user, '@')) { - list($user, $domain) = explode('@', $user); - } - return sprintf('user/%s/%s@%s', $user, $folder, $domain); - } - } - - /** - * Get the color value stored in metadata - * - * @param string Default color value to return if not set - * @return mixed Color value from IMAP metadata or $default is not set - */ - public function get_color($default = null) - { - // color is defined in folder METADATA - $metadata = $this->get_metadata(array(kolab_storage::COLOR_KEY_PRIVATE, kolab_storage::COLOR_KEY_SHARED)); - if (($color = $metadata[kolab_storage::COLOR_KEY_PRIVATE]) || ($color = $metadata[kolab_storage::COLOR_KEY_SHARED])) { - return $color; - } - - return $default; - } - - - /** - * Returns IMAP metadata/annotations (GETMETADATA/GETANNOTATION) - * - * @param array List of metadata keys to read - * @return array Metadata entry-value hash array on success, NULL on error - */ - public function get_metadata($keys) - { - $metadata = rcube::get_instance()->get_storage()->get_metadata($this->name, (array)$keys); - return $metadata[$this->name]; - } - - - /** - * Sets IMAP metadata/annotations (SETMETADATA/SETANNOTATION) - * - * @param array $entries Entry-value array (use NULL value as NIL) - * @return boolean True on success, False on failure - */ - public function set_metadata($entries) - { - return $this->imap->set_metadata($this->name, $entries); - } - - - /** - * - */ - public function get_folder_info() - { - if (!isset($this->info)) - $this->info = $this->imap->folder_info($this->name); - - return $this->info; - } - - /** - * Make IMAP folder data available for this folder - */ - public function get_imap_data() - { - if (!isset($this->idata)) - $this->idata = $this->imap->folder_data($this->name); - - return $this->idata; - } - - - /** - * Get IMAP ACL information for this folder - * - * @return string Permissions as string - */ - public function get_myrights() - { - $rights = $this->info['rights']; - - if (!is_array($rights)) - $rights = $this->imap->my_rights($this->name); - - return join('', (array)$rights); - } - - /** - * Helper method to extract folder UID metadata - * - * @return string Folder's UID - */ - public function get_uid() - { - // To be implemented by extending classes - return false; - } - - /** - * Check activation status of this folder - * - * @return boolean True if enabled, false if not - */ - public function is_active() - { - return kolab_storage::folder_is_active($this->name); - } - - /** - * Change activation status of this folder - * - * @param boolean The desired subscription status: true = active, false = not active - * - * @return True on success, false on error - */ - public function activate($active) - { - return $active ? kolab_storage::folder_activate($this->name) : kolab_storage::folder_deactivate($this->name); - } - - /** - * Check subscription status of this folder - * - * @return boolean True if subscribed, false if not - */ - public function is_subscribed() - { - return kolab_storage::folder_is_subscribed($this->name); - } - - /** - * Change subscription status of this folder - * - * @param boolean The desired subscription status: true = subscribed, false = not subscribed - * - * @return True on success, false on error - */ - public function subscribe($subscribed) - { - return $subscribed ? kolab_storage::folder_subscribe($this->name) : kolab_storage::folder_unsubscribe($this->name); - } - - /** - * Return folder name as string representation of this object - * - * @return string Full IMAP folder name - */ - public function __toString() - { - return $this->name; - } -} - diff --git a/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_folder_user.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_folder_user.php deleted file mode 100644 index 7c141c5..0000000 --- a/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_folder_user.php +++ /dev/null @@ -1,135 +0,0 @@ - - * - * Copyright (C) 2014, Kolab Systems AG - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -class kolab_storage_folder_user extends kolab_storage_folder_virtual -{ - protected static $ldapcache = array(); - - public $ldaprec; - public $type; - - /** - * Default constructor - */ - public function __construct($name, $parent = '', $ldaprec = null) - { - parent::__construct($name, $name, 'other', $parent); - - if (!empty($ldaprec)) { - self::$ldapcache[$name] = $this->ldaprec = $ldaprec; - } - // use value cached in memory for repeated lookups - else if (array_key_exists($name, self::$ldapcache)) { - $this->ldaprec = self::$ldapcache[$name]; - } - // lookup user in LDAP and set $this->ldaprec - else if ($ldap = kolab_storage::ldap()) { - // get domain from current user - list(,$domain) = explode('@', rcube::get_instance()->get_user_name()); - $this->ldaprec = $ldap->get_user_record(parent::get_foldername($this->name) . '@' . $domain, $_SESSION['imap_host']); - if (!empty($this->ldaprec)) { - $this->ldaprec['kolabtargetfolder'] = $name; - } - self::$ldapcache[$name] = $this->ldaprec; - } - } - - /** - * Getter for the top-end folder name to be displayed - * - * @return string Name of this folder - */ - public function get_foldername() - { - return $this->ldaprec ? ($this->ldaprec['displayname'] ?: $this->ldaprec['name']) : - parent::get_foldername(); - } - - /** - * Getter for a more informative title of this user folder - * - * @return string Title for the given user record - */ - public function get_title() - { - return trim($this->ldaprec['displayname'] . '; ' . $this->ldaprec['mail'], '; '); - } - - /** - * Returns the owner of the folder. - * - * @return string The owner of this folder. - */ - public function get_owner() - { - return $this->ldaprec['mail']; - } - - /** - * Check subscription status of this folder. - * Subscription of a virtual user folder depends on the subscriptions of subfolders. - * - * @return boolean True if subscribed, false if not - */ - public function is_subscribed() - { - if (!empty($this->type)) { - $children = $subscribed = 0; - $delimiter = $this->imap->get_hierarchy_delimiter(); - foreach ((array)kolab_storage::list_folders($this->name . $delimiter, '*', $this->type, false) as $subfolder) { - if (kolab_storage::folder_is_subscribed($subfolder)) { - $subscribed++; - } - $children++; - } - if ($subscribed > 0) { - return $subscribed == $children ? true : 2; - } - } - - return false; - } - - /** - * Change subscription status of this folder - * - * @param boolean The desired subscription status: true = subscribed, false = not subscribed - * - * @return True on success, false on error - */ - public function subscribe($subscribed) - { - $success = false; - - // (un)subscribe all subfolders of a given type - if (!empty($this->type)) { - $delimiter = $this->imap->get_hierarchy_delimiter(); - foreach ((array)kolab_storage::list_folders($this->name . $delimiter, '*', $this->type, false) as $subfolder) { - $success |= ($subscribed ? kolab_storage::folder_subscribe($subfolder) : kolab_storage::folder_unsubscribe($subfolder)); - } - } - - return $success; - } - -} \ No newline at end of file diff --git a/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_folder_virtual.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_folder_virtual.php deleted file mode 100644 index e419ced..0000000 --- a/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_folder_virtual.php +++ /dev/null @@ -1,59 +0,0 @@ - - * - * Copyright (C) 2014, Kolab Systems AG - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -class kolab_storage_folder_virtual extends kolab_storage_folder_api -{ - public $virtual = true; - - protected $displayname; - - public function __construct($name, $dispname, $ns, $parent = '') - { - parent::__construct($name); - - $this->namespace = $ns; - $this->parent = $parent; - $this->displayname = $dispname; - } - - /** - * Get the display name value of this folder - * - * @return string Folder name - */ - public function get_name() - { - return $this->displayname ?: parent::get_name(); - } - - /** - * Get the color value stored in metadata - * - * @param string Default color value to return if not set - * @return mixed Color value from IMAP metadata or $default is not set - */ - public function get_color($default = null) - { - return $default; - } -} \ No newline at end of file diff --git a/lib/drivers/kolab/plugins/libkolab/libkolab.php b/lib/drivers/kolab/plugins/libkolab/libkolab.php deleted file mode 100644 index 0e4c8af..0000000 --- a/lib/drivers/kolab/plugins/libkolab/libkolab.php +++ /dev/null @@ -1,316 +0,0 @@ - - * - * Copyright (C) 2012-2015, Kolab Systems AG - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -class libkolab extends rcube_plugin -{ - static $http_requests = array(); - static $bonnie_api = false; - - /** - * Required startup method of a Roundcube plugin - */ - public function init() - { - // load local config - $this->load_config(); - - // extend include path to load bundled lib classes - $include_path = $this->home . '/lib' . PATH_SEPARATOR . ini_get('include_path'); - set_include_path($include_path); - - $this->add_hook('storage_init', array($this, 'storage_init')); - $this->add_hook('user_delete', array('kolab_storage', 'delete_user_folders')); - - $rcmail = rcube::get_instance(); - try { - kolab_format::$timezone = new DateTimeZone($rcmail->config->get('timezone', 'GMT')); - } - catch (Exception $e) { - rcube::raise_error($e, true); - kolab_format::$timezone = new DateTimeZone('GMT'); - } - - $this->add_texts('localization/', false); - - // embed scripts and templates for email message audit trail - if ($rcmail->task == 'mail' && self::get_bonnie_api()) { - if ($rcmail->output->type == 'html') { - $this->add_hook('render_page', array($this, 'bonnie_render_page')); - - $this->include_script('js/audittrail.js'); - $this->include_stylesheet($this->local_skin_path() . '/libkolab.css'); - - // add 'Show history' item to message menu - $this->api->add_content(html::tag('li', null, - $this->api->output->button(array( - 'command' => 'kolab-mail-history', - 'label' => 'libkolab.showhistory', - 'type' => 'link', - 'classact' => 'icon history active', - 'class' => 'icon history', - 'innerclass' => 'icon history', - ))), - 'messagemenu'); - } - - $this->register_action('plugin.message-changelog', array($this, 'message_changelog')); - } - } - - /** - * Hook into IMAP FETCH HEADER.FIELDS command and request Kolab-specific headers - */ - function storage_init($p) - { - $p['fetch_headers'] = trim($p['fetch_headers'] .' X-KOLAB-TYPE X-KOLAB-MIME-VERSION'); - return $p; - } - - /** - * Getter for a singleton instance of the Bonnie API - * - * @return mixed kolab_bonnie_api instance if configured, false otherwise - */ - public static function get_bonnie_api() - { - // get configuration for the Bonnie API - if (!self::$bonnie_api && ($bonnie_config = rcube::get_instance()->config->get('kolab_bonnie_api', false))) { - self::$bonnie_api = new kolab_bonnie_api($bonnie_config); - } - - return self::$bonnie_api; - } - - /** - * Hook to append the message history dialog template to the mail view - */ - function bonnie_render_page($p) - { - if (($p['template'] === 'mail' || $p['template'] === 'message') && !$p['kolab-audittrail']) { - // append a template for the audit trail dialog - $this->api->output->add_footer( - html::div(array('id' => 'mailmessagehistory', 'class' => 'uidialog', 'aria-hidden' => 'true', 'style' => 'display:none'), - self::object_changelog_table(array('class' => 'records-table changelog-table')) - ) - ); - $this->api->output->set_env('kolab_audit_trail', true); - $p['kolab-audittrail'] = true; - } - - return $p; - } - - /** - * Handler for message audit trail changelog requests - */ - public function message_changelog() - { - if (!self::$bonnie_api) { - return false; - } - - $rcmail = rcube::get_instance(); - $msguid = rcube_utils::get_input_value('_uid', rcube_utils::INPUT_POST, true); - $mailbox = rcube_utils::get_input_value('_mbox', rcube_utils::INPUT_POST); - - $result = $msguid && $mailbox ? self::$bonnie_api->changelog('mail', null, $mailbox, $msguid) : null; - if (is_array($result)) { - if (is_array($result['changes'])) { - $dtformat = $rcmail->config->get('date_format') . ' ' . $rcmail->config->get('time_format'); - array_walk($result['changes'], function(&$change) use ($dtformat, $rcmail) { - if ($change['date']) { - $dt = rcube_utils::anytodatetime($change['date']); - if ($dt instanceof DateTime) { - $change['date'] = $rcmail->format_date($dt, $dtformat); - } - } - }); - } - $this->api->output->command('plugin.message_render_changelog', $result['changes']); - } - else { - $this->api->output->command('plugin.message_render_changelog', false); - } - - $this->api->output->send(); - } - - /** - * Wrapper function to load and initalize the HTTP_Request2 Object - * - * @param string|Net_Url2 Request URL - * @param string Request method ('OPTIONS','GET','HEAD','POST','PUT','DELETE','TRACE','CONNECT') - * @param array Configuration for this Request instance, that will be merged - * with default configuration - * - * @return HTTP_Request2 Request object - */ - public static function http_request($url = '', $method = 'GET', $config = array()) - { - $rcube = rcube::get_instance(); - $http_config = (array) $rcube->config->get('kolab_http_request'); - - // deprecated configuration options - if (empty($http_config)) { - foreach (array('ssl_verify_peer', 'ssl_verify_host') as $option) { - $value = $rcube->config->get('kolab_' . $option, true); - if (is_bool($value)) { - $http_config[$option] = $value; - } - } - } - - if (!empty($config)) { - $http_config = array_merge($http_config, $config); - } - - // force CURL adapter, this allows to handle correctly - // compressed responses with SplObserver registered (kolab_files) (#4507) - $http_config['adapter'] = 'HTTP_Request2_Adapter_Curl'; - - $key = md5(serialize($http_config)); - - if (!($request = self::$http_requests[$key])) { - // load HTTP_Request2 - require_once 'HTTP/Request2.php'; - - try { - $request = new HTTP_Request2(); - $request->setConfig($http_config); - } - catch (Exception $e) { - rcube::raise_error($e, true, true); - } - - // proxy User-Agent string - $request->setHeader('user-agent', $_SERVER['HTTP_USER_AGENT']); - - self::$http_requests[$key] = $request; - } - - // cleanup - try { - $request->setBody(''); - $request->setUrl($url); - $request->setMethod($method); - } - catch (Exception $e) { - rcube::raise_error($e, true, true); - } - - return $request; - } - - /** - * Table oultine for object changelog display - */ - public static function object_changelog_table($attrib = array()) - { - $rcube = rcube::get_instance(); - $attrib += array('domain' => 'libkolab'); - - $table = new html_table(array('cols' => 5, 'border' => 0, 'cellspacing' => 0)); - $table->add_header('diff', ''); - $table->add_header('revision', $rcube->gettext('revision', $attrib['domain'])); - $table->add_header('date', $rcube->gettext('date', $attrib['domain'])); - $table->add_header('user', $rcube->gettext('user', $attrib['domain'])); - $table->add_header('operation', $rcube->gettext('operation', $attrib['domain'])); - $table->add_header('actions', ' '); - - $rcube->output->add_label( - 'libkolab.showrevision', - 'libkolab.actionreceive', - 'libkolab.actionappend', - 'libkolab.actionmove', - 'libkolab.actiondelete', - 'libkolab.actionread', - 'libkolab.actionflagset', - 'libkolab.actionflagclear', - 'libkolab.objectchangelog', - 'close' - ); - - return $table->show($attrib); - } - - /** - * Wrapper function for generating a html diff using the FineDiff class by Raymond Hill - */ - public static function html_diff($from, $to, $is_html = null) - { - // auto-detect text/html format - if ($is_html === null) { - $from_html = (preg_match('/<(html|body)(\s+[a-z]|>)/', $from, $m) && strpos($from, '') > 0); - $to_html = (preg_match('/<(html|body)(\s+[a-z]|>)/', $to, $m) && strpos($to, '') > 0); - $is_html = $from_html || $to_html; - - // ensure both parts are of the same format - if ($is_html && !$from_html) { - $converter = new rcube_text2html($from, false, array('wrap' => true)); - $from = $converter->get_html(); - } - if ($is_html && !$to_html) { - $converter = new rcube_text2html($to, false, array('wrap' => true)); - $to = $converter->get_html(); - } - } - - // compute diff from HTML - if ($is_html) { - include_once __dir__ . '/vendor/Caxy/HtmlDiff/Match.php'; - include_once __dir__ . '/vendor/Caxy/HtmlDiff/Operation.php'; - include_once __dir__ . '/vendor/Caxy/HtmlDiff/HtmlDiff.php'; - - // replace data: urls with a transparent image to avoid memory problems - $from = preg_replace('/src="data:image[^"]+/', 'src="data:image/gif;base64,R0lGODlhAQABAPAAAOjq6gAAACH/C1hNUCBEYXRhWE1QAT8AIfkEBQAAAAAsAAAAAAEAAQAAAgJEAQA7', $from); - $to = preg_replace('/src="data:image[^"]+/', 'src="data:image/gif;base64,R0lGODlhAQABAPAAAOjq6gAAACH/C1hNUCBEYXRhWE1QAT8AIfkEBQAAAAAsAAAAAAEAAQAAAgJEAQA7', $to); - - $diff = new Caxy\HtmlDiff\HtmlDiff($from, $to); - $diffhtml = $diff->build(); - - // remove empty inserts (from tables) - return preg_replace('!\s*!Uims', '', $diffhtml); - } - else { - include_once __dir__ . '/vendor/finediff.php'; - - $diff = new FineDiff($from, $to, FineDiff::$wordGranularity); - return $diff->renderDiffToHTML(); - } - } - - /** - * Return a date() format string to render identifiers for recurrence instances - * - * @param array Hash array with event properties - * @return string Format string - */ - public static function recurrence_id_format($event) - { - return $event['allday'] ? 'Ymd' : 'Ymd\THis'; - } -} diff --git a/lib/drivers/kolab/plugins/libkolab/vendor/finediff.php b/lib/drivers/kolab/plugins/libkolab/vendor/finediff.php deleted file mode 100644 index b3c416c..0000000 --- a/lib/drivers/kolab/plugins/libkolab/vendor/finediff.php +++ /dev/null @@ -1,688 +0,0 @@ -copy->insert -* command (swap) for when the inserted segment is exactly the same -* as the deleted one, and with only a copy operation in between. -* TODO: How often this case occurs? Is it worth it? Can only -* be done as a postprocessing method (->optimize()?) -*/ -abstract class FineDiffOp { - abstract public function getFromLen(); - abstract public function getToLen(); - abstract public function getOpcode(); - } - -class FineDiffDeleteOp extends FineDiffOp { - public function __construct($len) { - $this->fromLen = $len; - } - public function getFromLen() { - return $this->fromLen; - } - public function getToLen() { - return 0; - } - public function getOpcode() { - if ( $this->fromLen === 1 ) { - return 'd'; - } - return "d{$this->fromLen}"; - } - } - -class FineDiffInsertOp extends FineDiffOp { - public function __construct($text) { - $this->text = $text; - } - public function getFromLen() { - return 0; - } - public function getToLen() { - return strlen($this->text); - } - public function getText() { - return $this->text; - } - public function getOpcode() { - $to_len = strlen($this->text); - if ( $to_len === 1 ) { - return "i:{$this->text}"; - } - return "i{$to_len}:{$this->text}"; - } - } - -class FineDiffReplaceOp extends FineDiffOp { - public function __construct($fromLen, $text) { - $this->fromLen = $fromLen; - $this->text = $text; - } - public function getFromLen() { - return $this->fromLen; - } - public function getToLen() { - return strlen($this->text); - } - public function getText() { - return $this->text; - } - public function getOpcode() { - if ( $this->fromLen === 1 ) { - $del_opcode = 'd'; - } - else { - $del_opcode = "d{$this->fromLen}"; - } - $to_len = strlen($this->text); - if ( $to_len === 1 ) { - return "{$del_opcode}i:{$this->text}"; - } - return "{$del_opcode}i{$to_len}:{$this->text}"; - } - } - -class FineDiffCopyOp extends FineDiffOp { - public function __construct($len) { - $this->len = $len; - } - public function getFromLen() { - return $this->len; - } - public function getToLen() { - return $this->len; - } - public function getOpcode() { - if ( $this->len === 1 ) { - return 'c'; - } - return "c{$this->len}"; - } - public function increase($size) { - return $this->len += $size; - } - } - -/** -* FineDiff ops -* -* Collection of ops -*/ -class FineDiffOps { - public function appendOpcode($opcode, $from, $from_offset, $from_len) { - if ( $opcode === 'c' ) { - $edits[] = new FineDiffCopyOp($from_len); - } - else if ( $opcode === 'd' ) { - $edits[] = new FineDiffDeleteOp($from_len); - } - else /* if ( $opcode === 'i' ) */ { - $edits[] = new FineDiffInsertOp(substr($from, $from_offset, $from_len)); - } - } - public $edits = array(); - } - -/** -* FineDiff class -* -* TODO: Document -* -*/ -class FineDiff { - - /**------------------------------------------------------------------------ - * - * Public section - * - */ - - /** - * Constructor - * ... - * The $granularityStack allows FineDiff to be configurable so that - * a particular stack tailored to the specific content of a document can - * be passed. - */ - public function __construct($from_text = '', $to_text = '', $granularityStack = null) { - // setup stack for generic text documents by default - $this->granularityStack = $granularityStack ? $granularityStack : FineDiff::$characterGranularity; - $this->edits = array(); - $this->from_text = $from_text; - $this->doDiff($from_text, $to_text); - } - - public function getOps() { - return $this->edits; - } - - public function getOpcodes() { - $opcodes = array(); - foreach ( $this->edits as $edit ) { - $opcodes[] = $edit->getOpcode(); - } - return implode('', $opcodes); - } - - public function renderDiffToHTML() { - $in_offset = 0; - $html = ''; - foreach ( $this->edits as $edit ) { - $n = $edit->getFromLen(); - if ( $edit instanceof FineDiffCopyOp ) { - $html .= FineDiff::renderDiffToHTMLFromOpcode('c', $this->from_text, $in_offset, $n); - } - else if ( $edit instanceof FineDiffDeleteOp ) { - $html .= FineDiff::renderDiffToHTMLFromOpcode('d', $this->from_text, $in_offset, $n); - } - else if ( $edit instanceof FineDiffInsertOp ) { - $html .= FineDiff::renderDiffToHTMLFromOpcode('i', $edit->getText(), 0, $edit->getToLen()); - } - else /* if ( $edit instanceof FineDiffReplaceOp ) */ { - $html .= FineDiff::renderDiffToHTMLFromOpcode('d', $this->from_text, $in_offset, $n); - $html .= FineDiff::renderDiffToHTMLFromOpcode('i', $edit->getText(), 0, $edit->getToLen()); - } - $in_offset += $n; - } - return $html; - } - - /**------------------------------------------------------------------------ - * Return an opcodes string describing the diff between a "From" and a - * "To" string - */ - public static function getDiffOpcodes($from, $to, $granularities = null) { - $diff = new FineDiff($from, $to, $granularities); - return $diff->getOpcodes(); - } - - /**------------------------------------------------------------------------ - * Return an iterable collection of diff ops from an opcodes string - */ - public static function getDiffOpsFromOpcodes($opcodes) { - $diffops = new FineDiffOps(); - FineDiff::renderFromOpcodes(null, $opcodes, array($diffops,'appendOpcode')); - return $diffops->edits; - } - - /**------------------------------------------------------------------------ - * Re-create the "To" string from the "From" string and an "Opcodes" string - */ - public static function renderToTextFromOpcodes($from, $opcodes) { - return FineDiff::renderFromOpcodes($from, $opcodes, array('FineDiff','renderToTextFromOpcode')); - } - - /**------------------------------------------------------------------------ - * Render the diff to an HTML string - */ - public static function renderDiffToHTMLFromOpcodes($from, $opcodes) { - return FineDiff::renderFromOpcodes($from, $opcodes, array('FineDiff','renderDiffToHTMLFromOpcode')); - } - - /**------------------------------------------------------------------------ - * Generic opcodes parser, user must supply callback for handling - * single opcode - */ - public static function renderFromOpcodes($from, $opcodes, $callback) { - if ( !is_callable($callback) ) { - return ''; - } - $out = ''; - $opcodes_len = strlen($opcodes); - $from_offset = $opcodes_offset = 0; - while ( $opcodes_offset < $opcodes_len ) { - $opcode = substr($opcodes, $opcodes_offset, 1); - $opcodes_offset++; - $n = intval(substr($opcodes, $opcodes_offset)); - if ( $n ) { - $opcodes_offset += strlen(strval($n)); - } - else { - $n = 1; - } - if ( $opcode === 'c' ) { // copy n characters from source - $out .= call_user_func($callback, 'c', $from, $from_offset, $n, ''); - $from_offset += $n; - } - else if ( $opcode === 'd' ) { // delete n characters from source - $out .= call_user_func($callback, 'd', $from, $from_offset, $n, ''); - $from_offset += $n; - } - else /* if ( $opcode === 'i' ) */ { // insert n characters from opcodes - $out .= call_user_func($callback, 'i', $opcodes, $opcodes_offset + 1, $n); - $opcodes_offset += 1 + $n; - } - } - return $out; - } - - /** - * Stock granularity stacks and delimiters - */ - - const paragraphDelimiters = "\n\r"; - public static $paragraphGranularity = array( - FineDiff::paragraphDelimiters - ); - const sentenceDelimiters = ".\n\r"; - public static $sentenceGranularity = array( - FineDiff::paragraphDelimiters, - FineDiff::sentenceDelimiters - ); - const wordDelimiters = " \t.\n\r"; - public static $wordGranularity = array( - FineDiff::paragraphDelimiters, - FineDiff::sentenceDelimiters, - FineDiff::wordDelimiters - ); - const characterDelimiters = ""; - public static $characterGranularity = array( - FineDiff::paragraphDelimiters, - FineDiff::sentenceDelimiters, - FineDiff::wordDelimiters, - FineDiff::characterDelimiters - ); - - public static $textStack = array( - ".", - " \t.\n\r", - "" - ); - - /**------------------------------------------------------------------------ - * - * Private section - * - */ - - /** - * Entry point to compute the diff. - */ - private function doDiff($from_text, $to_text) { - $this->last_edit = false; - $this->stackpointer = 0; - $this->from_text = $from_text; - $this->from_offset = 0; - // can't diff without at least one granularity specifier - if ( empty($this->granularityStack) ) { - return; - } - $this->_processGranularity($from_text, $to_text); - } - - /** - * This is the recursive function which is responsible for - * handling/increasing granularity. - * - * Incrementally increasing the granularity is key to compute the - * overall diff in a very efficient way. - */ - private function _processGranularity($from_segment, $to_segment) { - $delimiters = $this->granularityStack[$this->stackpointer++]; - $has_next_stage = $this->stackpointer < count($this->granularityStack); - foreach ( FineDiff::doFragmentDiff($from_segment, $to_segment, $delimiters) as $fragment_edit ) { - // increase granularity - if ( $fragment_edit instanceof FineDiffReplaceOp && $has_next_stage ) { - $this->_processGranularity( - substr($this->from_text, $this->from_offset, $fragment_edit->getFromLen()), - $fragment_edit->getText() - ); - } - // fuse copy ops whenever possible - else if ( $fragment_edit instanceof FineDiffCopyOp && $this->last_edit instanceof FineDiffCopyOp ) { - $this->edits[count($this->edits)-1]->increase($fragment_edit->getFromLen()); - $this->from_offset += $fragment_edit->getFromLen(); - } - else { - /* $fragment_edit instanceof FineDiffCopyOp */ - /* $fragment_edit instanceof FineDiffDeleteOp */ - /* $fragment_edit instanceof FineDiffInsertOp */ - $this->edits[] = $this->last_edit = $fragment_edit; - $this->from_offset += $fragment_edit->getFromLen(); - } - } - $this->stackpointer--; - } - - /** - * This is the core algorithm which actually perform the diff itself, - * fragmenting the strings as per specified delimiters. - * - * This function is naturally recursive, however for performance purpose - * a local job queue is used instead of outright recursivity. - */ - private static function doFragmentDiff($from_text, $to_text, $delimiters) { - // Empty delimiter means character-level diffing. - // In such case, use code path optimized for character-level - // diffing. - if ( empty($delimiters) ) { - return FineDiff::doCharDiff($from_text, $to_text); - } - - $result = array(); - - // fragment-level diffing - $from_text_len = strlen($from_text); - $to_text_len = strlen($to_text); - $from_fragments = FineDiff::extractFragments($from_text, $delimiters); - $to_fragments = FineDiff::extractFragments($to_text, $delimiters); - - $jobs = array(array(0, $from_text_len, 0, $to_text_len)); - - $cached_array_keys = array(); - - while ( $job = array_pop($jobs) ) { - - // get the segments which must be diff'ed - list($from_segment_start, $from_segment_end, $to_segment_start, $to_segment_end) = $job; - - // catch easy cases first - $from_segment_length = $from_segment_end - $from_segment_start; - $to_segment_length = $to_segment_end - $to_segment_start; - if ( !$from_segment_length || !$to_segment_length ) { - if ( $from_segment_length ) { - $result[$from_segment_start * 4] = new FineDiffDeleteOp($from_segment_length); - } - else if ( $to_segment_length ) { - $result[$from_segment_start * 4 + 1] = new FineDiffInsertOp(substr($to_text, $to_segment_start, $to_segment_length)); - } - continue; - } - - // find longest copy operation for the current segments - $best_copy_length = 0; - - $from_base_fragment_index = $from_segment_start; - - $cached_array_keys_for_current_segment = array(); - - while ( $from_base_fragment_index < $from_segment_end ) { - $from_base_fragment = $from_fragments[$from_base_fragment_index]; - $from_base_fragment_length = strlen($from_base_fragment); - // performance boost: cache array keys - if ( !isset($cached_array_keys_for_current_segment[$from_base_fragment]) ) { - if ( !isset($cached_array_keys[$from_base_fragment]) ) { - $to_all_fragment_indices = $cached_array_keys[$from_base_fragment] = array_keys($to_fragments, $from_base_fragment, true); - } - else { - $to_all_fragment_indices = $cached_array_keys[$from_base_fragment]; - } - // get only indices which falls within current segment - if ( $to_segment_start > 0 || $to_segment_end < $to_text_len ) { - $to_fragment_indices = array(); - foreach ( $to_all_fragment_indices as $to_fragment_index ) { - if ( $to_fragment_index < $to_segment_start ) { continue; } - if ( $to_fragment_index >= $to_segment_end ) { break; } - $to_fragment_indices[] = $to_fragment_index; - } - $cached_array_keys_for_current_segment[$from_base_fragment] = $to_fragment_indices; - } - else { - $to_fragment_indices = $to_all_fragment_indices; - } - } - else { - $to_fragment_indices = $cached_array_keys_for_current_segment[$from_base_fragment]; - } - // iterate through collected indices - foreach ( $to_fragment_indices as $to_base_fragment_index ) { - $fragment_index_offset = $from_base_fragment_length; - // iterate until no more match - for (;;) { - $fragment_from_index = $from_base_fragment_index + $fragment_index_offset; - if ( $fragment_from_index >= $from_segment_end ) { - break; - } - $fragment_to_index = $to_base_fragment_index + $fragment_index_offset; - if ( $fragment_to_index >= $to_segment_end ) { - break; - } - if ( $from_fragments[$fragment_from_index] !== $to_fragments[$fragment_to_index] ) { - break; - } - $fragment_length = strlen($from_fragments[$fragment_from_index]); - $fragment_index_offset += $fragment_length; - } - if ( $fragment_index_offset > $best_copy_length ) { - $best_copy_length = $fragment_index_offset; - $best_from_start = $from_base_fragment_index; - $best_to_start = $to_base_fragment_index; - } - } - $from_base_fragment_index += strlen($from_base_fragment); - // If match is larger than half segment size, no point trying to find better - // TODO: Really? - if ( $best_copy_length >= $from_segment_length / 2) { - break; - } - // no point to keep looking if what is left is less than - // current best match - if ( $from_base_fragment_index + $best_copy_length >= $from_segment_end ) { - break; - } - } - - if ( $best_copy_length ) { - $jobs[] = array($from_segment_start, $best_from_start, $to_segment_start, $best_to_start); - $result[$best_from_start * 4 + 2] = new FineDiffCopyOp($best_copy_length); - $jobs[] = array($best_from_start + $best_copy_length, $from_segment_end, $best_to_start + $best_copy_length, $to_segment_end); - } - else { - $result[$from_segment_start * 4 ] = new FineDiffReplaceOp($from_segment_length, substr($to_text, $to_segment_start, $to_segment_length)); - } - } - - ksort($result, SORT_NUMERIC); - return array_values($result); - } - - /** - * Perform a character-level diff. - * - * The algorithm is quite similar to doFragmentDiff(), except that - * the code path is optimized for character-level diff -- strpos() is - * used to find out the longest common subequence of characters. - * - * We try to find a match using the longest possible subsequence, which - * is at most the length of the shortest of the two strings, then incrementally - * reduce the size until a match is found. - * - * I still need to study more the performance of this function. It - * appears that for long strings, the generic doFragmentDiff() is more - * performant. For word-sized strings, doCharDiff() is somewhat more - * performant. - */ - private static function doCharDiff($from_text, $to_text) { - $result = array(); - $jobs = array(array(0, strlen($from_text), 0, strlen($to_text))); - while ( $job = array_pop($jobs) ) { - // get the segments which must be diff'ed - list($from_segment_start, $from_segment_end, $to_segment_start, $to_segment_end) = $job; - $from_segment_len = $from_segment_end - $from_segment_start; - $to_segment_len = $to_segment_end - $to_segment_start; - - // catch easy cases first - if ( !$from_segment_len || !$to_segment_len ) { - if ( $from_segment_len ) { - $result[$from_segment_start * 4 + 0] = new FineDiffDeleteOp($from_segment_len); - } - else if ( $to_segment_len ) { - $result[$from_segment_start * 4 + 1] = new FineDiffInsertOp(substr($to_text, $to_segment_start, $to_segment_len)); - } - continue; - } - if ( $from_segment_len >= $to_segment_len ) { - $copy_len = $to_segment_len; - while ( $copy_len ) { - $to_copy_start = $to_segment_start; - $to_copy_start_max = $to_segment_end - $copy_len; - while ( $to_copy_start <= $to_copy_start_max ) { - $from_copy_start = strpos(substr($from_text, $from_segment_start, $from_segment_len), substr($to_text, $to_copy_start, $copy_len)); - if ( $from_copy_start !== false ) { - $from_copy_start += $from_segment_start; - break 2; - } - $to_copy_start++; - } - $copy_len--; - } - } - else { - $copy_len = $from_segment_len; - while ( $copy_len ) { - $from_copy_start = $from_segment_start; - $from_copy_start_max = $from_segment_end - $copy_len; - while ( $from_copy_start <= $from_copy_start_max ) { - $to_copy_start = strpos(substr($to_text, $to_segment_start, $to_segment_len), substr($from_text, $from_copy_start, $copy_len)); - if ( $to_copy_start !== false ) { - $to_copy_start += $to_segment_start; - break 2; - } - $from_copy_start++; - } - $copy_len--; - } - } - // match found - if ( $copy_len ) { - $jobs[] = array($from_segment_start, $from_copy_start, $to_segment_start, $to_copy_start); - $result[$from_copy_start * 4 + 2] = new FineDiffCopyOp($copy_len); - $jobs[] = array($from_copy_start + $copy_len, $from_segment_end, $to_copy_start + $copy_len, $to_segment_end); - } - // no match, so delete all, insert all - else { - $result[$from_segment_start * 4] = new FineDiffReplaceOp($from_segment_len, substr($to_text, $to_segment_start, $to_segment_len)); - } - } - ksort($result, SORT_NUMERIC); - return array_values($result); - } - - /** - * Efficiently fragment the text into an array according to - * specified delimiters. - * No delimiters means fragment into single character. - * The array indices are the offset of the fragments into - * the input string. - * A sentinel empty fragment is always added at the end. - * Careful: No check is performed as to the validity of the - * delimiters. - */ - private static function extractFragments($text, $delimiters) { - // special case: split into characters - if ( empty($delimiters) ) { - $chars = str_split($text, 1); - $chars[strlen($text)] = ''; - return $chars; - } - $fragments = array(); - $start = $end = 0; - for (;;) { - $end += strcspn($text, $delimiters, $end); - $end += strspn($text, $delimiters, $end); - if ( $end === $start ) { - break; - } - $fragments[$start] = substr($text, $start, $end - $start); - $start = $end; - } - $fragments[$start] = ''; - return $fragments; - } - - /** - * Stock opcode renderers - */ - private static function renderToTextFromOpcode($opcode, $from, $from_offset, $from_len) { - if ( $opcode === 'c' || $opcode === 'i' ) { - return substr($from, $from_offset, $from_len); - } - return ''; - } - - private static function renderDiffToHTMLFromOpcode($opcode, $from, $from_offset, $from_len) { - if ( $opcode === 'c' ) { - return htmlentities(substr($from, $from_offset, $from_len)); - } - else if ( $opcode === 'd' ) { - $deletion = substr($from, $from_offset, $from_len); - if ( strcspn($deletion, " \n\r") === 0 ) { - $deletion = str_replace(array("\n","\r"), array('\n','\r'), $deletion); - } - return '' . htmlentities($deletion) . ''; - } - else /* if ( $opcode === 'i' ) */ { - return '' . htmlentities(substr($from, $from_offset, $from_len)) . ''; - } - return ''; - } - } - diff --git a/lib/drivers/kolab/plugins/libkolab/vendor/finediff_modifications.diff b/lib/drivers/kolab/plugins/libkolab/vendor/finediff_modifications.diff deleted file mode 100644 index 3a9ad5c..0000000 --- a/lib/drivers/kolab/plugins/libkolab/vendor/finediff_modifications.diff +++ /dev/null @@ -1,121 +0,0 @@ ---- finediff.php.orig 2014-07-29 14:24:10.000000000 +0200 -+++ finediff.php 2014-07-29 14:30:38.000000000 +0200 -@@ -234,25 +234,25 @@ - - public function renderDiffToHTML() { - $in_offset = 0; -- ob_start(); -+ $html = ''; - foreach ( $this->edits as $edit ) { - $n = $edit->getFromLen(); - if ( $edit instanceof FineDiffCopyOp ) { -- FineDiff::renderDiffToHTMLFromOpcode('c', $this->from_text, $in_offset, $n); -+ $html .= FineDiff::renderDiffToHTMLFromOpcode('c', $this->from_text, $in_offset, $n); - } - else if ( $edit instanceof FineDiffDeleteOp ) { -- FineDiff::renderDiffToHTMLFromOpcode('d', $this->from_text, $in_offset, $n); -+ $html .= FineDiff::renderDiffToHTMLFromOpcode('d', $this->from_text, $in_offset, $n); - } - else if ( $edit instanceof FineDiffInsertOp ) { -- FineDiff::renderDiffToHTMLFromOpcode('i', $edit->getText(), 0, $edit->getToLen()); -+ $html .= FineDiff::renderDiffToHTMLFromOpcode('i', $edit->getText(), 0, $edit->getToLen()); - } - else /* if ( $edit instanceof FineDiffReplaceOp ) */ { -- FineDiff::renderDiffToHTMLFromOpcode('d', $this->from_text, $in_offset, $n); -- FineDiff::renderDiffToHTMLFromOpcode('i', $edit->getText(), 0, $edit->getToLen()); -+ $html .= FineDiff::renderDiffToHTMLFromOpcode('d', $this->from_text, $in_offset, $n); -+ $html .= FineDiff::renderDiffToHTMLFromOpcode('i', $edit->getText(), 0, $edit->getToLen()); - } - $in_offset += $n; - } -- return ob_get_clean(); -+ return $html; - } - - /**------------------------------------------------------------------------ -@@ -277,18 +277,14 @@ - * Re-create the "To" string from the "From" string and an "Opcodes" string - */ - public static function renderToTextFromOpcodes($from, $opcodes) { -- ob_start(); -- FineDiff::renderFromOpcodes($from, $opcodes, array('FineDiff','renderToTextFromOpcode')); -- return ob_get_clean(); -+ return FineDiff::renderFromOpcodes($from, $opcodes, array('FineDiff','renderToTextFromOpcode')); - } - - /**------------------------------------------------------------------------ - * Render the diff to an HTML string - */ - public static function renderDiffToHTMLFromOpcodes($from, $opcodes) { -- ob_start(); -- FineDiff::renderFromOpcodes($from, $opcodes, array('FineDiff','renderDiffToHTMLFromOpcode')); -- return ob_get_clean(); -+ return FineDiff::renderFromOpcodes($from, $opcodes, array('FineDiff','renderDiffToHTMLFromOpcode')); - } - - /**------------------------------------------------------------------------ -@@ -297,8 +293,9 @@ - */ - public static function renderFromOpcodes($from, $opcodes, $callback) { - if ( !is_callable($callback) ) { -- return; -+ return ''; - } -+ $out = ''; - $opcodes_len = strlen($opcodes); - $from_offset = $opcodes_offset = 0; - while ( $opcodes_offset < $opcodes_len ) { -@@ -312,18 +309,19 @@ - $n = 1; - } - if ( $opcode === 'c' ) { // copy n characters from source -- call_user_func($callback, 'c', $from, $from_offset, $n, ''); -+ $out .= call_user_func($callback, 'c', $from, $from_offset, $n, ''); - $from_offset += $n; - } - else if ( $opcode === 'd' ) { // delete n characters from source -- call_user_func($callback, 'd', $from, $from_offset, $n, ''); -+ $out .= call_user_func($callback, 'd', $from, $from_offset, $n, ''); - $from_offset += $n; - } - else /* if ( $opcode === 'i' ) */ { // insert n characters from opcodes -- call_user_func($callback, 'i', $opcodes, $opcodes_offset + 1, $n); -+ $out .= call_user_func($callback, 'i', $opcodes, $opcodes_offset + 1, $n); - $opcodes_offset += 1 + $n; - } - } -+ return $out; - } - - /** -@@ -665,24 +663,26 @@ - */ - private static function renderToTextFromOpcode($opcode, $from, $from_offset, $from_len) { - if ( $opcode === 'c' || $opcode === 'i' ) { -- echo substr($from, $from_offset, $from_len); -+ return substr($from, $from_offset, $from_len); - } -+ return ''; - } - - private static function renderDiffToHTMLFromOpcode($opcode, $from, $from_offset, $from_len) { - if ( $opcode === 'c' ) { -- echo htmlentities(substr($from, $from_offset, $from_len)); -+ return htmlentities(substr($from, $from_offset, $from_len)); - } - else if ( $opcode === 'd' ) { - $deletion = substr($from, $from_offset, $from_len); - if ( strcspn($deletion, " \n\r") === 0 ) { - $deletion = str_replace(array("\n","\r"), array('\n','\r'), $deletion); - } -- echo '', htmlentities($deletion), ''; -+ return '' . htmlentities($deletion) . ''; - } - else /* if ( $opcode === 'i' ) */ { -- echo '', htmlentities(substr($from, $from_offset, $from_len)), ''; -+ return '' . htmlentities(substr($from, $from_offset, $from_len)) . ''; - } -+ return ''; - } - } - diff --git a/lib/ext/Auth/SASL.php b/lib/ext/Auth/SASL.php deleted file mode 100644 index b2be93c..0000000 --- a/lib/ext/Auth/SASL.php +++ /dev/null @@ -1,104 +0,0 @@ - | -// +-----------------------------------------------------------------------+ -// -// $Id$ - -/** -* Client implementation of various SASL mechanisms -* -* @author Richard Heyes -* @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 - * Types are not case sensitive - */ - 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': - $filename = 'Auth/SASL/CramMD5.php'; - $classname = 'Auth_SASL_CramMD5'; - break; - - case 'digestmd5': - $filename = 'Auth/SASL/DigestMD5.php'; - $classname = 'Auth_SASL_DigestMD5'; - break; - - default: - return PEAR::raiseError('Invalid SASL mechanism type'); - break; - } - - require_once($filename); - $obj = new $classname(); - return $obj; - } -} - -?> diff --git a/lib/ext/Auth/SASL/Anonymous.php b/lib/ext/Auth/SASL/Anonymous.php deleted file mode 100644 index 0811909..0000000 --- a/lib/ext/Auth/SASL/Anonymous.php +++ /dev/null @@ -1,71 +0,0 @@ - | -// +-----------------------------------------------------------------------+ -// -// $Id$ - -/** -* Implmentation of ANONYMOUS SASL mechanism -* -* @author Richard Heyes -* @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; - } -} -?> \ No newline at end of file diff --git a/lib/ext/Auth/SASL/Common.php b/lib/ext/Auth/SASL/Common.php deleted file mode 100644 index e7a18e2..0000000 --- a/lib/ext/Auth/SASL/Common.php +++ /dev/null @@ -1,74 +0,0 @@ - | -// +-----------------------------------------------------------------------+ -// -// $Id$ - -/** -* Common functionality to SASL mechanisms -* -* @author Richard Heyes -* @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 protect - * @return string The HMAC MD5 digest - */ - function _HMAC_MD5($key, $data) - { - 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); - - return $digest; - } -} -?> diff --git a/lib/ext/Auth/SASL/CramMD5.php b/lib/ext/Auth/SASL/CramMD5.php deleted file mode 100644 index d3fbf17..0000000 --- a/lib/ext/Auth/SASL/CramMD5.php +++ /dev/null @@ -1,68 +0,0 @@ - | -// +-----------------------------------------------------------------------+ -// -// $Id$ - -/** -* Implmentation of CRAM-MD5 SASL mechanism -* -* @author Richard Heyes -* @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 - * " ". This is NOT base64_encoded. - */ - function getResponse($user, $pass, $challenge) - { - return $user . ' ' . $this->_HMAC_MD5($pass, $challenge); - } -} -?> \ No newline at end of file diff --git a/lib/ext/Auth/SASL/DigestMD5.php b/lib/ext/Auth/SASL/DigestMD5.php deleted file mode 100644 index 07007b7..0000000 --- a/lib/ext/Auth/SASL/DigestMD5.php +++ /dev/null @@ -1,197 +0,0 @@ - | -// +-----------------------------------------------------------------------+ -// -// $Id$ - -/** -* Implmentation of DIGEST-MD5 SASL mechanism -* -* @author Richard Heyes -* @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-]+)=("[^"]+(? diff --git a/lib/ext/Auth/SASL/External.php b/lib/ext/Auth/SASL/External.php deleted file mode 100644 index 86a17cb..0000000 --- a/lib/ext/Auth/SASL/External.php +++ /dev/null @@ -1,63 +0,0 @@ - | -// +-----------------------------------------------------------------------+ -// -// $Id: External.php 286825 2009-08-05 06:23:42Z cweiske $ - -/** -* Implmentation of EXTERNAL SASL mechanism -* -* @author Christoph Schulz -* @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; - } -} -?> diff --git a/lib/ext/Auth/SASL/Login.php b/lib/ext/Auth/SASL/Login.php deleted file mode 100644 index 918daee..0000000 --- a/lib/ext/Auth/SASL/Login.php +++ /dev/null @@ -1,65 +0,0 @@ - | -// +-----------------------------------------------------------------------+ -// -// $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 -* @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); - } -} -?> \ No newline at end of file diff --git a/lib/ext/Auth/SASL/Plain.php b/lib/ext/Auth/SASL/Plain.php deleted file mode 100644 index 57894d0..0000000 --- a/lib/ext/Auth/SASL/Plain.php +++ /dev/null @@ -1,63 +0,0 @@ - | -// +-----------------------------------------------------------------------+ -// -// $Id$ - -/** -* Implmentation of PLAIN SASL mechanism -* -* @author Richard Heyes -* @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; - } -} -?> diff --git a/lib/ext/HTTP/Request2.php b/lib/ext/HTTP/Request2.php deleted file mode 100644 index 6d8d953..0000000 --- a/lib/ext/HTTP/Request2.php +++ /dev/null @@ -1,1015 +0,0 @@ - - * 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. - * * 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. - * - * @category HTTP - * @package HTTP_Request2 - * @author Alexey Borzov - * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version SVN: $Id: Request2.php 315409 2011-08-24 07:29:23Z avb $ - * @link http://pear.php.net/package/HTTP_Request2 - */ - -/** - * A class representing an URL as per RFC 3986. - */ -require_once 'Net/URL2.php'; - -/** - * Exception class for HTTP_Request2 package - */ -require_once 'HTTP/Request2/Exception.php'; - -/** - * Class representing a HTTP request message - * - * @category HTTP - * @package HTTP_Request2 - * @author Alexey Borzov - * @version Release: 2.0.0 - * @link http://tools.ietf.org/html/rfc2616#section-5 - */ -class HTTP_Request2 implements SplSubject -{ - /**#@+ - * Constants for HTTP request methods - * - * @link http://tools.ietf.org/html/rfc2616#section-5.1.1 - */ - const METHOD_OPTIONS = 'OPTIONS'; - const METHOD_GET = 'GET'; - const METHOD_HEAD = 'HEAD'; - const METHOD_POST = 'POST'; - const METHOD_PUT = 'PUT'; - const METHOD_DELETE = 'DELETE'; - const METHOD_TRACE = 'TRACE'; - const METHOD_CONNECT = 'CONNECT'; - /**#@-*/ - - /**#@+ - * Constants for HTTP authentication schemes - * - * @link http://tools.ietf.org/html/rfc2617 - */ - const AUTH_BASIC = 'basic'; - const AUTH_DIGEST = 'digest'; - /**#@-*/ - - /** - * Regular expression used to check for invalid symbols in RFC 2616 tokens - * @link http://pear.php.net/bugs/bug.php?id=15630 - */ - const REGEXP_INVALID_TOKEN = '![\x00-\x1f\x7f-\xff()<>@,;:\\\\"/\[\]?={}\s]!'; - - /** - * Regular expression used to check for invalid symbols in cookie strings - * @link http://pear.php.net/bugs/bug.php?id=15630 - * @link http://web.archive.org/web/20080331104521/http://cgi.netscape.com/newsref/std/cookie_spec.html - */ - const REGEXP_INVALID_COOKIE = '/[\s,;]/'; - - /** - * Fileinfo magic database resource - * @var resource - * @see detectMimeType() - */ - private static $_fileinfoDb; - - /** - * Observers attached to the request (instances of SplObserver) - * @var array - */ - protected $observers = array(); - - /** - * Request URL - * @var Net_URL2 - */ - protected $url; - - /** - * Request method - * @var string - */ - protected $method = self::METHOD_GET; - - /** - * Authentication data - * @var array - * @see getAuth() - */ - protected $auth; - - /** - * Request headers - * @var array - */ - protected $headers = array(); - - /** - * Configuration parameters - * @var array - * @see setConfig() - */ - protected $config = array( - 'adapter' => 'HTTP_Request2_Adapter_Socket', - 'connect_timeout' => 10, - 'timeout' => 0, - 'use_brackets' => true, - 'protocol_version' => '1.1', - 'buffer_size' => 16384, - 'store_body' => true, - - 'proxy_host' => '', - 'proxy_port' => '', - 'proxy_user' => '', - 'proxy_password' => '', - 'proxy_auth_scheme' => self::AUTH_BASIC, - - 'ssl_verify_peer' => true, - 'ssl_verify_host' => true, - 'ssl_cafile' => null, - 'ssl_capath' => null, - 'ssl_local_cert' => null, - 'ssl_passphrase' => null, - - 'digest_compat_ie' => false, - - 'follow_redirects' => false, - 'max_redirects' => 5, - 'strict_redirects' => false - ); - - /** - * Last event in request / response handling, intended for observers - * @var array - * @see getLastEvent() - */ - protected $lastEvent = array( - 'name' => 'start', - 'data' => null - ); - - /** - * Request body - * @var string|resource - * @see setBody() - */ - protected $body = ''; - - /** - * Array of POST parameters - * @var array - */ - protected $postParams = array(); - - /** - * Array of file uploads (for multipart/form-data POST requests) - * @var array - */ - protected $uploads = array(); - - /** - * Adapter used to perform actual HTTP request - * @var HTTP_Request2_Adapter - */ - protected $adapter; - - /** - * Cookie jar to persist cookies between requests - * @var HTTP_Request2_CookieJar - */ - protected $cookieJar = null; - - /** - * Constructor. Can set request URL, method and configuration array. - * - * Also sets a default value for User-Agent header. - * - * @param string|Net_Url2 Request URL - * @param string Request method - * @param array Configuration for this Request instance - */ - public function __construct($url = null, $method = self::METHOD_GET, array $config = array()) - { - $this->setConfig($config); - if (!empty($url)) { - $this->setUrl($url); - } - if (!empty($method)) { - $this->setMethod($method); - } - $this->setHeader('user-agent', 'HTTP_Request2/2.0.0 ' . - '(http://pear.php.net/package/http_request2) ' . - 'PHP/' . phpversion()); - } - - /** - * Sets the URL for this request - * - * If the URL has userinfo part (username & password) these will be removed - * and converted to auth data. If the URL does not have a path component, - * that will be set to '/'. - * - * @param string|Net_URL2 Request URL - * @return HTTP_Request2 - * @throws HTTP_Request2_LogicException - */ - public function setUrl($url) - { - if (is_string($url)) { - $url = new Net_URL2( - $url, array(Net_URL2::OPTION_USE_BRACKETS => $this->config['use_brackets']) - ); - } - if (!$url instanceof Net_URL2) { - throw new HTTP_Request2_LogicException( - 'Parameter is not a valid HTTP URL', - HTTP_Request2_Exception::INVALID_ARGUMENT - ); - } - // URL contains username / password? - if ($url->getUserinfo()) { - $username = $url->getUser(); - $password = $url->getPassword(); - $this->setAuth(rawurldecode($username), $password? rawurldecode($password): ''); - $url->setUserinfo(''); - } - if ('' == $url->getPath()) { - $url->setPath('/'); - } - $this->url = $url; - - return $this; - } - - /** - * Returns the request URL - * - * @return Net_URL2 - */ - public function getUrl() - { - return $this->url; - } - - /** - * Sets the request method - * - * @param string - * @return HTTP_Request2 - * @throws HTTP_Request2_LogicException if the method name is invalid - */ - public function setMethod($method) - { - // Method name should be a token: http://tools.ietf.org/html/rfc2616#section-5.1.1 - if (preg_match(self::REGEXP_INVALID_TOKEN, $method)) { - throw new HTTP_Request2_LogicException( - "Invalid request method '{$method}'", - HTTP_Request2_Exception::INVALID_ARGUMENT - ); - } - $this->method = $method; - - return $this; - } - - /** - * Returns the request method - * - * @return string - */ - public function getMethod() - { - return $this->method; - } - - /** - * Sets the configuration parameter(s) - * - * The following parameters are available: - *
      - *
    • 'adapter' - adapter to use (string)
    • - *
    • 'connect_timeout' - Connection timeout in seconds (integer)
    • - *
    • 'timeout' - Total number of seconds a request can take. - * Use 0 for no limit, should be greater than - * 'connect_timeout' if set (integer)
    • - *
    • 'use_brackets' - Whether to append [] to array variable names (bool)
    • - *
    • 'protocol_version' - HTTP Version to use, '1.0' or '1.1' (string)
    • - *
    • 'buffer_size' - Buffer size to use for reading and writing (int)
    • - *
    • 'store_body' - Whether to store response body in response object. - * Set to false if receiving a huge response and - * using an Observer to save it (boolean)
    • - *
    • 'proxy_host' - Proxy server host (string)
    • - *
    • 'proxy_port' - Proxy server port (integer)
    • - *
    • 'proxy_user' - Proxy auth username (string)
    • - *
    • 'proxy_password' - Proxy auth password (string)
    • - *
    • 'proxy_auth_scheme' - Proxy auth scheme, one of HTTP_Request2::AUTH_* constants (string)
    • - *
    • 'ssl_verify_peer' - Whether to verify peer's SSL certificate (bool)
    • - *
    • 'ssl_verify_host' - Whether to check that Common Name in SSL - * certificate matches host name (bool)
    • - *
    • 'ssl_cafile' - Cerificate Authority file to verify the peer - * with (use with 'ssl_verify_peer') (string)
    • - *
    • 'ssl_capath' - Directory holding multiple Certificate - * Authority files (string)
    • - *
    • 'ssl_local_cert' - Name of a file containing local cerificate (string)
    • - *
    • 'ssl_passphrase' - Passphrase with which local certificate - * was encoded (string)
    • - *
    • 'digest_compat_ie' - Whether to imitate behaviour of MSIE 5 and 6 - * in using URL without query string in digest - * authentication (boolean)
    • - *
    • 'follow_redirects' - Whether to automatically follow HTTP Redirects (boolean)
    • - *
    • 'max_redirects' - Maximum number of redirects to follow (integer)
    • - *
    • 'strict_redirects' - Whether to keep request method on redirects via status 301 and - * 302 (true, needed for compatibility with RFC 2616) - * or switch to GET (false, needed for compatibility with most - * browsers) (boolean)
    • - *
    - * - * @param string|array configuration parameter name or array - * ('parameter name' => 'parameter value') - * @param mixed parameter value if $nameOrConfig is not an array - * @return HTTP_Request2 - * @throws HTTP_Request2_LogicException If the parameter is unknown - */ - public function setConfig($nameOrConfig, $value = null) - { - if (is_array($nameOrConfig)) { - foreach ($nameOrConfig as $name => $value) { - $this->setConfig($name, $value); - } - - } else { - if (!array_key_exists($nameOrConfig, $this->config)) { - throw new HTTP_Request2_LogicException( - "Unknown configuration parameter '{$nameOrConfig}'", - HTTP_Request2_Exception::INVALID_ARGUMENT - ); - } - $this->config[$nameOrConfig] = $value; - } - - return $this; - } - - /** - * Returns the value(s) of the configuration parameter(s) - * - * @param string parameter name - * @return mixed value of $name parameter, array of all configuration - * parameters if $name is not given - * @throws HTTP_Request2_LogicException If the parameter is unknown - */ - public function getConfig($name = null) - { - if (null === $name) { - return $this->config; - } elseif (!array_key_exists($name, $this->config)) { - throw new HTTP_Request2_LogicException( - "Unknown configuration parameter '{$name}'", - HTTP_Request2_Exception::INVALID_ARGUMENT - ); - } - return $this->config[$name]; - } - - /** - * Sets the autentification data - * - * @param string user name - * @param string password - * @param string authentication scheme - * @return HTTP_Request2 - */ - public function setAuth($user, $password = '', $scheme = self::AUTH_BASIC) - { - if (empty($user)) { - $this->auth = null; - } else { - $this->auth = array( - 'user' => (string)$user, - 'password' => (string)$password, - 'scheme' => $scheme - ); - } - - return $this; - } - - /** - * Returns the authentication data - * - * The array has the keys 'user', 'password' and 'scheme', where 'scheme' - * is one of the HTTP_Request2::AUTH_* constants. - * - * @return array - */ - public function getAuth() - { - return $this->auth; - } - - /** - * Sets request header(s) - * - * The first parameter may be either a full header string 'header: value' or - * header name. In the former case $value parameter is ignored, in the latter - * the header's value will either be set to $value or the header will be - * removed if $value is null. The first parameter can also be an array of - * headers, in that case method will be called recursively. - * - * Note that headers are treated case insensitively as per RFC 2616. - * - * - * $req->setHeader('Foo: Bar'); // sets the value of 'Foo' header to 'Bar' - * $req->setHeader('FoO', 'Baz'); // sets the value of 'Foo' header to 'Baz' - * $req->setHeader(array('foo' => 'Quux')); // sets the value of 'Foo' header to 'Quux' - * $req->setHeader('FOO'); // removes 'Foo' header from request - * - * - * @param string|array header name, header string ('Header: value') - * or an array of headers - * @param string|array|null header value if $name is not an array, - * header will be removed if value is null - * @param bool whether to replace previous header with the - * same name or append to its value - * @return HTTP_Request2 - * @throws HTTP_Request2_LogicException - */ - public function setHeader($name, $value = null, $replace = true) - { - if (is_array($name)) { - foreach ($name as $k => $v) { - if (is_string($k)) { - $this->setHeader($k, $v, $replace); - } else { - $this->setHeader($v, null, $replace); - } - } - } else { - if (null === $value && strpos($name, ':')) { - list($name, $value) = array_map('trim', explode(':', $name, 2)); - } - // Header name should be a token: http://tools.ietf.org/html/rfc2616#section-4.2 - if (preg_match(self::REGEXP_INVALID_TOKEN, $name)) { - throw new HTTP_Request2_LogicException( - "Invalid header name '{$name}'", - HTTP_Request2_Exception::INVALID_ARGUMENT - ); - } - // Header names are case insensitive anyway - $name = strtolower($name); - if (null === $value) { - unset($this->headers[$name]); - - } else { - if (is_array($value)) { - $value = implode(', ', array_map('trim', $value)); - } elseif (is_string($value)) { - $value = trim($value); - } - if (!isset($this->headers[$name]) || $replace) { - $this->headers[$name] = $value; - } else { - $this->headers[$name] .= ', ' . $value; - } - } - } - - return $this; - } - - /** - * Returns the request headers - * - * The array is of the form ('header name' => 'header value'), header names - * are lowercased - * - * @return array - */ - public function getHeaders() - { - return $this->headers; - } - - /** - * Adds a cookie to the request - * - * If the request does not have a CookieJar object set, this method simply - * appends a cookie to "Cookie:" header. - * - * If a CookieJar object is available, the cookie is stored in that object. - * Data from request URL will be used for setting its 'domain' and 'path' - * parameters, 'expires' and 'secure' will be set to null and false, - * respectively. If you need further control, use CookieJar's methods. - * - * @param string cookie name - * @param string cookie value - * @return HTTP_Request2 - * @throws HTTP_Request2_LogicException - * @see setCookieJar() - */ - public function addCookie($name, $value) - { - if (!empty($this->cookieJar)) { - $this->cookieJar->store(array('name' => $name, 'value' => $value), - $this->url); - - } else { - $cookie = $name . '=' . $value; - if (preg_match(self::REGEXP_INVALID_COOKIE, $cookie)) { - throw new HTTP_Request2_LogicException( - "Invalid cookie: '{$cookie}'", - HTTP_Request2_Exception::INVALID_ARGUMENT - ); - } - $cookies = empty($this->headers['cookie'])? '': $this->headers['cookie'] . '; '; - $this->setHeader('cookie', $cookies . $cookie); - } - - return $this; - } - - /** - * Sets the request body - * - * If you provide file pointer rather than file name, it should support - * fstat() and rewind() operations. - * - * @param string|resource|HTTP_Request2_MultipartBody Either a string - * with the body or filename containing body or pointer to - * an open file or object with multipart body data - * @param bool Whether first parameter is a filename - * @return HTTP_Request2 - * @throws HTTP_Request2_LogicException - */ - public function setBody($body, $isFilename = false) - { - if (!$isFilename && !is_resource($body)) { - if (!$body instanceof HTTP_Request2_MultipartBody) { - $this->body = (string)$body; - } else { - $this->body = $body; - } - } else { - $fileData = $this->fopenWrapper($body, empty($this->headers['content-type'])); - $this->body = $fileData['fp']; - if (empty($this->headers['content-type'])) { - $this->setHeader('content-type', $fileData['type']); - } - } - $this->postParams = $this->uploads = array(); - - return $this; - } - - /** - * Returns the request body - * - * @return string|resource|HTTP_Request2_MultipartBody - */ - public function getBody() - { - if (self::METHOD_POST == $this->method && - (!empty($this->postParams) || !empty($this->uploads)) - ) { - if (0 === strpos($this->headers['content-type'], 'application/x-www-form-urlencoded')) { - $body = http_build_query($this->postParams, '', '&'); - if (!$this->getConfig('use_brackets')) { - $body = preg_replace('/%5B\d+%5D=/', '=', $body); - } - // support RFC 3986 by not encoding '~' symbol (request #15368) - return str_replace('%7E', '~', $body); - - } elseif (0 === strpos($this->headers['content-type'], 'multipart/form-data')) { - require_once 'HTTP/Request2/MultipartBody.php'; - return new HTTP_Request2_MultipartBody( - $this->postParams, $this->uploads, $this->getConfig('use_brackets') - ); - } - } - return $this->body; - } - - /** - * Adds a file to form-based file upload - * - * Used to emulate file upload via a HTML form. The method also sets - * Content-Type of HTTP request to 'multipart/form-data'. - * - * If you just want to send the contents of a file as the body of HTTP - * request you should use setBody() method. - * - * If you provide file pointers rather than file names, they should support - * fstat() and rewind() operations. - * - * @param string name of file-upload field - * @param string|resource|array full name of local file, pointer to - * open file or an array of files - * @param string filename to send in the request - * @param string content-type of file being uploaded - * @return HTTP_Request2 - * @throws HTTP_Request2_LogicException - */ - public function addUpload($fieldName, $filename, $sendFilename = null, - $contentType = null) - { - if (!is_array($filename)) { - $fileData = $this->fopenWrapper($filename, empty($contentType)); - $this->uploads[$fieldName] = array( - 'fp' => $fileData['fp'], - 'filename' => !empty($sendFilename)? $sendFilename - :(is_string($filename)? basename($filename): 'anonymous.blob') , - 'size' => $fileData['size'], - 'type' => empty($contentType)? $fileData['type']: $contentType - ); - } else { - $fps = $names = $sizes = $types = array(); - foreach ($filename as $f) { - if (!is_array($f)) { - $f = array($f); - } - $fileData = $this->fopenWrapper($f[0], empty($f[2])); - $fps[] = $fileData['fp']; - $names[] = !empty($f[1])? $f[1] - :(is_string($f[0])? basename($f[0]): 'anonymous.blob'); - $sizes[] = $fileData['size']; - $types[] = empty($f[2])? $fileData['type']: $f[2]; - } - $this->uploads[$fieldName] = array( - 'fp' => $fps, 'filename' => $names, 'size' => $sizes, 'type' => $types - ); - } - if (empty($this->headers['content-type']) || - 'application/x-www-form-urlencoded' == $this->headers['content-type'] - ) { - $this->setHeader('content-type', 'multipart/form-data'); - } - - return $this; - } - - /** - * Adds POST parameter(s) to the request. - * - * @param string|array parameter name or array ('name' => 'value') - * @param mixed parameter value (can be an array) - * @return HTTP_Request2 - */ - public function addPostParameter($name, $value = null) - { - if (!is_array($name)) { - $this->postParams[$name] = $value; - } else { - foreach ($name as $k => $v) { - $this->addPostParameter($k, $v); - } - } - if (empty($this->headers['content-type'])) { - $this->setHeader('content-type', 'application/x-www-form-urlencoded'); - } - - return $this; - } - - /** - * Attaches a new observer - * - * @param SplObserver - */ - public function attach(SplObserver $observer) - { - foreach ($this->observers as $attached) { - if ($attached === $observer) { - return; - } - } - $this->observers[] = $observer; - } - - /** - * Detaches an existing observer - * - * @param SplObserver - */ - public function detach(SplObserver $observer) - { - foreach ($this->observers as $key => $attached) { - if ($attached === $observer) { - unset($this->observers[$key]); - return; - } - } - } - - /** - * Notifies all observers - */ - public function notify() - { - foreach ($this->observers as $observer) { - $observer->update($this); - } - } - - /** - * Sets the last event - * - * Adapters should use this method to set the current state of the request - * and notify the observers. - * - * @param string event name - * @param mixed event data - */ - public function setLastEvent($name, $data = null) - { - $this->lastEvent = array( - 'name' => $name, - 'data' => $data - ); - $this->notify(); - } - - /** - * Returns the last event - * - * Observers should use this method to access the last change in request. - * The following event names are possible: - *
      - *
    • 'connect' - after connection to remote server, - * data is the destination (string)
    • - *
    • 'disconnect' - after disconnection from server
    • - *
    • 'sentHeaders' - after sending the request headers, - * data is the headers sent (string)
    • - *
    • 'sentBodyPart' - after sending a part of the request body, - * data is the length of that part (int)
    • - *
    • 'sentBody' - after sending the whole request body, - * data is request body length (int)
    • - *
    • 'receivedHeaders' - after receiving the response headers, - * data is HTTP_Request2_Response object
    • - *
    • 'receivedBodyPart' - after receiving a part of the response - * body, data is that part (string)
    • - *
    • 'receivedEncodedBodyPart' - as 'receivedBodyPart', but data is still - * encoded by Content-Encoding
    • - *
    • 'receivedBody' - after receiving the complete response - * body, data is HTTP_Request2_Response object
    • - *
    - * Different adapters may not send all the event types. Mock adapter does - * not send any events to the observers. - * - * @return array The array has two keys: 'name' and 'data' - */ - public function getLastEvent() - { - return $this->lastEvent; - } - - /** - * Sets the adapter used to actually perform the request - * - * You can pass either an instance of a class implementing HTTP_Request2_Adapter - * or a class name. The method will only try to include a file if the class - * name starts with HTTP_Request2_Adapter_, it will also try to prepend this - * prefix to the class name if it doesn't contain any underscores, so that - * - * $request->setAdapter('curl'); - * - * will work. - * - * @param string|HTTP_Request2_Adapter - * @return HTTP_Request2 - * @throws HTTP_Request2_LogicException - */ - public function setAdapter($adapter) - { - if (is_string($adapter)) { - if (!class_exists($adapter, false)) { - if (false === strpos($adapter, '_')) { - $adapter = 'HTTP_Request2_Adapter_' . ucfirst($adapter); - } - if (preg_match('/^HTTP_Request2_Adapter_([a-zA-Z0-9]+)$/', $adapter)) { - include_once str_replace('_', DIRECTORY_SEPARATOR, $adapter) . '.php'; - } - if (!class_exists($adapter, false)) { - throw new HTTP_Request2_LogicException( - "Class {$adapter} not found", - HTTP_Request2_Exception::MISSING_VALUE - ); - } - } - $adapter = new $adapter; - } - if (!$adapter instanceof HTTP_Request2_Adapter) { - throw new HTTP_Request2_LogicException( - 'Parameter is not a HTTP request adapter', - HTTP_Request2_Exception::INVALID_ARGUMENT - ); - } - $this->adapter = $adapter; - - return $this; - } - - /** - * Sets the cookie jar - * - * A cookie jar is used to maintain cookies across HTTP requests and - * responses. Cookies from jar will be automatically added to the request - * headers based on request URL. - * - * @param HTTP_Request2_CookieJar|bool Existing CookieJar object, true to - * create a new one, false to remove - */ - public function setCookieJar($jar = true) - { - if (!class_exists('HTTP_Request2_CookieJar', false)) { - require_once 'HTTP/Request2/CookieJar.php'; - } - - if ($jar instanceof HTTP_Request2_CookieJar) { - $this->cookieJar = $jar; - } elseif (true === $jar) { - $this->cookieJar = new HTTP_Request2_CookieJar(); - } elseif (!$jar) { - $this->cookieJar = null; - } else { - throw new HTTP_Request2_LogicException( - 'Invalid parameter passed to setCookieJar()', - HTTP_Request2_Exception::INVALID_ARGUMENT - ); - } - - return $this; - } - - /** - * Returns current CookieJar object or null if none - * - * @return HTTP_Request2_CookieJar|null - */ - public function getCookieJar() - { - return $this->cookieJar; - } - - /** - * Sends the request and returns the response - * - * @throws HTTP_Request2_Exception - * @return HTTP_Request2_Response - */ - public function send() - { - // Sanity check for URL - if (!$this->url instanceof Net_URL2 - || !$this->url->isAbsolute() - || !in_array(strtolower($this->url->getScheme()), array('https', 'http')) - ) { - throw new HTTP_Request2_LogicException( - 'HTTP_Request2 needs an absolute HTTP(S) request URL, ' - . ($this->url instanceof Net_URL2 - ? "'" . $this->url->__toString() . "'" : 'none') - . ' given', - HTTP_Request2_Exception::INVALID_ARGUMENT - ); - } - if (empty($this->adapter)) { - $this->setAdapter($this->getConfig('adapter')); - } - // magic_quotes_runtime may break file uploads and chunked response - // processing; see bug #4543. Don't use ini_get() here; see bug #16440. - if ($magicQuotes = get_magic_quotes_runtime()) { - set_magic_quotes_runtime(false); - } - // force using single byte encoding if mbstring extension overloads - // strlen() and substr(); see bug #1781, bug #10605 - if (extension_loaded('mbstring') && (2 & ini_get('mbstring.func_overload'))) { - $oldEncoding = mb_internal_encoding(); - mb_internal_encoding('iso-8859-1'); - } - - try { - $response = $this->adapter->sendRequest($this); - } catch (Exception $e) { - } - // cleanup in either case (poor man's "finally" clause) - if ($magicQuotes) { - set_magic_quotes_runtime(true); - } - if (!empty($oldEncoding)) { - mb_internal_encoding($oldEncoding); - } - // rethrow the exception - if (!empty($e)) { - throw $e; - } - return $response; - } - - /** - * Wrapper around fopen()/fstat() used by setBody() and addUpload() - * - * @param string|resource file name or pointer to open file - * @param bool whether to try autodetecting MIME type of file, - * will only work if $file is a filename, not pointer - * @return array array('fp' => file pointer, 'size' => file size, 'type' => MIME type) - * @throws HTTP_Request2_LogicException - */ - protected function fopenWrapper($file, $detectType = false) - { - if (!is_string($file) && !is_resource($file)) { - throw new HTTP_Request2_LogicException( - "Filename or file pointer resource expected", - HTTP_Request2_Exception::INVALID_ARGUMENT - ); - } - $fileData = array( - 'fp' => is_string($file)? null: $file, - 'type' => 'application/octet-stream', - 'size' => 0 - ); - if (is_string($file)) { - $track = @ini_set('track_errors', 1); - if (!($fileData['fp'] = @fopen($file, 'rb'))) { - $e = new HTTP_Request2_LogicException( - $php_errormsg, HTTP_Request2_Exception::READ_ERROR - ); - } - @ini_set('track_errors', $track); - if (isset($e)) { - throw $e; - } - if ($detectType) { - $fileData['type'] = self::detectMimeType($file); - } - } - if (!($stat = fstat($fileData['fp']))) { - throw new HTTP_Request2_LogicException( - "fstat() call failed", HTTP_Request2_Exception::READ_ERROR - ); - } - $fileData['size'] = $stat['size']; - - return $fileData; - } - - /** - * Tries to detect MIME type of a file - * - * The method will try to use fileinfo extension if it is available, - * deprecated mime_content_type() function in the other case. If neither - * works, default 'application/octet-stream' MIME type is returned - * - * @param string filename - * @return string file MIME type - */ - protected static function detectMimeType($filename) - { - // finfo extension from PECL available - if (function_exists('finfo_open')) { - if (!isset(self::$_fileinfoDb)) { - self::$_fileinfoDb = @finfo_open(FILEINFO_MIME); - } - if (self::$_fileinfoDb) { - $info = finfo_file(self::$_fileinfoDb, $filename); - } - } - // (deprecated) mime_content_type function available - if (empty($info) && function_exists('mime_content_type')) { - return mime_content_type($filename); - } - return empty($info)? 'application/octet-stream': $info; - } -} -?> diff --git a/lib/ext/HTTP/Request2/Adapter.php b/lib/ext/HTTP/Request2/Adapter.php deleted file mode 100644 index 56d81a9..0000000 --- a/lib/ext/HTTP/Request2/Adapter.php +++ /dev/null @@ -1,154 +0,0 @@ - - * 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. - * * 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. - * - * @category HTTP - * @package HTTP_Request2 - * @author Alexey Borzov - * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version SVN: $Id: Adapter.php 308322 2011-02-14 13:58:03Z avb $ - * @link http://pear.php.net/package/HTTP_Request2 - */ - -/** - * Class representing a HTTP response - */ -require_once 'HTTP/Request2/Response.php'; - -/** - * Base class for HTTP_Request2 adapters - * - * HTTP_Request2 class itself only defines methods for aggregating the request - * data, all actual work of sending the request to the remote server and - * receiving its response is performed by adapters. - * - * @category HTTP - * @package HTTP_Request2 - * @author Alexey Borzov - * @version Release: 2.0.0 - */ -abstract class HTTP_Request2_Adapter -{ - /** - * A list of methods that MUST NOT have a request body, per RFC 2616 - * @var array - */ - protected static $bodyDisallowed = array('TRACE'); - - /** - * Methods having defined semantics for request body - * - * Content-Length header (indicating that the body follows, section 4.3 of - * RFC 2616) will be sent for these methods even if no body was added - * - * @var array - * @link http://pear.php.net/bugs/bug.php?id=12900 - * @link http://pear.php.net/bugs/bug.php?id=14740 - */ - protected static $bodyRequired = array('POST', 'PUT'); - - /** - * Request being sent - * @var HTTP_Request2 - */ - protected $request; - - /** - * Request body - * @var string|resource|HTTP_Request2_MultipartBody - * @see HTTP_Request2::getBody() - */ - protected $requestBody; - - /** - * Length of the request body - * @var integer - */ - protected $contentLength; - - /** - * Sends request to the remote server and returns its response - * - * @param HTTP_Request2 - * @return HTTP_Request2_Response - * @throws HTTP_Request2_Exception - */ - abstract public function sendRequest(HTTP_Request2 $request); - - /** - * Calculates length of the request body, adds proper headers - * - * @param array associative array of request headers, this method will - * add proper 'Content-Length' and 'Content-Type' headers - * to this array (or remove them if not needed) - */ - protected function calculateRequestLength(&$headers) - { - $this->requestBody = $this->request->getBody(); - - if (is_string($this->requestBody)) { - $this->contentLength = strlen($this->requestBody); - } elseif (is_resource($this->requestBody)) { - $stat = fstat($this->requestBody); - $this->contentLength = $stat['size']; - rewind($this->requestBody); - } else { - $this->contentLength = $this->requestBody->getLength(); - $headers['content-type'] = 'multipart/form-data; boundary=' . - $this->requestBody->getBoundary(); - $this->requestBody->rewind(); - } - - if (in_array($this->request->getMethod(), self::$bodyDisallowed) || - 0 == $this->contentLength - ) { - // No body: send a Content-Length header nonetheless (request #12900), - // but do that only for methods that require a body (bug #14740) - if (in_array($this->request->getMethod(), self::$bodyRequired)) { - $headers['content-length'] = 0; - } else { - unset($headers['content-length']); - // if the method doesn't require a body and doesn't have a - // body, don't send a Content-Type header. (request #16799) - unset($headers['content-type']); - } - } else { - if (empty($headers['content-type'])) { - $headers['content-type'] = 'application/x-www-form-urlencoded'; - } - $headers['content-length'] = $this->contentLength; - } - } -} -?> diff --git a/lib/ext/HTTP/Request2/Adapter/Curl.php b/lib/ext/HTTP/Request2/Adapter/Curl.php deleted file mode 100644 index 82d227f..0000000 --- a/lib/ext/HTTP/Request2/Adapter/Curl.php +++ /dev/null @@ -1,560 +0,0 @@ - - * 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. - * * 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. - * - * @category HTTP - * @package HTTP_Request2 - * @author Alexey Borzov - * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version SVN: $Id: Curl.php 310800 2011-05-06 07:29:56Z avb $ - * @link http://pear.php.net/package/HTTP_Request2 - */ - -/** - * Base class for HTTP_Request2 adapters - */ -require_once 'HTTP/Request2/Adapter.php'; - -/** - * Adapter for HTTP_Request2 wrapping around cURL extension - * - * @category HTTP - * @package HTTP_Request2 - * @author Alexey Borzov - * @version Release: 2.0.0 - */ -class HTTP_Request2_Adapter_Curl extends HTTP_Request2_Adapter -{ - /** - * Mapping of header names to cURL options - * @var array - */ - protected static $headerMap = array( - 'accept-encoding' => CURLOPT_ENCODING, - 'cookie' => CURLOPT_COOKIE, - 'referer' => CURLOPT_REFERER, - 'user-agent' => CURLOPT_USERAGENT - ); - - /** - * Mapping of SSL context options to cURL options - * @var array - */ - protected static $sslContextMap = array( - 'ssl_verify_peer' => CURLOPT_SSL_VERIFYPEER, - 'ssl_cafile' => CURLOPT_CAINFO, - 'ssl_capath' => CURLOPT_CAPATH, - 'ssl_local_cert' => CURLOPT_SSLCERT, - 'ssl_passphrase' => CURLOPT_SSLCERTPASSWD - ); - - /** - * Mapping of CURLE_* constants to Exception subclasses and error codes - * @var array - */ - protected static $errorMap = array( - CURLE_UNSUPPORTED_PROTOCOL => array('HTTP_Request2_MessageException', - HTTP_Request2_Exception::NON_HTTP_REDIRECT), - CURLE_COULDNT_RESOLVE_PROXY => array('HTTP_Request2_ConnectionException'), - CURLE_COULDNT_RESOLVE_HOST => array('HTTP_Request2_ConnectionException'), - CURLE_COULDNT_CONNECT => array('HTTP_Request2_ConnectionException'), - // error returned from write callback - CURLE_WRITE_ERROR => array('HTTP_Request2_MessageException', - HTTP_Request2_Exception::NON_HTTP_REDIRECT), - CURLE_OPERATION_TIMEOUTED => array('HTTP_Request2_MessageException', - HTTP_Request2_Exception::TIMEOUT), - CURLE_HTTP_RANGE_ERROR => array('HTTP_Request2_MessageException'), - CURLE_SSL_CONNECT_ERROR => array('HTTP_Request2_ConnectionException'), - CURLE_LIBRARY_NOT_FOUND => array('HTTP_Request2_LogicException', - HTTP_Request2_Exception::MISCONFIGURATION), - CURLE_FUNCTION_NOT_FOUND => array('HTTP_Request2_LogicException', - HTTP_Request2_Exception::MISCONFIGURATION), - CURLE_ABORTED_BY_CALLBACK => array('HTTP_Request2_MessageException', - HTTP_Request2_Exception::NON_HTTP_REDIRECT), - CURLE_TOO_MANY_REDIRECTS => array('HTTP_Request2_MessageException', - HTTP_Request2_Exception::TOO_MANY_REDIRECTS), - CURLE_SSL_PEER_CERTIFICATE => array('HTTP_Request2_ConnectionException'), - CURLE_GOT_NOTHING => array('HTTP_Request2_MessageException'), - CURLE_SSL_ENGINE_NOTFOUND => array('HTTP_Request2_LogicException', - HTTP_Request2_Exception::MISCONFIGURATION), - CURLE_SSL_ENGINE_SETFAILED => array('HTTP_Request2_LogicException', - HTTP_Request2_Exception::MISCONFIGURATION), - CURLE_SEND_ERROR => array('HTTP_Request2_MessageException'), - CURLE_RECV_ERROR => array('HTTP_Request2_MessageException'), - CURLE_SSL_CERTPROBLEM => array('HTTP_Request2_LogicException', - HTTP_Request2_Exception::INVALID_ARGUMENT), - CURLE_SSL_CIPHER => array('HTTP_Request2_ConnectionException'), - CURLE_SSL_CACERT => array('HTTP_Request2_ConnectionException'), - CURLE_BAD_CONTENT_ENCODING => array('HTTP_Request2_MessageException'), - ); - - /** - * Response being received - * @var HTTP_Request2_Response - */ - protected $response; - - /** - * Whether 'sentHeaders' event was sent to observers - * @var boolean - */ - protected $eventSentHeaders = false; - - /** - * Whether 'receivedHeaders' event was sent to observers - * @var boolean - */ - protected $eventReceivedHeaders = false; - - /** - * Position within request body - * @var integer - * @see callbackReadBody() - */ - protected $position = 0; - - /** - * Information about last transfer, as returned by curl_getinfo() - * @var array - */ - protected $lastInfo; - - /** - * Creates a subclass of HTTP_Request2_Exception from curl error data - * - * @param resource curl handle - * @return HTTP_Request2_Exception - */ - protected static function wrapCurlError($ch) - { - $nativeCode = curl_errno($ch); - $message = 'Curl error: ' . curl_error($ch); - if (!isset(self::$errorMap[$nativeCode])) { - return new HTTP_Request2_Exception($message, 0, $nativeCode); - } else { - $class = self::$errorMap[$nativeCode][0]; - $code = empty(self::$errorMap[$nativeCode][1]) - ? 0 : self::$errorMap[$nativeCode][1]; - return new $class($message, $code, $nativeCode); - } - } - - /** - * Sends request to the remote server and returns its response - * - * @param HTTP_Request2 - * @return HTTP_Request2_Response - * @throws HTTP_Request2_Exception - */ - public function sendRequest(HTTP_Request2 $request) - { - if (!extension_loaded('curl')) { - throw new HTTP_Request2_LogicException( - 'cURL extension not available', HTTP_Request2_Exception::MISCONFIGURATION - ); - } - - $this->request = $request; - $this->response = null; - $this->position = 0; - $this->eventSentHeaders = false; - $this->eventReceivedHeaders = false; - - try { - if (false === curl_exec($ch = $this->createCurlHandle())) { - $e = self::wrapCurlError($ch); - } - } catch (Exception $e) { - } - if (isset($ch)) { - $this->lastInfo = curl_getinfo($ch); - curl_close($ch); - } - - $response = $this->response; - unset($this->request, $this->requestBody, $this->response); - - if (!empty($e)) { - throw $e; - } - - if ($jar = $request->getCookieJar()) { - $jar->addCookiesFromResponse($response, $request->getUrl()); - } - - if (0 < $this->lastInfo['size_download']) { - $request->setLastEvent('receivedBody', $response); - } - return $response; - } - - /** - * Returns information about last transfer - * - * @return array associative array as returned by curl_getinfo() - */ - public function getInfo() - { - return $this->lastInfo; - } - - /** - * Creates a new cURL handle and populates it with data from the request - * - * @return resource a cURL handle, as created by curl_init() - * @throws HTTP_Request2_LogicException - */ - protected function createCurlHandle() - { - $ch = curl_init(); - - curl_setopt_array($ch, array( - // setup write callbacks - CURLOPT_HEADERFUNCTION => array($this, 'callbackWriteHeader'), - CURLOPT_WRITEFUNCTION => array($this, 'callbackWriteBody'), - // buffer size - CURLOPT_BUFFERSIZE => $this->request->getConfig('buffer_size'), - // connection timeout - CURLOPT_CONNECTTIMEOUT => $this->request->getConfig('connect_timeout'), - // save full outgoing headers, in case someone is interested - CURLINFO_HEADER_OUT => true, - // request url - CURLOPT_URL => $this->request->getUrl()->getUrl() - )); - - // set up redirects - if (!$this->request->getConfig('follow_redirects')) { - curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false); - } else { - if (!@curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true)) { - throw new HTTP_Request2_LogicException( - 'Redirect support in curl is unavailable due to open_basedir or safe_mode setting', - HTTP_Request2_Exception::MISCONFIGURATION - ); - } - curl_setopt($ch, CURLOPT_MAXREDIRS, $this->request->getConfig('max_redirects')); - // limit redirects to http(s), works in 5.2.10+ - if (defined('CURLOPT_REDIR_PROTOCOLS')) { - curl_setopt($ch, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); - } - // works in 5.3.2+, http://bugs.php.net/bug.php?id=49571 - if ($this->request->getConfig('strict_redirects') && defined('CURLOPT_POSTREDIR')) { - curl_setopt($ch, CURLOPT_POSTREDIR, 3); - } - } - - // request timeout - if ($timeout = $this->request->getConfig('timeout')) { - curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); - } - - // set HTTP version - switch ($this->request->getConfig('protocol_version')) { - case '1.0': - curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); - break; - case '1.1': - curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); - } - - // set request method - switch ($this->request->getMethod()) { - case HTTP_Request2::METHOD_GET: - curl_setopt($ch, CURLOPT_HTTPGET, true); - break; - case HTTP_Request2::METHOD_POST: - curl_setopt($ch, CURLOPT_POST, true); - break; - case HTTP_Request2::METHOD_HEAD: - curl_setopt($ch, CURLOPT_NOBODY, true); - break; - case HTTP_Request2::METHOD_PUT: - curl_setopt($ch, CURLOPT_UPLOAD, true); - break; - default: - curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $this->request->getMethod()); - } - - // set proxy, if needed - if ($host = $this->request->getConfig('proxy_host')) { - if (!($port = $this->request->getConfig('proxy_port'))) { - throw new HTTP_Request2_LogicException( - 'Proxy port not provided', HTTP_Request2_Exception::MISSING_VALUE - ); - } - curl_setopt($ch, CURLOPT_PROXY, $host . ':' . $port); - if ($user = $this->request->getConfig('proxy_user')) { - curl_setopt($ch, CURLOPT_PROXYUSERPWD, $user . ':' . - $this->request->getConfig('proxy_password')); - switch ($this->request->getConfig('proxy_auth_scheme')) { - case HTTP_Request2::AUTH_BASIC: - curl_setopt($ch, CURLOPT_PROXYAUTH, CURLAUTH_BASIC); - break; - case HTTP_Request2::AUTH_DIGEST: - curl_setopt($ch, CURLOPT_PROXYAUTH, CURLAUTH_DIGEST); - } - } - } - - // set authentication data - if ($auth = $this->request->getAuth()) { - curl_setopt($ch, CURLOPT_USERPWD, $auth['user'] . ':' . $auth['password']); - switch ($auth['scheme']) { - case HTTP_Request2::AUTH_BASIC: - curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); - break; - case HTTP_Request2::AUTH_DIGEST: - curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST); - } - } - - // set SSL options - foreach ($this->request->getConfig() as $name => $value) { - if ('ssl_verify_host' == $name && null !== $value) { - curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, $value? 2: 0); - } elseif (isset(self::$sslContextMap[$name]) && null !== $value) { - curl_setopt($ch, self::$sslContextMap[$name], $value); - } - } - - $headers = $this->request->getHeaders(); - // make cURL automagically send proper header - if (!isset($headers['accept-encoding'])) { - $headers['accept-encoding'] = ''; - } - - if (($jar = $this->request->getCookieJar()) - && ($cookies = $jar->getMatching($this->request->getUrl(), true)) - ) { - $headers['cookie'] = (empty($headers['cookie'])? '': $headers['cookie'] . '; ') . $cookies; - } - - // set headers having special cURL keys - foreach (self::$headerMap as $name => $option) { - if (isset($headers[$name])) { - curl_setopt($ch, $option, $headers[$name]); - unset($headers[$name]); - } - } - - $this->calculateRequestLength($headers); - if (isset($headers['content-length'])) { - $this->workaroundPhpBug47204($ch, $headers); - } - - // set headers not having special keys - $headersFmt = array(); - foreach ($headers as $name => $value) { - $canonicalName = implode('-', array_map('ucfirst', explode('-', $name))); - $headersFmt[] = $canonicalName . ': ' . $value; - } - curl_setopt($ch, CURLOPT_HTTPHEADER, $headersFmt); - - return $ch; - } - - /** - * Workaround for PHP bug #47204 that prevents rewinding request body - * - * The workaround consists of reading the entire request body into memory - * and setting it as CURLOPT_POSTFIELDS, so it isn't recommended for large - * file uploads, use Socket adapter instead. - * - * @param resource cURL handle - * @param array Request headers - */ - protected function workaroundPhpBug47204($ch, &$headers) - { - // no redirects, no digest auth -> probably no rewind needed - if (!$this->request->getConfig('follow_redirects') - && (!($auth = $this->request->getAuth()) - || HTTP_Request2::AUTH_DIGEST != $auth['scheme']) - ) { - curl_setopt($ch, CURLOPT_READFUNCTION, array($this, 'callbackReadBody')); - - // rewind may be needed, read the whole body into memory - } else { - if ($this->requestBody instanceof HTTP_Request2_MultipartBody) { - $this->requestBody = $this->requestBody->__toString(); - - } elseif (is_resource($this->requestBody)) { - $fp = $this->requestBody; - $this->requestBody = ''; - while (!feof($fp)) { - $this->requestBody .= fread($fp, 16384); - } - } - // curl hangs up if content-length is present - unset($headers['content-length']); - curl_setopt($ch, CURLOPT_POSTFIELDS, $this->requestBody); - } - } - - /** - * Callback function called by cURL for reading the request body - * - * @param resource cURL handle - * @param resource file descriptor (not used) - * @param integer maximum length of data to return - * @return string part of the request body, up to $length bytes - */ - protected function callbackReadBody($ch, $fd, $length) - { - if (!$this->eventSentHeaders) { - $this->request->setLastEvent( - 'sentHeaders', curl_getinfo($ch, CURLINFO_HEADER_OUT) - ); - $this->eventSentHeaders = true; - } - if (in_array($this->request->getMethod(), self::$bodyDisallowed) || - 0 == $this->contentLength || $this->position >= $this->contentLength - ) { - return ''; - } - if (is_string($this->requestBody)) { - $string = substr($this->requestBody, $this->position, $length); - } elseif (is_resource($this->requestBody)) { - $string = fread($this->requestBody, $length); - } else { - $string = $this->requestBody->read($length); - } - $this->request->setLastEvent('sentBodyPart', strlen($string)); - $this->position += strlen($string); - return $string; - } - - /** - * Callback function called by cURL for saving the response headers - * - * @param resource cURL handle - * @param string response header (with trailing CRLF) - * @return integer number of bytes saved - * @see HTTP_Request2_Response::parseHeaderLine() - */ - protected function callbackWriteHeader($ch, $string) - { - // we may receive a second set of headers if doing e.g. digest auth - if ($this->eventReceivedHeaders || !$this->eventSentHeaders) { - // don't bother with 100-Continue responses (bug #15785) - if (!$this->eventSentHeaders || - $this->response->getStatus() >= 200 - ) { - $this->request->setLastEvent( - 'sentHeaders', curl_getinfo($ch, CURLINFO_HEADER_OUT) - ); - } - $upload = curl_getinfo($ch, CURLINFO_SIZE_UPLOAD); - // if body wasn't read by a callback, send event with total body size - if ($upload > $this->position) { - $this->request->setLastEvent( - 'sentBodyPart', $upload - $this->position - ); - $this->position = $upload; - } - if ($upload && (!$this->eventSentHeaders - || $this->response->getStatus() >= 200) - ) { - $this->request->setLastEvent('sentBody', $upload); - } - $this->eventSentHeaders = true; - // we'll need a new response object - if ($this->eventReceivedHeaders) { - $this->eventReceivedHeaders = false; - $this->response = null; - } - } - if (empty($this->response)) { - $this->response = new HTTP_Request2_Response( - $string, false, curl_getinfo($ch, CURLINFO_EFFECTIVE_URL) - ); - } else { - $this->response->parseHeaderLine($string); - if ('' == trim($string)) { - // don't bother with 100-Continue responses (bug #15785) - if (200 <= $this->response->getStatus()) { - $this->request->setLastEvent('receivedHeaders', $this->response); - } - - if ($this->request->getConfig('follow_redirects') && $this->response->isRedirect()) { - $redirectUrl = new Net_URL2($this->response->getHeader('location')); - - // for versions lower than 5.2.10, check the redirection URL protocol - if (!defined('CURLOPT_REDIR_PROTOCOLS') && $redirectUrl->isAbsolute() - && !in_array($redirectUrl->getScheme(), array('http', 'https')) - ) { - return -1; - } - - if ($jar = $this->request->getCookieJar()) { - $jar->addCookiesFromResponse($this->response, $this->request->getUrl()); - if (!$redirectUrl->isAbsolute()) { - $redirectUrl = $this->request->getUrl()->resolve($redirectUrl); - } - if ($cookies = $jar->getMatching($redirectUrl, true)) { - curl_setopt($ch, CURLOPT_COOKIE, $cookies); - } - } - } - $this->eventReceivedHeaders = true; - } - } - return strlen($string); - } - - /** - * Callback function called by cURL for saving the response body - * - * @param resource cURL handle (not used) - * @param string part of the response body - * @return integer number of bytes saved - * @see HTTP_Request2_Response::appendBody() - */ - protected function callbackWriteBody($ch, $string) - { - // cURL calls WRITEFUNCTION callback without calling HEADERFUNCTION if - // response doesn't start with proper HTTP status line (see bug #15716) - if (empty($this->response)) { - throw new HTTP_Request2_MessageException( - "Malformed response: {$string}", - HTTP_Request2_Exception::MALFORMED_RESPONSE - ); - } - if ($this->request->getConfig('store_body')) { - $this->response->appendBody($string); - } - $this->request->setLastEvent('receivedBodyPart', $string); - return strlen($string); - } -} -?> diff --git a/lib/ext/HTTP/Request2/Adapter/Mock.php b/lib/ext/HTTP/Request2/Adapter/Mock.php deleted file mode 100644 index 6e9f827..0000000 --- a/lib/ext/HTTP/Request2/Adapter/Mock.php +++ /dev/null @@ -1,171 +0,0 @@ - - * 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. - * * 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. - * - * @category HTTP - * @package HTTP_Request2 - * @author Alexey Borzov - * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version SVN: $Id: Mock.php 308322 2011-02-14 13:58:03Z avb $ - * @link http://pear.php.net/package/HTTP_Request2 - */ - -/** - * Base class for HTTP_Request2 adapters - */ -require_once 'HTTP/Request2/Adapter.php'; - -/** - * Mock adapter intended for testing - * - * Can be used to test applications depending on HTTP_Request2 package without - * actually performing any HTTP requests. This adapter will return responses - * previously added via addResponse() - * - * $mock = new HTTP_Request2_Adapter_Mock(); - * $mock->addResponse("HTTP/1.1 ... "); - * - * $request = new HTTP_Request2(); - * $request->setAdapter($mock); - * - * // This will return the response set above - * $response = $req->send(); - * - * - * @category HTTP - * @package HTTP_Request2 - * @author Alexey Borzov - * @version Release: 2.0.0 - */ -class HTTP_Request2_Adapter_Mock extends HTTP_Request2_Adapter -{ - /** - * A queue of responses to be returned by sendRequest() - * @var array - */ - protected $responses = array(); - - /** - * Returns the next response from the queue built by addResponse() - * - * If the queue is empty it will return default empty response with status 400, - * if an Exception object was added to the queue it will be thrown. - * - * @param HTTP_Request2 - * @return HTTP_Request2_Response - * @throws Exception - */ - public function sendRequest(HTTP_Request2 $request) - { - if (count($this->responses) > 0) { - $response = array_shift($this->responses); - if ($response instanceof HTTP_Request2_Response) { - return $response; - } else { - // rethrow the exception - $class = get_class($response); - $message = $response->getMessage(); - $code = $response->getCode(); - throw new $class($message, $code); - } - } else { - return self::createResponseFromString("HTTP/1.1 400 Bad Request\r\n\r\n"); - } - } - - /** - * Adds response to the queue - * - * @param mixed either a string, a pointer to an open file, - * an instance of HTTP_Request2_Response or Exception - * @throws HTTP_Request2_Exception - */ - public function addResponse($response) - { - if (is_string($response)) { - $response = self::createResponseFromString($response); - } elseif (is_resource($response)) { - $response = self::createResponseFromFile($response); - } elseif (!$response instanceof HTTP_Request2_Response && - !$response instanceof Exception - ) { - throw new HTTP_Request2_Exception('Parameter is not a valid response'); - } - $this->responses[] = $response; - } - - /** - * Creates a new HTTP_Request2_Response object from a string - * - * @param string - * @return HTTP_Request2_Response - * @throws HTTP_Request2_Exception - */ - public static function createResponseFromString($str) - { - $parts = preg_split('!(\r?\n){2}!m', $str, 2); - $headerLines = explode("\n", $parts[0]); - $response = new HTTP_Request2_Response(array_shift($headerLines)); - foreach ($headerLines as $headerLine) { - $response->parseHeaderLine($headerLine); - } - $response->parseHeaderLine(''); - if (isset($parts[1])) { - $response->appendBody($parts[1]); - } - return $response; - } - - /** - * Creates a new HTTP_Request2_Response object from a file - * - * @param resource file pointer returned by fopen() - * @return HTTP_Request2_Response - * @throws HTTP_Request2_Exception - */ - public static function createResponseFromFile($fp) - { - $response = new HTTP_Request2_Response(fgets($fp)); - do { - $headerLine = fgets($fp); - $response->parseHeaderLine($headerLine); - } while ('' != trim($headerLine)); - - while (!feof($fp)) { - $response->appendBody(fread($fp, 8192)); - } - return $response; - } -} -?> \ No newline at end of file diff --git a/lib/ext/HTTP/Request2/Adapter/Socket.php b/lib/ext/HTTP/Request2/Adapter/Socket.php deleted file mode 100644 index 05cac6e..0000000 --- a/lib/ext/HTTP/Request2/Adapter/Socket.php +++ /dev/null @@ -1,1084 +0,0 @@ - - * 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. - * * 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. - * - * @category HTTP - * @package HTTP_Request2 - * @author Alexey Borzov - * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version SVN: $Id: Socket.php 309921 2011-04-03 16:43:02Z avb $ - * @link http://pear.php.net/package/HTTP_Request2 - */ - -/** - * Base class for HTTP_Request2 adapters - */ -require_once 'HTTP/Request2/Adapter.php'; - -/** - * Socket-based adapter for HTTP_Request2 - * - * This adapter uses only PHP sockets and will work on almost any PHP - * environment. Code is based on original HTTP_Request PEAR package. - * - * @category HTTP - * @package HTTP_Request2 - * @author Alexey Borzov - * @version Release: 2.0.0 - */ -class HTTP_Request2_Adapter_Socket extends HTTP_Request2_Adapter -{ - /** - * Regular expression for 'token' rule from RFC 2616 - */ - const REGEXP_TOKEN = '[^\x00-\x1f\x7f-\xff()<>@,;:\\\\"/\[\]?={}\s]+'; - - /** - * Regular expression for 'quoted-string' rule from RFC 2616 - */ - const REGEXP_QUOTED_STRING = '"(?:\\\\.|[^\\\\"])*"'; - - /** - * Connected sockets, needed for Keep-Alive support - * @var array - * @see connect() - */ - protected static $sockets = array(); - - /** - * Data for digest authentication scheme - * - * The keys for the array are URL prefixes. - * - * The values are associative arrays with data (realm, nonce, nonce-count, - * opaque...) needed for digest authentication. Stored here to prevent making - * duplicate requests to digest-protected resources after we have already - * received the challenge. - * - * @var array - */ - protected static $challenges = array(); - - /** - * Connected socket - * @var resource - * @see connect() - */ - protected $socket; - - /** - * Challenge used for server digest authentication - * @var array - */ - protected $serverChallenge; - - /** - * Challenge used for proxy digest authentication - * @var array - */ - protected $proxyChallenge; - - /** - * Sum of start time and global timeout, exception will be thrown if request continues past this time - * @var integer - */ - protected $deadline = null; - - /** - * Remaining length of the current chunk, when reading chunked response - * @var integer - * @see readChunked() - */ - protected $chunkLength = 0; - - /** - * Remaining amount of redirections to follow - * - * Starts at 'max_redirects' configuration parameter and is reduced on each - * subsequent redirect. An Exception will be thrown once it reaches zero. - * - * @var integer - */ - protected $redirectCountdown = null; - - /** - * Sends request to the remote server and returns its response - * - * @param HTTP_Request2 - * @return HTTP_Request2_Response - * @throws HTTP_Request2_Exception - */ - public function sendRequest(HTTP_Request2 $request) - { - $this->request = $request; - - // Use global request timeout if given, see feature requests #5735, #8964 - if ($timeout = $request->getConfig('timeout')) { - $this->deadline = time() + $timeout; - } else { - $this->deadline = null; - } - - try { - $keepAlive = $this->connect(); - $headers = $this->prepareHeaders(); - if (false === @fwrite($this->socket, $headers, strlen($headers))) { - throw new HTTP_Request2_MessageException('Error writing request'); - } - // provide request headers to the observer, see request #7633 - $this->request->setLastEvent('sentHeaders', $headers); - $this->writeBody(); - - if ($this->deadline && time() > $this->deadline) { - throw new HTTP_Request2_MessageException( - 'Request timed out after ' . - $request->getConfig('timeout') . ' second(s)', - HTTP_Request2_Exception::TIMEOUT - ); - } - - $response = $this->readResponse(); - - if ($jar = $request->getCookieJar()) { - $jar->addCookiesFromResponse($response, $request->getUrl()); - } - - if (!$this->canKeepAlive($keepAlive, $response)) { - $this->disconnect(); - } - - if ($this->shouldUseProxyDigestAuth($response)) { - return $this->sendRequest($request); - } - if ($this->shouldUseServerDigestAuth($response)) { - return $this->sendRequest($request); - } - if ($authInfo = $response->getHeader('authentication-info')) { - $this->updateChallenge($this->serverChallenge, $authInfo); - } - if ($proxyInfo = $response->getHeader('proxy-authentication-info')) { - $this->updateChallenge($this->proxyChallenge, $proxyInfo); - } - - } catch (Exception $e) { - $this->disconnect(); - } - - unset($this->request, $this->requestBody); - - if (!empty($e)) { - $this->redirectCountdown = null; - throw $e; - } - - if (!$request->getConfig('follow_redirects') || !$response->isRedirect()) { - $this->redirectCountdown = null; - return $response; - } else { - return $this->handleRedirect($request, $response); - } - } - - /** - * Connects to the remote server - * - * @return bool whether the connection can be persistent - * @throws HTTP_Request2_Exception - */ - protected function connect() - { - $secure = 0 == strcasecmp($this->request->getUrl()->getScheme(), 'https'); - $tunnel = HTTP_Request2::METHOD_CONNECT == $this->request->getMethod(); - $headers = $this->request->getHeaders(); - $reqHost = $this->request->getUrl()->getHost(); - if (!($reqPort = $this->request->getUrl()->getPort())) { - $reqPort = $secure? 443: 80; - } - - if ($host = $this->request->getConfig('proxy_host')) { - if (!($port = $this->request->getConfig('proxy_port'))) { - throw new HTTP_Request2_LogicException( - 'Proxy port not provided', - HTTP_Request2_Exception::MISSING_VALUE - ); - } - $proxy = true; - } else { - $host = $reqHost; - $port = $reqPort; - $proxy = false; - } - - if ($tunnel && !$proxy) { - throw new HTTP_Request2_LogicException( - "Trying to perform CONNECT request without proxy", - HTTP_Request2_Exception::MISSING_VALUE - ); - } - if ($secure && !in_array('ssl', stream_get_transports())) { - throw new HTTP_Request2_LogicException( - 'Need OpenSSL support for https:// requests', - HTTP_Request2_Exception::MISCONFIGURATION - ); - } - - // RFC 2068, section 19.7.1: A client MUST NOT send the Keep-Alive - // connection token to a proxy server... - if ($proxy && !$secure && - !empty($headers['connection']) && 'Keep-Alive' == $headers['connection'] - ) { - $this->request->setHeader('connection'); - } - - $keepAlive = ('1.1' == $this->request->getConfig('protocol_version') && - empty($headers['connection'])) || - (!empty($headers['connection']) && - 'Keep-Alive' == $headers['connection']); - $host = ((!$secure || $proxy)? 'tcp://': 'ssl://') . $host; - - $options = array(); - if ($secure || $tunnel) { - foreach ($this->request->getConfig() as $name => $value) { - if ('ssl_' == substr($name, 0, 4) && null !== $value) { - if ('ssl_verify_host' == $name) { - if ($value) { - $options['CN_match'] = $reqHost; - } - } else { - $options[substr($name, 4)] = $value; - } - } - } - ksort($options); - } - - // Changing SSL context options after connection is established does *not* - // work, we need a new connection if options change - $remote = $host . ':' . $port; - $socketKey = $remote . (($secure && $proxy)? "->{$reqHost}:{$reqPort}": '') . - (empty($options)? '': ':' . serialize($options)); - unset($this->socket); - - // We use persistent connections and have a connected socket? - // Ensure that the socket is still connected, see bug #16149 - if ($keepAlive && !empty(self::$sockets[$socketKey]) && - !feof(self::$sockets[$socketKey]) - ) { - $this->socket =& self::$sockets[$socketKey]; - - } elseif ($secure && $proxy && !$tunnel) { - $this->establishTunnel(); - $this->request->setLastEvent( - 'connect', "ssl://{$reqHost}:{$reqPort} via {$host}:{$port}" - ); - self::$sockets[$socketKey] =& $this->socket; - - } else { - // Set SSL context options if doing HTTPS request or creating a tunnel - $context = stream_context_create(); - foreach ($options as $name => $value) { - if (!stream_context_set_option($context, 'ssl', $name, $value)) { - throw new HTTP_Request2_LogicException( - "Error setting SSL context option '{$name}'" - ); - } - } - $track = @ini_set('track_errors', 1); - $this->socket = @stream_socket_client( - $remote, $errno, $errstr, - $this->request->getConfig('connect_timeout'), - STREAM_CLIENT_CONNECT, $context - ); - if (!$this->socket) { - $e = new HTTP_Request2_ConnectionException( - "Unable to connect to {$remote}. Error: " - . (empty($errstr)? $php_errormsg: $errstr), 0, $errno - ); - } - @ini_set('track_errors', $track); - if (isset($e)) { - throw $e; - } - $this->request->setLastEvent('connect', $remote); - self::$sockets[$socketKey] =& $this->socket; - } - return $keepAlive; - } - - /** - * Establishes a tunnel to a secure remote server via HTTP CONNECT request - * - * This method will fail if 'ssl_verify_peer' is enabled. Probably because PHP - * sees that we are connected to a proxy server (duh!) rather than the server - * that presents its certificate. - * - * @link http://tools.ietf.org/html/rfc2817#section-5.2 - * @throws HTTP_Request2_Exception - */ - protected function establishTunnel() - { - $donor = new self; - $connect = new HTTP_Request2( - $this->request->getUrl(), HTTP_Request2::METHOD_CONNECT, - array_merge($this->request->getConfig(), - array('adapter' => $donor)) - ); - $response = $connect->send(); - // Need any successful (2XX) response - if (200 > $response->getStatus() || 300 <= $response->getStatus()) { - throw new HTTP_Request2_ConnectionException( - 'Failed to connect via HTTPS proxy. Proxy response: ' . - $response->getStatus() . ' ' . $response->getReasonPhrase() - ); - } - $this->socket = $donor->socket; - - $modes = array( - STREAM_CRYPTO_METHOD_TLS_CLIENT, - STREAM_CRYPTO_METHOD_SSLv3_CLIENT, - STREAM_CRYPTO_METHOD_SSLv23_CLIENT, - STREAM_CRYPTO_METHOD_SSLv2_CLIENT - ); - - foreach ($modes as $mode) { - if (stream_socket_enable_crypto($this->socket, true, $mode)) { - return; - } - } - throw new HTTP_Request2_ConnectionException( - 'Failed to enable secure connection when connecting through proxy' - ); - } - - /** - * Checks whether current connection may be reused or should be closed - * - * @param boolean whether connection could be persistent - * in the first place - * @param HTTP_Request2_Response response object to check - * @return boolean - */ - protected function canKeepAlive($requestKeepAlive, HTTP_Request2_Response $response) - { - // Do not close socket on successful CONNECT request - if (HTTP_Request2::METHOD_CONNECT == $this->request->getMethod() && - 200 <= $response->getStatus() && 300 > $response->getStatus() - ) { - return true; - } - - $lengthKnown = 'chunked' == strtolower($response->getHeader('transfer-encoding')) - || null !== $response->getHeader('content-length') - // no body possible for such responses, see also request #17031 - || HTTP_Request2::METHOD_HEAD == $this->request->getMethod() - || in_array($response->getStatus(), array(204, 304)); - $persistent = 'keep-alive' == strtolower($response->getHeader('connection')) || - (null === $response->getHeader('connection') && - '1.1' == $response->getVersion()); - return $requestKeepAlive && $lengthKnown && $persistent; - } - - /** - * Disconnects from the remote server - */ - protected function disconnect() - { - if (is_resource($this->socket)) { - fclose($this->socket); - $this->socket = null; - $this->request->setLastEvent('disconnect'); - } - } - - /** - * Handles HTTP redirection - * - * This method will throw an Exception if redirect to a non-HTTP(S) location - * is attempted, also if number of redirects performed already is equal to - * 'max_redirects' configuration parameter. - * - * @param HTTP_Request2 Original request - * @param HTTP_Request2_Response Response containing redirect - * @return HTTP_Request2_Response Response from a new location - * @throws HTTP_Request2_Exception - */ - protected function handleRedirect(HTTP_Request2 $request, - HTTP_Request2_Response $response) - { - if (is_null($this->redirectCountdown)) { - $this->redirectCountdown = $request->getConfig('max_redirects'); - } - if (0 == $this->redirectCountdown) { - $this->redirectCountdown = null; - // Copying cURL behaviour - throw new HTTP_Request2_MessageException ( - 'Maximum (' . $request->getConfig('max_redirects') . ') redirects followed', - HTTP_Request2_Exception::TOO_MANY_REDIRECTS - ); - } - $redirectUrl = new Net_URL2( - $response->getHeader('location'), - array(Net_URL2::OPTION_USE_BRACKETS => $request->getConfig('use_brackets')) - ); - // refuse non-HTTP redirect - if ($redirectUrl->isAbsolute() - && !in_array($redirectUrl->getScheme(), array('http', 'https')) - ) { - $this->redirectCountdown = null; - throw new HTTP_Request2_MessageException( - 'Refusing to redirect to a non-HTTP URL ' . $redirectUrl->__toString(), - HTTP_Request2_Exception::NON_HTTP_REDIRECT - ); - } - // Theoretically URL should be absolute (see http://tools.ietf.org/html/rfc2616#section-14.30), - // but in practice it is often not - if (!$redirectUrl->isAbsolute()) { - $redirectUrl = $request->getUrl()->resolve($redirectUrl); - } - $redirect = clone $request; - $redirect->setUrl($redirectUrl); - if (303 == $response->getStatus() || (!$request->getConfig('strict_redirects') - && in_array($response->getStatus(), array(301, 302))) - ) { - $redirect->setMethod(HTTP_Request2::METHOD_GET); - $redirect->setBody(''); - } - - if (0 < $this->redirectCountdown) { - $this->redirectCountdown--; - } - return $this->sendRequest($redirect); - } - - /** - * Checks whether another request should be performed with server digest auth - * - * Several conditions should be satisfied for it to return true: - * - response status should be 401 - * - auth credentials should be set in the request object - * - response should contain WWW-Authenticate header with digest challenge - * - there is either no challenge stored for this URL or new challenge - * contains stale=true parameter (in other case we probably just failed - * due to invalid username / password) - * - * The method stores challenge values in $challenges static property - * - * @param HTTP_Request2_Response response to check - * @return boolean whether another request should be performed - * @throws HTTP_Request2_Exception in case of unsupported challenge parameters - */ - protected function shouldUseServerDigestAuth(HTTP_Request2_Response $response) - { - // no sense repeating a request if we don't have credentials - if (401 != $response->getStatus() || !$this->request->getAuth()) { - return false; - } - if (!$challenge = $this->parseDigestChallenge($response->getHeader('www-authenticate'))) { - return false; - } - - $url = $this->request->getUrl(); - $scheme = $url->getScheme(); - $host = $scheme . '://' . $url->getHost(); - if ($port = $url->getPort()) { - if ((0 == strcasecmp($scheme, 'http') && 80 != $port) || - (0 == strcasecmp($scheme, 'https') && 443 != $port) - ) { - $host .= ':' . $port; - } - } - - if (!empty($challenge['domain'])) { - $prefixes = array(); - foreach (preg_split('/\\s+/', $challenge['domain']) as $prefix) { - // don't bother with different servers - if ('/' == substr($prefix, 0, 1)) { - $prefixes[] = $host . $prefix; - } - } - } - if (empty($prefixes)) { - $prefixes = array($host . '/'); - } - - $ret = true; - foreach ($prefixes as $prefix) { - if (!empty(self::$challenges[$prefix]) && - (empty($challenge['stale']) || strcasecmp('true', $challenge['stale'])) - ) { - // probably credentials are invalid - $ret = false; - } - self::$challenges[$prefix] =& $challenge; - } - return $ret; - } - - /** - * Checks whether another request should be performed with proxy digest auth - * - * Several conditions should be satisfied for it to return true: - * - response status should be 407 - * - proxy auth credentials should be set in the request object - * - response should contain Proxy-Authenticate header with digest challenge - * - there is either no challenge stored for this proxy or new challenge - * contains stale=true parameter (in other case we probably just failed - * due to invalid username / password) - * - * The method stores challenge values in $challenges static property - * - * @param HTTP_Request2_Response response to check - * @return boolean whether another request should be performed - * @throws HTTP_Request2_Exception in case of unsupported challenge parameters - */ - protected function shouldUseProxyDigestAuth(HTTP_Request2_Response $response) - { - if (407 != $response->getStatus() || !$this->request->getConfig('proxy_user')) { - return false; - } - if (!($challenge = $this->parseDigestChallenge($response->getHeader('proxy-authenticate')))) { - return false; - } - - $key = 'proxy://' . $this->request->getConfig('proxy_host') . - ':' . $this->request->getConfig('proxy_port'); - - if (!empty(self::$challenges[$key]) && - (empty($challenge['stale']) || strcasecmp('true', $challenge['stale'])) - ) { - $ret = false; - } else { - $ret = true; - } - self::$challenges[$key] = $challenge; - return $ret; - } - - /** - * Extracts digest method challenge from (WWW|Proxy)-Authenticate header value - * - * There is a problem with implementation of RFC 2617: several of the parameters - * are defined as quoted-string there and thus may contain backslash escaped - * double quotes (RFC 2616, section 2.2). However, RFC 2617 defines unq(X) as - * just value of quoted-string X without surrounding quotes, it doesn't speak - * about removing backslash escaping. - * - * Now realm parameter is user-defined and human-readable, strange things - * happen when it contains quotes: - * - Apache allows quotes in realm, but apparently uses realm value without - * backslashes for digest computation - * - Squid allows (manually escaped) quotes there, but it is impossible to - * authorize with either escaped or unescaped quotes used in digest, - * probably it can't parse the response (?) - * - Both IE and Firefox display realm value with backslashes in - * the password popup and apparently use the same value for digest - * - * HTTP_Request2 follows IE and Firefox (and hopefully RFC 2617) in - * quoted-string handling, unfortunately that means failure to authorize - * sometimes - * - * @param string value of WWW-Authenticate or Proxy-Authenticate header - * @return mixed associative array with challenge parameters, false if - * no challenge is present in header value - * @throws HTTP_Request2_NotImplementedException in case of unsupported challenge parameters - */ - protected function parseDigestChallenge($headerValue) - { - $authParam = '(' . self::REGEXP_TOKEN . ')\\s*=\\s*(' . - self::REGEXP_TOKEN . '|' . self::REGEXP_QUOTED_STRING . ')'; - $challenge = "!(?<=^|\\s|,)Digest ({$authParam}\\s*(,\\s*|$))+!"; - if (!preg_match($challenge, $headerValue, $matches)) { - return false; - } - - preg_match_all('!' . $authParam . '!', $matches[0], $params); - $paramsAry = array(); - $knownParams = array('realm', 'domain', 'nonce', 'opaque', 'stale', - 'algorithm', 'qop'); - for ($i = 0; $i < count($params[0]); $i++) { - // section 3.2.1: Any unrecognized directive MUST be ignored. - if (in_array($params[1][$i], $knownParams)) { - if ('"' == substr($params[2][$i], 0, 1)) { - $paramsAry[$params[1][$i]] = substr($params[2][$i], 1, -1); - } else { - $paramsAry[$params[1][$i]] = $params[2][$i]; - } - } - } - // we only support qop=auth - if (!empty($paramsAry['qop']) && - !in_array('auth', array_map('trim', explode(',', $paramsAry['qop']))) - ) { - throw new HTTP_Request2_NotImplementedException( - "Only 'auth' qop is currently supported in digest authentication, " . - "server requested '{$paramsAry['qop']}'" - ); - } - // we only support algorithm=MD5 - if (!empty($paramsAry['algorithm']) && 'MD5' != $paramsAry['algorithm']) { - throw new HTTP_Request2_NotImplementedException( - "Only 'MD5' algorithm is currently supported in digest authentication, " . - "server requested '{$paramsAry['algorithm']}'" - ); - } - - return $paramsAry; - } - - /** - * Parses [Proxy-]Authentication-Info header value and updates challenge - * - * @param array challenge to update - * @param string value of [Proxy-]Authentication-Info header - * @todo validate server rspauth response - */ - protected function updateChallenge(&$challenge, $headerValue) - { - $authParam = '!(' . self::REGEXP_TOKEN . ')\\s*=\\s*(' . - self::REGEXP_TOKEN . '|' . self::REGEXP_QUOTED_STRING . ')!'; - $paramsAry = array(); - - preg_match_all($authParam, $headerValue, $params); - for ($i = 0; $i < count($params[0]); $i++) { - if ('"' == substr($params[2][$i], 0, 1)) { - $paramsAry[$params[1][$i]] = substr($params[2][$i], 1, -1); - } else { - $paramsAry[$params[1][$i]] = $params[2][$i]; - } - } - // for now, just update the nonce value - if (!empty($paramsAry['nextnonce'])) { - $challenge['nonce'] = $paramsAry['nextnonce']; - $challenge['nc'] = 1; - } - } - - /** - * Creates a value for [Proxy-]Authorization header when using digest authentication - * - * @param string user name - * @param string password - * @param string request URL - * @param array digest challenge parameters - * @return string value of [Proxy-]Authorization request header - * @link http://tools.ietf.org/html/rfc2617#section-3.2.2 - */ - protected function createDigestResponse($user, $password, $url, &$challenge) - { - if (false !== ($q = strpos($url, '?')) && - $this->request->getConfig('digest_compat_ie') - ) { - $url = substr($url, 0, $q); - } - - $a1 = md5($user . ':' . $challenge['realm'] . ':' . $password); - $a2 = md5($this->request->getMethod() . ':' . $url); - - if (empty($challenge['qop'])) { - $digest = md5($a1 . ':' . $challenge['nonce'] . ':' . $a2); - } else { - $challenge['cnonce'] = 'Req2.' . rand(); - if (empty($challenge['nc'])) { - $challenge['nc'] = 1; - } - $nc = sprintf('%08x', $challenge['nc']++); - $digest = md5($a1 . ':' . $challenge['nonce'] . ':' . $nc . ':' . - $challenge['cnonce'] . ':auth:' . $a2); - } - return 'Digest username="' . str_replace(array('\\', '"'), array('\\\\', '\\"'), $user) . '", ' . - 'realm="' . $challenge['realm'] . '", ' . - 'nonce="' . $challenge['nonce'] . '", ' . - 'uri="' . $url . '", ' . - 'response="' . $digest . '"' . - (!empty($challenge['opaque'])? - ', opaque="' . $challenge['opaque'] . '"': - '') . - (!empty($challenge['qop'])? - ', qop="auth", nc=' . $nc . ', cnonce="' . $challenge['cnonce'] . '"': - ''); - } - - /** - * Adds 'Authorization' header (if needed) to request headers array - * - * @param array request headers - * @param string request host (needed for digest authentication) - * @param string request URL (needed for digest authentication) - * @throws HTTP_Request2_NotImplementedException - */ - protected function addAuthorizationHeader(&$headers, $requestHost, $requestUrl) - { - if (!($auth = $this->request->getAuth())) { - return; - } - switch ($auth['scheme']) { - case HTTP_Request2::AUTH_BASIC: - $headers['authorization'] = - 'Basic ' . base64_encode($auth['user'] . ':' . $auth['password']); - break; - - case HTTP_Request2::AUTH_DIGEST: - unset($this->serverChallenge); - $fullUrl = ('/' == $requestUrl[0])? - $this->request->getUrl()->getScheme() . '://' . - $requestHost . $requestUrl: - $requestUrl; - foreach (array_keys(self::$challenges) as $key) { - if ($key == substr($fullUrl, 0, strlen($key))) { - $headers['authorization'] = $this->createDigestResponse( - $auth['user'], $auth['password'], - $requestUrl, self::$challenges[$key] - ); - $this->serverChallenge =& self::$challenges[$key]; - break; - } - } - break; - - default: - throw new HTTP_Request2_NotImplementedException( - "Unknown HTTP authentication scheme '{$auth['scheme']}'" - ); - } - } - - /** - * Adds 'Proxy-Authorization' header (if needed) to request headers array - * - * @param array request headers - * @param string request URL (needed for digest authentication) - * @throws HTTP_Request2_NotImplementedException - */ - protected function addProxyAuthorizationHeader(&$headers, $requestUrl) - { - if (!$this->request->getConfig('proxy_host') || - !($user = $this->request->getConfig('proxy_user')) || - (0 == strcasecmp('https', $this->request->getUrl()->getScheme()) && - HTTP_Request2::METHOD_CONNECT != $this->request->getMethod()) - ) { - return; - } - - $password = $this->request->getConfig('proxy_password'); - switch ($this->request->getConfig('proxy_auth_scheme')) { - case HTTP_Request2::AUTH_BASIC: - $headers['proxy-authorization'] = - 'Basic ' . base64_encode($user . ':' . $password); - break; - - case HTTP_Request2::AUTH_DIGEST: - unset($this->proxyChallenge); - $proxyUrl = 'proxy://' . $this->request->getConfig('proxy_host') . - ':' . $this->request->getConfig('proxy_port'); - if (!empty(self::$challenges[$proxyUrl])) { - $headers['proxy-authorization'] = $this->createDigestResponse( - $user, $password, - $requestUrl, self::$challenges[$proxyUrl] - ); - $this->proxyChallenge =& self::$challenges[$proxyUrl]; - } - break; - - default: - throw new HTTP_Request2_NotImplementedException( - "Unknown HTTP authentication scheme '" . - $this->request->getConfig('proxy_auth_scheme') . "'" - ); - } - } - - - /** - * Creates the string with the Request-Line and request headers - * - * @return string - * @throws HTTP_Request2_Exception - */ - protected function prepareHeaders() - { - $headers = $this->request->getHeaders(); - $url = $this->request->getUrl(); - $connect = HTTP_Request2::METHOD_CONNECT == $this->request->getMethod(); - $host = $url->getHost(); - - $defaultPort = 0 == strcasecmp($url->getScheme(), 'https')? 443: 80; - if (($port = $url->getPort()) && $port != $defaultPort || $connect) { - $host .= ':' . (empty($port)? $defaultPort: $port); - } - // Do not overwrite explicitly set 'Host' header, see bug #16146 - if (!isset($headers['host'])) { - $headers['host'] = $host; - } - - if ($connect) { - $requestUrl = $host; - - } else { - if (!$this->request->getConfig('proxy_host') || - 0 == strcasecmp($url->getScheme(), 'https') - ) { - $requestUrl = ''; - } else { - $requestUrl = $url->getScheme() . '://' . $host; - } - $path = $url->getPath(); - $query = $url->getQuery(); - $requestUrl .= (empty($path)? '/': $path) . (empty($query)? '': '?' . $query); - } - - if ('1.1' == $this->request->getConfig('protocol_version') && - extension_loaded('zlib') && !isset($headers['accept-encoding']) - ) { - $headers['accept-encoding'] = 'gzip, deflate'; - } - if (($jar = $this->request->getCookieJar()) - && ($cookies = $jar->getMatching($this->request->getUrl(), true)) - ) { - $headers['cookie'] = (empty($headers['cookie'])? '': $headers['cookie'] . '; ') . $cookies; - } - - $this->addAuthorizationHeader($headers, $host, $requestUrl); - $this->addProxyAuthorizationHeader($headers, $requestUrl); - $this->calculateRequestLength($headers); - - $headersStr = $this->request->getMethod() . ' ' . $requestUrl . ' HTTP/' . - $this->request->getConfig('protocol_version') . "\r\n"; - foreach ($headers as $name => $value) { - $canonicalName = implode('-', array_map('ucfirst', explode('-', $name))); - $headersStr .= $canonicalName . ': ' . $value . "\r\n"; - } - return $headersStr . "\r\n"; - } - - /** - * Sends the request body - * - * @throws HTTP_Request2_MessageException - */ - protected function writeBody() - { - if (in_array($this->request->getMethod(), self::$bodyDisallowed) || - 0 == $this->contentLength - ) { - return; - } - - $position = 0; - $bufferSize = $this->request->getConfig('buffer_size'); - while ($position < $this->contentLength) { - if (is_string($this->requestBody)) { - $str = substr($this->requestBody, $position, $bufferSize); - } elseif (is_resource($this->requestBody)) { - $str = fread($this->requestBody, $bufferSize); - } else { - $str = $this->requestBody->read($bufferSize); - } - if (false === @fwrite($this->socket, $str, strlen($str))) { - throw new HTTP_Request2_MessageException('Error writing request'); - } - // Provide the length of written string to the observer, request #7630 - $this->request->setLastEvent('sentBodyPart', strlen($str)); - $position += strlen($str); - } - $this->request->setLastEvent('sentBody', $this->contentLength); - } - - /** - * Reads the remote server's response - * - * @return HTTP_Request2_Response - * @throws HTTP_Request2_Exception - */ - protected function readResponse() - { - $bufferSize = $this->request->getConfig('buffer_size'); - - do { - $response = new HTTP_Request2_Response( - $this->readLine($bufferSize), true, $this->request->getUrl() - ); - do { - $headerLine = $this->readLine($bufferSize); - $response->parseHeaderLine($headerLine); - } while ('' != $headerLine); - } while (in_array($response->getStatus(), array(100, 101))); - - $this->request->setLastEvent('receivedHeaders', $response); - - // No body possible in such responses - if (HTTP_Request2::METHOD_HEAD == $this->request->getMethod() || - (HTTP_Request2::METHOD_CONNECT == $this->request->getMethod() && - 200 <= $response->getStatus() && 300 > $response->getStatus()) || - in_array($response->getStatus(), array(204, 304)) - ) { - return $response; - } - - $chunked = 'chunked' == $response->getHeader('transfer-encoding'); - $length = $response->getHeader('content-length'); - $hasBody = false; - if ($chunked || null === $length || 0 < intval($length)) { - // RFC 2616, section 4.4: - // 3. ... If a message is received with both a - // Transfer-Encoding header field and a Content-Length header field, - // the latter MUST be ignored. - $toRead = ($chunked || null === $length)? null: $length; - $this->chunkLength = 0; - - while (!feof($this->socket) && (is_null($toRead) || 0 < $toRead)) { - if ($chunked) { - $data = $this->readChunked($bufferSize); - } elseif (is_null($toRead)) { - $data = $this->fread($bufferSize); - } else { - $data = $this->fread(min($toRead, $bufferSize)); - $toRead -= strlen($data); - } - if ('' == $data && (!$this->chunkLength || feof($this->socket))) { - break; - } - - $hasBody = true; - if ($this->request->getConfig('store_body')) { - $response->appendBody($data); - } - if (!in_array($response->getHeader('content-encoding'), array('identity', null))) { - $this->request->setLastEvent('receivedEncodedBodyPart', $data); - } else { - $this->request->setLastEvent('receivedBodyPart', $data); - } - } - } - - if ($hasBody) { - $this->request->setLastEvent('receivedBody', $response); - } - return $response; - } - - /** - * Reads until either the end of the socket or a newline, whichever comes first - * - * Strips the trailing newline from the returned data, handles global - * request timeout. Method idea borrowed from Net_Socket PEAR package. - * - * @param int buffer size to use for reading - * @return Available data up to the newline (not including newline) - * @throws HTTP_Request2_MessageException In case of timeout - */ - protected function readLine($bufferSize) - { - $line = ''; - while (!feof($this->socket)) { - if ($this->deadline) { - stream_set_timeout($this->socket, max($this->deadline - time(), 1)); - } - $line .= @fgets($this->socket, $bufferSize); - $info = stream_get_meta_data($this->socket); - if ($info['timed_out'] || $this->deadline && time() > $this->deadline) { - $reason = $this->deadline - ? 'after ' . $this->request->getConfig('timeout') . ' second(s)' - : 'due to default_socket_timeout php.ini setting'; - throw new HTTP_Request2_MessageException( - "Request timed out {$reason}", HTTP_Request2_Exception::TIMEOUT - ); - } - if (substr($line, -1) == "\n") { - return rtrim($line, "\r\n"); - } - } - return $line; - } - - /** - * Wrapper around fread(), handles global request timeout - * - * @param int Reads up to this number of bytes - * @return Data read from socket - * @throws HTTP_Request2_MessageException In case of timeout - */ - protected function fread($length) - { - if ($this->deadline) { - stream_set_timeout($this->socket, max($this->deadline - time(), 1)); - } - $data = fread($this->socket, $length); - $info = stream_get_meta_data($this->socket); - if ($info['timed_out'] || $this->deadline && time() > $this->deadline) { - $reason = $this->deadline - ? 'after ' . $this->request->getConfig('timeout') . ' second(s)' - : 'due to default_socket_timeout php.ini setting'; - throw new HTTP_Request2_MessageException( - "Request timed out {$reason}", HTTP_Request2_Exception::TIMEOUT - ); - } - return $data; - } - - /** - * Reads a part of response body encoded with chunked Transfer-Encoding - * - * @param int buffer size to use for reading - * @return string - * @throws HTTP_Request2_MessageException - */ - protected function readChunked($bufferSize) - { - // at start of the next chunk? - if (0 == $this->chunkLength) { - $line = $this->readLine($bufferSize); - if (!preg_match('/^([0-9a-f]+)/i', $line, $matches)) { - throw new HTTP_Request2_MessageException( - "Cannot decode chunked response, invalid chunk length '{$line}'", - HTTP_Request2_Exception::DECODE_ERROR - ); - } else { - $this->chunkLength = hexdec($matches[1]); - // Chunk with zero length indicates the end - if (0 == $this->chunkLength) { - $this->readLine($bufferSize); - return ''; - } - } - } - $data = $this->fread(min($this->chunkLength, $bufferSize)); - $this->chunkLength -= strlen($data); - if (0 == $this->chunkLength) { - $this->readLine($bufferSize); // Trailing CRLF - } - return $data; - } -} - -?> \ No newline at end of file diff --git a/lib/ext/HTTP/Request2/CookieJar.php b/lib/ext/HTTP/Request2/CookieJar.php deleted file mode 100644 index af7534f..0000000 --- a/lib/ext/HTTP/Request2/CookieJar.php +++ /dev/null @@ -1,499 +0,0 @@ - - * 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. - * * 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. - * - * @category HTTP - * @package HTTP_Request2 - * @author Alexey Borzov - * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version SVN: $Id: CookieJar.php 308629 2011-02-24 17:34:24Z avb $ - * @link http://pear.php.net/package/HTTP_Request2 - */ - -/** Class representing a HTTP request message */ -require_once 'HTTP/Request2.php'; - -/** - * Stores cookies and passes them between HTTP requests - * - * @category HTTP - * @package HTTP_Request2 - * @author Alexey Borzov - * @version Release: @package_version@ - */ -class HTTP_Request2_CookieJar implements Serializable -{ - /** - * Array of stored cookies - * - * The array is indexed by domain, path and cookie name - * .example.com - * / - * some_cookie => cookie data - * /subdir - * other_cookie => cookie data - * .example.org - * ... - * - * @var array - */ - protected $cookies = array(); - - /** - * Whether session cookies should be serialized when serializing the jar - * @var bool - */ - protected $serializeSession = false; - - /** - * Whether Public Suffix List should be used for domain matching - * @var bool - */ - protected $useList = true; - - /** - * Array with Public Suffix List data - * @var array - * @link http://publicsuffix.org/ - */ - protected static $psl = array(); - - /** - * Class constructor, sets various options - * - * @param bool Controls serializing session cookies, see {@link serializeSessionCookies()} - * @param bool Controls using Public Suffix List, see {@link usePublicSuffixList()} - */ - public function __construct($serializeSessionCookies = false, $usePublicSuffixList = true) - { - $this->serializeSessionCookies($serializeSessionCookies); - $this->usePublicSuffixList($usePublicSuffixList); - } - - /** - * Returns current time formatted in ISO-8601 at UTC timezone - * - * @return string - */ - protected function now() - { - $dt = new DateTime(); - $dt->setTimezone(new DateTimeZone('UTC')); - return $dt->format(DateTime::ISO8601); - } - - /** - * Checks cookie array for correctness, possibly updating its 'domain', 'path' and 'expires' fields - * - * The checks are as follows: - * - cookie array should contain 'name' and 'value' fields; - * - name and value should not contain disallowed symbols; - * - 'expires' should be either empty parseable by DateTime; - * - 'domain' and 'path' should be either not empty or an URL where - * cookie was set should be provided. - * - if $setter is provided, then document at that URL should be allowed - * to set a cookie for that 'domain'. If $setter is not provided, - * then no domain checks will be made. - * - * 'expires' field will be converted to ISO8601 format from COOKIE format, - * 'domain' and 'path' will be set from setter URL if empty. - * - * @param array cookie data, as returned by {@link HTTP_Request2_Response::getCookies()} - * @param Net_URL2 URL of the document that sent Set-Cookie header - * @return array Updated cookie array - * @throws HTTP_Request2_LogicException - * @throws HTTP_Request2_MessageException - */ - protected function checkAndUpdateFields(array $cookie, Net_URL2 $setter = null) - { - if ($missing = array_diff(array('name', 'value'), array_keys($cookie))) { - throw new HTTP_Request2_LogicException( - "Cookie array should contain 'name' and 'value' fields", - HTTP_Request2_Exception::MISSING_VALUE - ); - } - if (preg_match(HTTP_Request2::REGEXP_INVALID_COOKIE, $cookie['name'])) { - throw new HTTP_Request2_LogicException( - "Invalid cookie name: '{$cookie['name']}'", - HTTP_Request2_Exception::INVALID_ARGUMENT - ); - } - if (preg_match(HTTP_Request2::REGEXP_INVALID_COOKIE, $cookie['value'])) { - throw new HTTP_Request2_LogicException( - "Invalid cookie value: '{$cookie['value']}'", - HTTP_Request2_Exception::INVALID_ARGUMENT - ); - } - $cookie += array('domain' => '', 'path' => '', 'expires' => null, 'secure' => false); - - // Need ISO-8601 date @ UTC timezone - if (!empty($cookie['expires']) - && !preg_match('/^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\+0000$/', $cookie['expires']) - ) { - try { - $dt = new DateTime($cookie['expires']); - $dt->setTimezone(new DateTimeZone('UTC')); - $cookie['expires'] = $dt->format(DateTime::ISO8601); - } catch (Exception $e) { - throw new HTTP_Request2_LogicException($e->getMessage()); - } - } - - if (empty($cookie['domain']) || empty($cookie['path'])) { - if (!$setter) { - throw new HTTP_Request2_LogicException( - 'Cookie misses domain and/or path component, cookie setter URL needed', - HTTP_Request2_Exception::MISSING_VALUE - ); - } - if (empty($cookie['domain'])) { - if ($host = $setter->getHost()) { - $cookie['domain'] = $host; - } else { - throw new HTTP_Request2_LogicException( - 'Setter URL does not contain host part, can\'t set cookie domain', - HTTP_Request2_Exception::MISSING_VALUE - ); - } - } - if (empty($cookie['path'])) { - $path = $setter->getPath(); - $cookie['path'] = empty($path)? '/': substr($path, 0, strrpos($path, '/') + 1); - } - } - - if ($setter && !$this->domainMatch($setter->getHost(), $cookie['domain'])) { - throw new HTTP_Request2_MessageException( - "Domain " . $setter->getHost() . " cannot set cookies for " - . $cookie['domain'] - ); - } - - return $cookie; - } - - /** - * Stores a cookie in the jar - * - * @param array cookie data, as returned by {@link HTTP_Request2_Response::getCookies()} - * @param Net_URL2 URL of the document that sent Set-Cookie header - * @throws HTTP_Request2_Exception - */ - public function store(array $cookie, Net_URL2 $setter = null) - { - $cookie = $this->checkAndUpdateFields($cookie, $setter); - - if (strlen($cookie['value']) - && (is_null($cookie['expires']) || $cookie['expires'] > $this->now()) - ) { - if (!isset($this->cookies[$cookie['domain']])) { - $this->cookies[$cookie['domain']] = array(); - } - if (!isset($this->cookies[$cookie['domain']][$cookie['path']])) { - $this->cookies[$cookie['domain']][$cookie['path']] = array(); - } - $this->cookies[$cookie['domain']][$cookie['path']][$cookie['name']] = $cookie; - - } elseif (isset($this->cookies[$cookie['domain']][$cookie['path']][$cookie['name']])) { - unset($this->cookies[$cookie['domain']][$cookie['path']][$cookie['name']]); - } - } - - /** - * Adds cookies set in HTTP response to the jar - * - * @param HTTP_Request2_Response response - * @param Net_URL2 original request URL, needed for setting - * default domain/path - */ - public function addCookiesFromResponse(HTTP_Request2_Response $response, Net_URL2 $setter) - { - foreach ($response->getCookies() as $cookie) { - $this->store($cookie, $setter); - } - } - - /** - * Returns all cookies matching a given request URL - * - * The following checks are made: - * - cookie domain should match request host - * - cookie path should be a prefix for request path - * - 'secure' cookies will only be sent for HTTPS requests - * - * @param Net_URL2 - * @param bool Whether to return cookies as string for "Cookie: " header - * @return array - */ - public function getMatching(Net_URL2 $url, $asString = false) - { - $host = $url->getHost(); - $path = $url->getPath(); - $secure = 0 == strcasecmp($url->getScheme(), 'https'); - - $matched = $ret = array(); - foreach (array_keys($this->cookies) as $domain) { - if ($this->domainMatch($host, $domain)) { - foreach (array_keys($this->cookies[$domain]) as $cPath) { - if (0 === strpos($path, $cPath)) { - foreach ($this->cookies[$domain][$cPath] as $name => $cookie) { - if (!$cookie['secure'] || $secure) { - $matched[$name][strlen($cookie['path'])] = $cookie; - } - } - } - } - } - } - foreach ($matched as $cookies) { - krsort($cookies); - $ret = array_merge($ret, $cookies); - } - if (!$asString) { - return $ret; - } else { - $str = ''; - foreach ($ret as $c) { - $str .= (empty($str)? '': '; ') . $c['name'] . '=' . $c['value']; - } - return $str; - } - } - - /** - * Returns all cookies stored in a jar - * - * @return array - */ - public function getAll() - { - $cookies = array(); - foreach (array_keys($this->cookies) as $domain) { - foreach (array_keys($this->cookies[$domain]) as $path) { - foreach ($this->cookies[$domain][$path] as $name => $cookie) { - $cookies[] = $cookie; - } - } - } - return $cookies; - } - - /** - * Sets whether session cookies should be serialized when serializing the jar - * - * @param boolean - */ - public function serializeSessionCookies($serialize) - { - $this->serializeSession = (bool)$serialize; - } - - /** - * Sets whether Public Suffix List should be used for restricting cookie-setting - * - * Without PSL {@link domainMatch()} will only prevent setting cookies for - * top-level domains like '.com' or '.org'. However, it will not prevent - * setting a cookie for '.co.uk' even though only third-level registrations - * are possible in .uk domain. - * - * With the List it is possible to find the highest level at which a domain - * may be registered for a particular top-level domain and consequently - * prevent cookies set for '.co.uk' or '.msk.ru'. The same list is used by - * Firefox, Chrome and Opera browsers to restrict cookie setting. - * - * Note that PSL is licensed differently to HTTP_Request2 package (refer to - * the license information in public-suffix-list.php), so you can disable - * its use if this is an issue for you. - * - * @param boolean - * @link http://publicsuffix.org/learn/ - */ - public function usePublicSuffixList($useList) - { - $this->useList = (bool)$useList; - } - - /** - * Returns string representation of object - * - * @return string - * @see Serializable::serialize() - */ - public function serialize() - { - $cookies = $this->getAll(); - if (!$this->serializeSession) { - for ($i = count($cookies) - 1; $i >= 0; $i--) { - if (empty($cookies[$i]['expires'])) { - unset($cookies[$i]); - } - } - } - return serialize(array( - 'cookies' => $cookies, - 'serializeSession' => $this->serializeSession, - 'useList' => $this->useList - )); - } - - /** - * Constructs the object from serialized string - * - * @param string string representation - * @see Serializable::unserialize() - */ - public function unserialize($serialized) - { - $data = unserialize($serialized); - $now = $this->now(); - $this->serializeSessionCookies($data['serializeSession']); - $this->usePublicSuffixList($data['useList']); - foreach ($data['cookies'] as $cookie) { - if (!empty($cookie['expires']) && $cookie['expires'] <= $now) { - continue; - } - if (!isset($this->cookies[$cookie['domain']])) { - $this->cookies[$cookie['domain']] = array(); - } - if (!isset($this->cookies[$cookie['domain']][$cookie['path']])) { - $this->cookies[$cookie['domain']][$cookie['path']] = array(); - } - $this->cookies[$cookie['domain']][$cookie['path']][$cookie['name']] = $cookie; - } - } - - /** - * Checks whether a cookie domain matches a request host. - * - * The method is used by {@link store()} to check for whether a document - * at given URL can set a cookie with a given domain attribute and by - * {@link getMatching()} to find cookies matching the request URL. - * - * @param string request host - * @param string cookie domain - * @return bool match success - */ - public function domainMatch($requestHost, $cookieDomain) - { - if ($requestHost == $cookieDomain) { - return true; - } - // IP address, we require exact match - if (preg_match('/^(?:\d{1,3}\.){3}\d{1,3}$/', $requestHost)) { - return false; - } - if ('.' != $cookieDomain[0]) { - $cookieDomain = '.' . $cookieDomain; - } - // prevents setting cookies for '.com' and similar domains - if (!$this->useList && substr_count($cookieDomain, '.') < 2 - || $this->useList && !self::getRegisteredDomain($cookieDomain) - ) { - return false; - } - return substr('.' . $requestHost, -strlen($cookieDomain)) == $cookieDomain; - } - - /** - * Removes subdomains to get the registered domain (the first after top-level) - * - * The method will check Public Suffix List to find out where top-level - * domain ends and registered domain starts. It will remove domain parts - * to the left of registered one. - * - * @param string domain name - * @return string|bool registered domain, will return false if $domain is - * either invalid or a TLD itself - */ - public static function getRegisteredDomain($domain) - { - $domainParts = explode('.', ltrim($domain, '.')); - - // load the list if needed - if (empty(self::$psl)) { - $path = '@data_dir@' . DIRECTORY_SEPARATOR . 'HTTP_Request2'; - if (0 === strpos($path, '@' . 'data_dir@')) { - $path = realpath(dirname(__FILE__) . DIRECTORY_SEPARATOR . '..' - . DIRECTORY_SEPARATOR . 'data'); - } - self::$psl = include_once $path . DIRECTORY_SEPARATOR . 'public-suffix-list.php'; - } - - if (!($result = self::checkDomainsList($domainParts, self::$psl))) { - // known TLD, invalid domain name - return false; - } - - // unknown TLD - if (!strpos($result, '.')) { - // fallback to checking that domain "has at least two dots" - if (2 > ($count = count($domainParts))) { - return false; - } - return $domainParts[$count - 2] . '.' . $domainParts[$count - 1]; - } - return $result; - } - - /** - * Recursive helper method for {@link getRegisteredDomain()} - * - * @param array remaining domain parts - * @param mixed node in {@link HTTP_Request2_CookieJar::$psl} to check - * @return string|null concatenated domain parts, null in case of error - */ - protected static function checkDomainsList(array $domainParts, $listNode) - { - $sub = array_pop($domainParts); - $result = null; - - if (!is_array($listNode) || is_null($sub) - || array_key_exists('!' . $sub, $listNode) - ) { - return $sub; - - } elseif (array_key_exists($sub, $listNode)) { - $result = self::checkDomainsList($domainParts, $listNode[$sub]); - - } elseif (array_key_exists('*', $listNode)) { - $result = self::checkDomainsList($domainParts, $listNode['*']); - - } else { - return $sub; - } - - return (strlen($result) > 0) ? ($result . '.' . $sub) : null; - } -} -?> \ No newline at end of file diff --git a/lib/ext/HTTP/Request2/Exception.php b/lib/ext/HTTP/Request2/Exception.php deleted file mode 100644 index 34256c2..0000000 --- a/lib/ext/HTTP/Request2/Exception.php +++ /dev/null @@ -1,160 +0,0 @@ - - * 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. - * * 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. - * - * @category HTTP - * @package HTTP_Request2 - * @author Alexey Borzov - * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version SVN: $Id: Exception.php 308629 2011-02-24 17:34:24Z avb $ - * @link http://pear.php.net/package/HTTP_Request2 - */ - -/** - * Base class for exceptions in PEAR - */ -require_once 'PEAR/Exception.php'; - -/** - * Base exception class for HTTP_Request2 package - * - * @category HTTP - * @package HTTP_Request2 - * @version Release: 2.0.0 - * @link http://pear.php.net/pepr/pepr-proposal-show.php?id=132 - */ -class HTTP_Request2_Exception extends PEAR_Exception -{ - /** An invalid argument was passed to a method */ - const INVALID_ARGUMENT = 1; - /** Some required value was not available */ - const MISSING_VALUE = 2; - /** Request cannot be processed due to errors in PHP configuration */ - const MISCONFIGURATION = 3; - /** Error reading the local file */ - const READ_ERROR = 4; - - /** Server returned a response that does not conform to HTTP protocol */ - const MALFORMED_RESPONSE = 10; - /** Failure decoding Content-Encoding or Transfer-Encoding of response */ - const DECODE_ERROR = 20; - /** Operation timed out */ - const TIMEOUT = 30; - /** Number of redirects exceeded 'max_redirects' configuration parameter */ - const TOO_MANY_REDIRECTS = 40; - /** Redirect to a protocol other than http(s):// */ - const NON_HTTP_REDIRECT = 50; - - /** - * Native error code - * @var int - */ - private $_nativeCode; - - /** - * Constructor, can set package error code and native error code - * - * @param string exception message - * @param int package error code, one of class constants - * @param int error code from underlying PHP extension - */ - public function __construct($message = null, $code = null, $nativeCode = null) - { - parent::__construct($message, $code); - $this->_nativeCode = $nativeCode; - } - - /** - * Returns error code produced by underlying PHP extension - * - * For Socket Adapter this may contain error number returned by - * stream_socket_client(), for Curl Adapter this will contain error number - * returned by curl_errno() - * - * @return integer - */ - public function getNativeCode() - { - return $this->_nativeCode; - } -} - -/** - * Exception thrown in case of missing features - * - * @category HTTP - * @package HTTP_Request2 - * @version Release: 2.0.0 - */ -class HTTP_Request2_NotImplementedException extends HTTP_Request2_Exception {} - -/** - * Exception that represents error in the program logic - * - * This exception usually implies a programmer's error, like passing invalid - * data to methods or trying to use PHP extensions that weren't installed or - * enabled. Usually exceptions of this kind will be thrown before request even - * starts. - * - * The exception will usually contain a package error code. - * - * @category HTTP - * @package HTTP_Request2 - * @version Release: 2.0.0 - */ -class HTTP_Request2_LogicException extends HTTP_Request2_Exception {} - -/** - * Exception thrown when connection to a web or proxy server fails - * - * The exception will not contain a package error code, but will contain - * native error code, as returned by stream_socket_client() or curl_errno(). - * - * @category HTTP - * @package HTTP_Request2 - * @version Release: 2.0.0 - */ -class HTTP_Request2_ConnectionException extends HTTP_Request2_Exception {} - -/** - * Exception thrown when sending or receiving HTTP message fails - * - * The exception may contain both package error code and native error code. - * - * @category HTTP - * @package HTTP_Request2 - * @version Release: 2.0.0 - */ -class HTTP_Request2_MessageException extends HTTP_Request2_Exception {} -?> \ No newline at end of file diff --git a/lib/ext/HTTP/Request2/MultipartBody.php b/lib/ext/HTTP/Request2/MultipartBody.php deleted file mode 100644 index 021a199..0000000 --- a/lib/ext/HTTP/Request2/MultipartBody.php +++ /dev/null @@ -1,274 +0,0 @@ - - * 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. - * * 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. - * - * @category HTTP - * @package HTTP_Request2 - * @author Alexey Borzov - * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version SVN: $Id: MultipartBody.php 308322 2011-02-14 13:58:03Z avb $ - * @link http://pear.php.net/package/HTTP_Request2 - */ - -/** - * Class for building multipart/form-data request body - * - * The class helps to reduce memory consumption by streaming large file uploads - * from disk, it also allows monitoring of upload progress (see request #7630) - * - * @category HTTP - * @package HTTP_Request2 - * @author Alexey Borzov - * @version Release: 2.0.0 - * @link http://tools.ietf.org/html/rfc1867 - */ -class HTTP_Request2_MultipartBody -{ - /** - * MIME boundary - * @var string - */ - private $_boundary; - - /** - * Form parameters added via {@link HTTP_Request2::addPostParameter()} - * @var array - */ - private $_params = array(); - - /** - * File uploads added via {@link HTTP_Request2::addUpload()} - * @var array - */ - private $_uploads = array(); - - /** - * Header for parts with parameters - * @var string - */ - private $_headerParam = "--%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n"; - - /** - * Header for parts with uploads - * @var string - */ - private $_headerUpload = "--%s\r\nContent-Disposition: form-data; name=\"%s\"; filename=\"%s\"\r\nContent-Type: %s\r\n\r\n"; - - /** - * Current position in parameter and upload arrays - * - * First number is index of "current" part, second number is position within - * "current" part - * - * @var array - */ - private $_pos = array(0, 0); - - - /** - * Constructor. Sets the arrays with POST data. - * - * @param array values of form fields set via {@link HTTP_Request2::addPostParameter()} - * @param array file uploads set via {@link HTTP_Request2::addUpload()} - * @param bool whether to append brackets to array variable names - */ - public function __construct(array $params, array $uploads, $useBrackets = true) - { - $this->_params = self::_flattenArray('', $params, $useBrackets); - foreach ($uploads as $fieldName => $f) { - if (!is_array($f['fp'])) { - $this->_uploads[] = $f + array('name' => $fieldName); - } else { - for ($i = 0; $i < count($f['fp']); $i++) { - $upload = array( - 'name' => ($useBrackets? $fieldName . '[' . $i . ']': $fieldName) - ); - foreach (array('fp', 'filename', 'size', 'type') as $key) { - $upload[$key] = $f[$key][$i]; - } - $this->_uploads[] = $upload; - } - } - } - } - - /** - * Returns the length of the body to use in Content-Length header - * - * @return integer - */ - public function getLength() - { - $boundaryLength = strlen($this->getBoundary()); - $headerParamLength = strlen($this->_headerParam) - 4 + $boundaryLength; - $headerUploadLength = strlen($this->_headerUpload) - 8 + $boundaryLength; - $length = $boundaryLength + 6; - foreach ($this->_params as $p) { - $length += $headerParamLength + strlen($p[0]) + strlen($p[1]) + 2; - } - foreach ($this->_uploads as $u) { - $length += $headerUploadLength + strlen($u['name']) + strlen($u['type']) + - strlen($u['filename']) + $u['size'] + 2; - } - return $length; - } - - /** - * Returns the boundary to use in Content-Type header - * - * @return string - */ - public function getBoundary() - { - if (empty($this->_boundary)) { - $this->_boundary = '--' . md5('PEAR-HTTP_Request2-' . microtime()); - } - return $this->_boundary; - } - - /** - * Returns next chunk of request body - * - * @param integer Amount of bytes to read - * @return string Up to $length bytes of data, empty string if at end - */ - public function read($length) - { - $ret = ''; - $boundary = $this->getBoundary(); - $paramCount = count($this->_params); - $uploadCount = count($this->_uploads); - while ($length > 0 && $this->_pos[0] <= $paramCount + $uploadCount) { - $oldLength = $length; - if ($this->_pos[0] < $paramCount) { - $param = sprintf($this->_headerParam, $boundary, - $this->_params[$this->_pos[0]][0]) . - $this->_params[$this->_pos[0]][1] . "\r\n"; - $ret .= substr($param, $this->_pos[1], $length); - $length -= min(strlen($param) - $this->_pos[1], $length); - - } elseif ($this->_pos[0] < $paramCount + $uploadCount) { - $pos = $this->_pos[0] - $paramCount; - $header = sprintf($this->_headerUpload, $boundary, - $this->_uploads[$pos]['name'], - $this->_uploads[$pos]['filename'], - $this->_uploads[$pos]['type']); - if ($this->_pos[1] < strlen($header)) { - $ret .= substr($header, $this->_pos[1], $length); - $length -= min(strlen($header) - $this->_pos[1], $length); - } - $filePos = max(0, $this->_pos[1] - strlen($header)); - if ($length > 0 && $filePos < $this->_uploads[$pos]['size']) { - $ret .= fread($this->_uploads[$pos]['fp'], $length); - $length -= min($length, $this->_uploads[$pos]['size'] - $filePos); - } - if ($length > 0) { - $start = $this->_pos[1] + ($oldLength - $length) - - strlen($header) - $this->_uploads[$pos]['size']; - $ret .= substr("\r\n", $start, $length); - $length -= min(2 - $start, $length); - } - - } else { - $closing = '--' . $boundary . "--\r\n"; - $ret .= substr($closing, $this->_pos[1], $length); - $length -= min(strlen($closing) - $this->_pos[1], $length); - } - if ($length > 0) { - $this->_pos = array($this->_pos[0] + 1, 0); - } else { - $this->_pos[1] += $oldLength; - } - } - return $ret; - } - - /** - * Sets the current position to the start of the body - * - * This allows reusing the same body in another request - */ - public function rewind() - { - $this->_pos = array(0, 0); - foreach ($this->_uploads as $u) { - rewind($u['fp']); - } - } - - /** - * Returns the body as string - * - * Note that it reads all file uploads into memory so it is a good idea not - * to use this method with large file uploads and rely on read() instead. - * - * @return string - */ - public function __toString() - { - $this->rewind(); - return $this->read($this->getLength()); - } - - - /** - * Helper function to change the (probably multidimensional) associative array - * into the simple one. - * - * @param string name for item - * @param mixed item's values - * @param bool whether to append [] to array variables' names - * @return array array with the following items: array('item name', 'item value'); - */ - private static function _flattenArray($name, $values, $useBrackets) - { - if (!is_array($values)) { - return array(array($name, $values)); - } else { - $ret = array(); - foreach ($values as $k => $v) { - if (empty($name)) { - $newName = $k; - } elseif ($useBrackets) { - $newName = $name . '[' . $k . ']'; - } else { - $newName = $name; - } - $ret = array_merge($ret, self::_flattenArray($newName, $v, $useBrackets)); - } - return $ret; - } - } -} -?> diff --git a/lib/ext/HTTP/Request2/Observer/Log.php b/lib/ext/HTTP/Request2/Observer/Log.php deleted file mode 100644 index dd59859..0000000 --- a/lib/ext/HTTP/Request2/Observer/Log.php +++ /dev/null @@ -1,215 +0,0 @@ - - * 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. - * * 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. - * - * @category HTTP - * @package HTTP_Request2 - * @author David Jean Louis - * @author Alexey Borzov - * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version SVN: $Id: Log.php 308680 2011-02-25 17:40:17Z avb $ - * @link http://pear.php.net/package/HTTP_Request2 - */ - -/** - * Exception class for HTTP_Request2 package - */ -require_once 'HTTP/Request2/Exception.php'; - -/** - * A debug observer useful for debugging / testing. - * - * This observer logs to a log target data corresponding to the various request - * and response events, it logs by default to php://output but can be configured - * to log to a file or via the PEAR Log package. - * - * A simple example: - * - * require_once 'HTTP/Request2.php'; - * require_once 'HTTP/Request2/Observer/Log.php'; - * - * $request = new HTTP_Request2('http://www.example.com'); - * $observer = new HTTP_Request2_Observer_Log(); - * $request->attach($observer); - * $request->send(); - * - * - * A more complex example with PEAR Log: - * - * require_once 'HTTP/Request2.php'; - * require_once 'HTTP/Request2/Observer/Log.php'; - * require_once 'Log.php'; - * - * $request = new HTTP_Request2('http://www.example.com'); - * // we want to log with PEAR log - * $observer = new HTTP_Request2_Observer_Log(Log::factory('console')); - * - * // we only want to log received headers - * $observer->events = array('receivedHeaders'); - * - * $request->attach($observer); - * $request->send(); - * - * - * @category HTTP - * @package HTTP_Request2 - * @author David Jean Louis - * @author Alexey Borzov - * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 2.0.0 - * @link http://pear.php.net/package/HTTP_Request2 - */ -class HTTP_Request2_Observer_Log implements SplObserver -{ - // properties {{{ - - /** - * The log target, it can be a a resource or a PEAR Log instance. - * - * @var resource|Log $target - */ - protected $target = null; - - /** - * The events to log. - * - * @var array $events - */ - public $events = array( - 'connect', - 'sentHeaders', - 'sentBody', - 'receivedHeaders', - 'receivedBody', - 'disconnect', - ); - - // }}} - // __construct() {{{ - - /** - * Constructor. - * - * @param mixed $target Can be a file path (default: php://output), a resource, - * or an instance of the PEAR Log class. - * @param array $events Array of events to listen to (default: all events) - * - * @return void - */ - public function __construct($target = 'php://output', array $events = array()) - { - if (!empty($events)) { - $this->events = $events; - } - if (is_resource($target) || $target instanceof Log) { - $this->target = $target; - } elseif (false === ($this->target = @fopen($target, 'ab'))) { - throw new HTTP_Request2_Exception("Unable to open '{$target}'"); - } - } - - // }}} - // update() {{{ - - /** - * Called when the request notifies us of an event. - * - * @param HTTP_Request2 $subject The HTTP_Request2 instance - * - * @return void - */ - public function update(SplSubject $subject) - { - $event = $subject->getLastEvent(); - if (!in_array($event['name'], $this->events)) { - return; - } - - switch ($event['name']) { - case 'connect': - $this->log('* Connected to ' . $event['data']); - break; - case 'sentHeaders': - $headers = explode("\r\n", $event['data']); - array_pop($headers); - foreach ($headers as $header) { - $this->log('> ' . $header); - } - break; - case 'sentBody': - $this->log('> ' . $event['data'] . ' byte(s) sent'); - break; - case 'receivedHeaders': - $this->log(sprintf('< HTTP/%s %s %s', - $event['data']->getVersion(), - $event['data']->getStatus(), - $event['data']->getReasonPhrase())); - $headers = $event['data']->getHeader(); - foreach ($headers as $key => $val) { - $this->log('< ' . $key . ': ' . $val); - } - $this->log('< '); - break; - case 'receivedBody': - $this->log($event['data']->getBody()); - break; - case 'disconnect': - $this->log('* Disconnected'); - break; - } - } - - // }}} - // log() {{{ - - /** - * Logs the given message to the configured target. - * - * @param string $message Message to display - * - * @return void - */ - protected function log($message) - { - if ($this->target instanceof Log) { - $this->target->debug($message); - } elseif (is_resource($this->target)) { - fwrite($this->target, $message . "\r\n"); - } - } - - // }}} -} - -?> \ No newline at end of file diff --git a/lib/ext/HTTP/Request2/Response.php b/lib/ext/HTTP/Request2/Response.php deleted file mode 100644 index 6e0f659..0000000 --- a/lib/ext/HTTP/Request2/Response.php +++ /dev/null @@ -1,643 +0,0 @@ - - * 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. - * * 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. - * - * @category HTTP - * @package HTTP_Request2 - * @author Alexey Borzov - * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version SVN: $Id: Response.php 317591 2011-10-01 08:37:49Z avb $ - * @link http://pear.php.net/package/HTTP_Request2 - */ - -/** - * Exception class for HTTP_Request2 package - */ -require_once 'HTTP/Request2/Exception.php'; - -/** - * Class representing a HTTP response - * - * The class is designed to be used in "streaming" scenario, building the - * response as it is being received: - * - * $statusLine = read_status_line(); - * $response = new HTTP_Request2_Response($statusLine); - * do { - * $headerLine = read_header_line(); - * $response->parseHeaderLine($headerLine); - * } while ($headerLine != ''); - * - * while ($chunk = read_body()) { - * $response->appendBody($chunk); - * } - * - * var_dump($response->getHeader(), $response->getCookies(), $response->getBody()); - * - * - * - * @category HTTP - * @package HTTP_Request2 - * @author Alexey Borzov - * @version Release: 2.0.0 - * @link http://tools.ietf.org/html/rfc2616#section-6 - */ -class HTTP_Request2_Response -{ - /** - * HTTP protocol version (e.g. 1.0, 1.1) - * @var string - */ - protected $version; - - /** - * Status code - * @var integer - * @link http://tools.ietf.org/html/rfc2616#section-6.1.1 - */ - protected $code; - - /** - * Reason phrase - * @var string - * @link http://tools.ietf.org/html/rfc2616#section-6.1.1 - */ - protected $reasonPhrase; - - /** - * Effective URL (may be different from original request URL in case of redirects) - * @var string - */ - protected $effectiveUrl; - - /** - * Associative array of response headers - * @var array - */ - protected $headers = array(); - - /** - * Cookies set in the response - * @var array - */ - protected $cookies = array(); - - /** - * Name of last header processed by parseHederLine() - * - * Used to handle the headers that span multiple lines - * - * @var string - */ - protected $lastHeader = null; - - /** - * Response body - * @var string - */ - protected $body = ''; - - /** - * Whether the body is still encoded by Content-Encoding - * - * cURL provides the decoded body to the callback; if we are reading from - * socket the body is still gzipped / deflated - * - * @var bool - */ - protected $bodyEncoded; - - /** - * Associative array of HTTP status code / reason phrase. - * - * @var array - * @link http://tools.ietf.org/html/rfc2616#section-10 - */ - protected static $phrases = array( - - // 1xx: Informational - Request received, continuing process - 100 => 'Continue', - 101 => 'Switching Protocols', - - // 2xx: Success - The action was successfully received, understood and - // accepted - 200 => 'OK', - 201 => 'Created', - 202 => 'Accepted', - 203 => 'Non-Authoritative Information', - 204 => 'No Content', - 205 => 'Reset Content', - 206 => 'Partial Content', - - // 3xx: Redirection - Further action must be taken in order to complete - // the request - 300 => 'Multiple Choices', - 301 => 'Moved Permanently', - 302 => 'Found', // 1.1 - 303 => 'See Other', - 304 => 'Not Modified', - 305 => 'Use Proxy', - 307 => 'Temporary Redirect', - - // 4xx: Client Error - The request contains bad syntax or cannot be - // fulfilled - 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 Timeout', - 409 => 'Conflict', - 410 => 'Gone', - 411 => 'Length Required', - 412 => 'Precondition Failed', - 413 => 'Request Entity Too Large', - 414 => 'Request-URI Too Long', - 415 => 'Unsupported Media Type', - 416 => 'Requested Range Not Satisfiable', - 417 => 'Expectation Failed', - - // 5xx: Server Error - The server failed to fulfill an apparently - // valid request - 500 => 'Internal Server Error', - 501 => 'Not Implemented', - 502 => 'Bad Gateway', - 503 => 'Service Unavailable', - 504 => 'Gateway Timeout', - 505 => 'HTTP Version Not Supported', - 509 => 'Bandwidth Limit Exceeded', - - ); - - /** - * Returns the default reason phrase for the given code or all reason phrases - * - * @param int $code Response code - * @return string|array|null Default reason phrase for $code if $code is given - * (null if no phrase is available), array of all - * reason phrases if $code is null - * @link http://pear.php.net/bugs/18716 - */ - public static function getDefaultReasonPhrase($code = null) - { - if (null === $code) { - return self::$phrases; - } else { - return isset(self::$phrases[$code]) ? self::$phrases[$code] : null; - } - } - - /** - * Constructor, parses the response status line - * - * @param string Response status line (e.g. "HTTP/1.1 200 OK") - * @param bool Whether body is still encoded by Content-Encoding - * @param string Effective URL of the response - * @throws HTTP_Request2_MessageException if status line is invalid according to spec - */ - public function __construct($statusLine, $bodyEncoded = true, $effectiveUrl = null) - { - if (!preg_match('!^HTTP/(\d\.\d) (\d{3})(?: (.+))?!', $statusLine, $m)) { - throw new HTTP_Request2_MessageException( - "Malformed response: {$statusLine}", - HTTP_Request2_Exception::MALFORMED_RESPONSE - ); - } - $this->version = $m[1]; - $this->code = intval($m[2]); - $this->reasonPhrase = !empty($m[3]) ? trim($m[3]) : self::getDefaultReasonPhrase($this->code); - $this->bodyEncoded = (bool)$bodyEncoded; - $this->effectiveUrl = (string)$effectiveUrl; - } - - /** - * Parses the line from HTTP response filling $headers array - * - * The method should be called after reading the line from socket or receiving - * it into cURL callback. Passing an empty string here indicates the end of - * response headers and triggers additional processing, so be sure to pass an - * empty string in the end. - * - * @param string Line from HTTP response - */ - public function parseHeaderLine($headerLine) - { - $headerLine = trim($headerLine, "\r\n"); - - // empty string signals the end of headers, process the received ones - if ('' == $headerLine) { - if (!empty($this->headers['set-cookie'])) { - $cookies = is_array($this->headers['set-cookie'])? - $this->headers['set-cookie']: - array($this->headers['set-cookie']); - foreach ($cookies as $cookieString) { - $this->parseCookie($cookieString); - } - unset($this->headers['set-cookie']); - } - foreach (array_keys($this->headers) as $k) { - if (is_array($this->headers[$k])) { - $this->headers[$k] = implode(', ', $this->headers[$k]); - } - } - - // string of the form header-name: header value - } elseif (preg_match('!^([^\x00-\x1f\x7f-\xff()<>@,;:\\\\"/\[\]?={}\s]+):(.+)$!', $headerLine, $m)) { - $name = strtolower($m[1]); - $value = trim($m[2]); - if (empty($this->headers[$name])) { - $this->headers[$name] = $value; - } else { - if (!is_array($this->headers[$name])) { - $this->headers[$name] = array($this->headers[$name]); - } - $this->headers[$name][] = $value; - } - $this->lastHeader = $name; - - // continuation of a previous header - } elseif (preg_match('!^\s+(.+)$!', $headerLine, $m) && $this->lastHeader) { - if (!is_array($this->headers[$this->lastHeader])) { - $this->headers[$this->lastHeader] .= ' ' . trim($m[1]); - } else { - $key = count($this->headers[$this->lastHeader]) - 1; - $this->headers[$this->lastHeader][$key] .= ' ' . trim($m[1]); - } - } - } - - /** - * Parses a Set-Cookie header to fill $cookies array - * - * @param string value of Set-Cookie header - * @link http://web.archive.org/web/20080331104521/http://cgi.netscape.com/newsref/std/cookie_spec.html - */ - protected function parseCookie($cookieString) - { - $cookie = array( - 'expires' => null, - 'domain' => null, - 'path' => null, - 'secure' => false - ); - - // Only a name=value pair - if (!strpos($cookieString, ';')) { - $pos = strpos($cookieString, '='); - $cookie['name'] = trim(substr($cookieString, 0, $pos)); - $cookie['value'] = trim(substr($cookieString, $pos + 1)); - - // Some optional parameters are supplied - } else { - $elements = explode(';', $cookieString); - $pos = strpos($elements[0], '='); - $cookie['name'] = trim(substr($elements[0], 0, $pos)); - $cookie['value'] = trim(substr($elements[0], $pos + 1)); - - for ($i = 1; $i < count($elements); $i++) { - if (false === strpos($elements[$i], '=')) { - $elName = trim($elements[$i]); - $elValue = null; - } else { - list ($elName, $elValue) = array_map('trim', explode('=', $elements[$i])); - } - $elName = strtolower($elName); - if ('secure' == $elName) { - $cookie['secure'] = true; - } elseif ('expires' == $elName) { - $cookie['expires'] = str_replace('"', '', $elValue); - } elseif ('path' == $elName || 'domain' == $elName) { - $cookie[$elName] = urldecode($elValue); - } else { - $cookie[$elName] = $elValue; - } - } - } - $this->cookies[] = $cookie; - } - - /** - * Appends a string to the response body - * @param string - */ - public function appendBody($bodyChunk) - { - $this->body .= $bodyChunk; - } - - /** - * Returns the effective URL of the response - * - * This may be different from the request URL if redirects were followed. - * - * @return string - * @link http://pear.php.net/bugs/bug.php?id=18412 - */ - public function getEffectiveUrl() - { - return $this->effectiveUrl; - } - - /** - * Returns the status code - * @return integer - */ - public function getStatus() - { - return $this->code; - } - - /** - * Returns the reason phrase - * @return string - */ - public function getReasonPhrase() - { - return $this->reasonPhrase; - } - - /** - * Whether response is a redirect that can be automatically handled by HTTP_Request2 - * @return bool - */ - public function isRedirect() - { - return in_array($this->code, array(300, 301, 302, 303, 307)) - && isset($this->headers['location']); - } - - /** - * Returns either the named header or all response headers - * - * @param string Name of header to return - * @return string|array Value of $headerName header (null if header is - * not present), array of all response headers if - * $headerName is null - */ - public function getHeader($headerName = null) - { - if (null === $headerName) { - return $this->headers; - } else { - $headerName = strtolower($headerName); - return isset($this->headers[$headerName])? $this->headers[$headerName]: null; - } - } - - /** - * Returns cookies set in response - * - * @return array - */ - public function getCookies() - { - return $this->cookies; - } - - /** - * Returns the body of the response - * - * @return string - * @throws HTTP_Request2_Exception if body cannot be decoded - */ - public function getBody() - { - if (0 == strlen($this->body) || !$this->bodyEncoded || - !in_array(strtolower($this->getHeader('content-encoding')), array('gzip', 'deflate')) - ) { - return $this->body; - - } else { - if (extension_loaded('mbstring') && (2 & ini_get('mbstring.func_overload'))) { - $oldEncoding = mb_internal_encoding(); - mb_internal_encoding('iso-8859-1'); - } - - try { - switch (strtolower($this->getHeader('content-encoding'))) { - case 'gzip': - $decoded = self::decodeGzip($this->body); - break; - case 'deflate': - $decoded = self::decodeDeflate($this->body); - } - } catch (Exception $e) { - } - - if (!empty($oldEncoding)) { - mb_internal_encoding($oldEncoding); - } - if (!empty($e)) { - throw $e; - } - return $decoded; - } - } - - /** - * Get the HTTP version of the response - * - * @return string - */ - public function getVersion() - { - return $this->version; - } - - /** - * Decodes the message-body encoded by gzip - * - * The real decoding work is done by gzinflate() built-in function, this - * method only parses the header and checks data for compliance with - * RFC 1952 - * - * @param string gzip-encoded data - * @return string decoded data - * @throws HTTP_Request2_LogicException - * @throws HTTP_Request2_MessageException - * @link http://tools.ietf.org/html/rfc1952 - */ - public static function decodeGzip($data) - { - $length = strlen($data); - // If it doesn't look like gzip-encoded data, don't bother - if (18 > $length || strcmp(substr($data, 0, 2), "\x1f\x8b")) { - return $data; - } - if (!function_exists('gzinflate')) { - throw new HTTP_Request2_LogicException( - 'Unable to decode body: gzip extension not available', - HTTP_Request2_Exception::MISCONFIGURATION - ); - } - $method = ord(substr($data, 2, 1)); - if (8 != $method) { - throw new HTTP_Request2_MessageException( - 'Error parsing gzip header: unknown compression method', - HTTP_Request2_Exception::DECODE_ERROR - ); - } - $flags = ord(substr($data, 3, 1)); - if ($flags & 224) { - throw new HTTP_Request2_MessageException( - 'Error parsing gzip header: reserved bits are set', - HTTP_Request2_Exception::DECODE_ERROR - ); - } - - // header is 10 bytes minimum. may be longer, though. - $headerLength = 10; - // extra fields, need to skip 'em - if ($flags & 4) { - if ($length - $headerLength - 2 < 8) { - throw new HTTP_Request2_MessageException( - 'Error parsing gzip header: data too short', - HTTP_Request2_Exception::DECODE_ERROR - ); - } - $extraLength = unpack('v', substr($data, 10, 2)); - if ($length - $headerLength - 2 - $extraLength[1] < 8) { - throw new HTTP_Request2_MessageException( - 'Error parsing gzip header: data too short', - HTTP_Request2_Exception::DECODE_ERROR - ); - } - $headerLength += $extraLength[1] + 2; - } - // file name, need to skip that - if ($flags & 8) { - if ($length - $headerLength - 1 < 8) { - throw new HTTP_Request2_MessageException( - 'Error parsing gzip header: data too short', - HTTP_Request2_Exception::DECODE_ERROR - ); - } - $filenameLength = strpos(substr($data, $headerLength), chr(0)); - if (false === $filenameLength || $length - $headerLength - $filenameLength - 1 < 8) { - throw new HTTP_Request2_MessageException( - 'Error parsing gzip header: data too short', - HTTP_Request2_Exception::DECODE_ERROR - ); - } - $headerLength += $filenameLength + 1; - } - // comment, need to skip that also - if ($flags & 16) { - if ($length - $headerLength - 1 < 8) { - throw new HTTP_Request2_MessageException( - 'Error parsing gzip header: data too short', - HTTP_Request2_Exception::DECODE_ERROR - ); - } - $commentLength = strpos(substr($data, $headerLength), chr(0)); - if (false === $commentLength || $length - $headerLength - $commentLength - 1 < 8) { - throw new HTTP_Request2_MessageException( - 'Error parsing gzip header: data too short', - HTTP_Request2_Exception::DECODE_ERROR - ); - } - $headerLength += $commentLength + 1; - } - // have a CRC for header. let's check - if ($flags & 2) { - if ($length - $headerLength - 2 < 8) { - throw new HTTP_Request2_MessageException( - 'Error parsing gzip header: data too short', - HTTP_Request2_Exception::DECODE_ERROR - ); - } - $crcReal = 0xffff & crc32(substr($data, 0, $headerLength)); - $crcStored = unpack('v', substr($data, $headerLength, 2)); - if ($crcReal != $crcStored[1]) { - throw new HTTP_Request2_MessageException( - 'Header CRC check failed', - HTTP_Request2_Exception::DECODE_ERROR - ); - } - $headerLength += 2; - } - // unpacked data CRC and size at the end of encoded data - $tmp = unpack('V2', substr($data, -8)); - $dataCrc = $tmp[1]; - $dataSize = $tmp[2]; - - // finally, call the gzinflate() function - // don't pass $dataSize to gzinflate, see bugs #13135, #14370 - $unpacked = gzinflate(substr($data, $headerLength, -8)); - if (false === $unpacked) { - throw new HTTP_Request2_MessageException( - 'gzinflate() call failed', - HTTP_Request2_Exception::DECODE_ERROR - ); - } elseif ($dataSize != strlen($unpacked)) { - throw new HTTP_Request2_MessageException( - 'Data size check failed', - HTTP_Request2_Exception::DECODE_ERROR - ); - } elseif ((0xffffffff & $dataCrc) != (0xffffffff & crc32($unpacked))) { - throw new HTTP_Request2_Exception( - 'Data CRC check failed', - HTTP_Request2_Exception::DECODE_ERROR - ); - } - return $unpacked; - } - - /** - * Decodes the message-body encoded by deflate - * - * @param string deflate-encoded data - * @return string decoded data - * @throws HTTP_Request2_LogicException - */ - public static function decodeDeflate($data) - { - if (!function_exists('gzuncompress')) { - throw new HTTP_Request2_LogicException( - 'Unable to decode body: gzip extension not available', - HTTP_Request2_Exception::MISCONFIGURATION - ); - } - // RFC 2616 defines 'deflate' encoding as zlib format from RFC 1950, - // while many applications send raw deflate stream from RFC 1951. - // We should check for presence of zlib header and use gzuncompress() or - // gzinflate() as needed. See bug #15305 - $header = unpack('n', substr($data, 0, 2)); - return (0 == $header[1] % 31)? gzuncompress($data): gzinflate($data); - } -} -?> \ No newline at end of file diff --git a/lib/ext/Mail/mime.php b/lib/ext/Mail/mime.php deleted file mode 100644 index c459b91..0000000 --- a/lib/ext/Mail/mime.php +++ /dev/null @@ -1,1476 +0,0 @@ - - * Copyright (c) 2003-2006, PEAR - * 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 - * @author Tomas V.V. Cox - * @author Cipriano Groenendal - * @author Sean Coates - * @author Aleksander Machniak - * @copyright 2003-2006 PEAR - * @license http://www.opensource.org/licenses/bsd-license.php BSD License - * @version 1.8.5 - * @link http://pear.php.net/package/Mail_mime - * - * This class is based on HTML Mime Mail class from - * Richard Heyes which was based also - * in the mime_mail.class by Tobias Ratschiller - * and Sascha Schumann - */ - - -/** - * require PEAR - * - * This package depends on PEAR to raise errors. - */ -require_once 'PEAR.php'; - -/** - * require Mail_mimePart - * - * Mail_mimePart contains the code required to - * create all the different parts a mail can - * consist of. - */ -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 - * @author Tomas V.V. Cox - * @author Cipriano Groenendal - * @author Sean Coates - * @copyright 2003-2006 PEAR - * @license http://www.opensource.org/licenses/bsd-license.php BSD License - * @version Release: 1.8.5 - * @link http://pear.php.net/package/Mail_mime - */ -class Mail_mime -{ - /** - * Contains the plain text part of the email - * - * @var string - * @access private - */ - var $_txtbody; - - /** - * Contains the html part of the email - * - * @var string - * @access private - */ - var $_htmlbody; - - /** - * list of the attached images - * - * @var array - * @access private - */ - var $_html_images = array(); - - /** - * list of the attachements - * - * @var array - * @access private - */ - var $_parts = array(); - - /** - * Headers for the mail - * - * @var array - * @access private - */ - var $_headers = array(); - - /** - * Build parameters - * - * @var array - * @access private - */ - var $_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', - // 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 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 - ); - - /** - * 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 - * @access public - */ - function Mail_mime($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)) { - while (list($key, $value) = each($params)) { - $this->_build_params[$key] = $value; - } - } - } - - /** - * Set build parameter value - * - * @param string $name Parameter name - * @param string $value Parameter value - * - * @return void - * @access public - * @since 1.6.0 - */ - function setParam($name, $value) - { - $this->_build_params[$name] = $value; - } - - /** - * Get build parameter value - * - * @param string $name Parameter name - * - * @return mixed Parameter value - * @access public - * @since 1.6.0 - */ - 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 - * @access public - */ - function setTXTBody($data, $isfile = false, $append = false) - { - if (!$isfile) { - if (!$append) { - $this->_txtbody = $data; - } else { - $this->_txtbody .= $data; - } - } else { - $cont = $this->_file2str($data); - if (PEAR::isError($cont)) { - return $cont; - } - if (!$append) { - $this->_txtbody = $cont; - } else { - $this->_txtbody .= $cont; - } - } - return true; - } - - /** - * Get message text body - * - * @return string Text body - * @access public - * @since 1.6.0 - */ - 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 - * @access public - */ - function setHTMLBody($data, $isfile = false) - { - if (!$isfile) { - $this->_htmlbody = $data; - } else { - $cont = $this->_file2str($data); - if (PEAR::isError($cont)) { - return $cont; - } - $this->_htmlbody = $cont; - } - - return true; - } - - /** - * Get message HTML body - * - * @return string HTML body - * @access public - * @since 1.6.0 - */ - function getHTMLBody() - { - return $this->_htmlbody; - } - - /** - * Adds an image to the list of embedded images. - * - * @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. - * Only used if $file is the image data. - * @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 - * @access 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 (PEAR::isError($filedata = $this->_file2str($file))) { - return $filedata; - } - } - $filename = ($name ? $name : $file); - } else { - $filedata = $file; - $filename = $name; - } - - if (!$content_id) { - $content_id = md5(uniqid(time())); - } - - $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 string $file The file name of the file to attach - * or the file contents itself - * @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 : - * - * @return mixed True on success or PEAR_Error object - * @access 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() - ) { - $bodyfile = null; - - if ($isfile) { - // Don't load file into memory - if ($this->_build_params['delay_file_io']) { - $filedata = null; - $bodyfile = $file; - } else { - if (PEAR::isError($filedata = $this->_file2str($file))) { - return $filedata; - } - } - // Force the name the user supplied, otherwise use $file - $filename = ($name ? $name : $file); - } else { - $filedata = $file; - $filename = $name; - } - - if (!strlen($filename)) { - $msg = "The supplied filename for the attachment can't be empty"; - $err = PEAR::raiseError($msg); - return $err; - } - $filename = $this->_basename($filename); - - $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; - } - - /** - * 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 - * @access private - */ - function &_file2str($file_name) - { - // Check state of file and raise an error properly - if (!file_exists($file_name)) { - $err = PEAR::raiseError('File not found: ' . $file_name); - return $err; - } - if (!is_file($file_name)) { - $err = PEAR::raiseError('Not a regular file: ' . $file_name); - return $err; - } - if (!is_readable($file_name)) { - $err = PEAR::raiseError('File is not readable: ' . $file_name); - return $err; - } - - // Temporarily reset magic_quotes_runtime and read file contents - if ($magic_quote_setting = get_magic_quotes_runtime()) { - @ini_set('magic_quotes_runtime', 0); - } - $cont = file_get_contents($file_name); - if ($magic_quote_setting) { - @ini_set('magic_quotes_runtime', $magic_quote_setting); - } - - 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 - * null if a new object is to be created. - * @param string $text The text to add. - * - * @return object The text mimePart object - * @access private - */ - function &_addTextPart(&$obj, $text) - { - $params['content_type'] = 'text/plain'; - $params['encoding'] = $this->_build_params['text_encoding']; - $params['charset'] = $this->_build_params['text_charset']; - $params['eol'] = $this->_build_params['eol']; - - if (is_object($obj)) { - $ret = $obj->addSubpart($text, $params); - return $ret; - } else { - $ret = new Mail_mimePart($text, $params); - return $ret; - } - } - - /** - * 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 - * null if a new object is to be created. - * - * @return object The html mimePart object - * @access private - */ - function &_addHtmlPart(&$obj) - { - $params['content_type'] = 'text/html'; - $params['encoding'] = $this->_build_params['html_encoding']; - $params['charset'] = $this->_build_params['html_charset']; - $params['eol'] = $this->_build_params['eol']; - - if (is_object($obj)) { - $ret = $obj->addSubpart($this->_htmlbody, $params); - return $ret; - } else { - $ret = new Mail_mimePart($this->_htmlbody, $params); - return $ret; - } - } - - /** - * Creates a new mimePart object, using multipart/mixed as - * the initial content-type and returns it during the - * build process. - * - * @return object The multipart/mixed mimePart object - * @access private - */ - function &_addMixedPart() - { - $params = array(); - $params['content_type'] = 'multipart/mixed'; - $params['eol'] = $this->_build_params['eol']; - - // Create empty multipart/mixed Mail_mimePart object to return - $ret = new Mail_mimePart('', $params); - return $ret; - } - - /** - * 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 - * null if a new object is to be created. - * - * @return object The multipart/mixed mimePart object - * @access private - */ - function &_addAlternativePart(&$obj) - { - $params['content_type'] = 'multipart/alternative'; - $params['eol'] = $this->_build_params['eol']; - - if (is_object($obj)) { - return $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 - * null if a new object is to be created - * - * @return object The multipart/mixed mimePart object - * @access private - */ - function &_addRelatedPart(&$obj) - { - $params['content_type'] = 'multipart/related'; - $params['eol'] = $this->_build_params['eol']; - - if (is_object($obj)) { - return $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 object The image mimePart object - * @access private - */ - 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']; - } - - $ret = $obj->addSubpart($value['body'], $params); - return $ret; - } - - /** - * 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 array $value The attachment information - * - * @return object The image mimePart object - * @access private - */ - function &_addAttachmentPart(&$obj, $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']; - } - - $ret = $obj->addSubpart($value['body'], $params); - return $ret; - } - - /** - * 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() function. - * 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 - * @access public - */ - function getMessage($separation = null, $params = null, $headers = null, - $overwrite = false - ) { - if ($separation === null) { - $separation = $this->_build_params['eol']; - } - - $body = $this->get($params); - - if (PEAR::isError($body)) { - return $body; - } - - $head = $this->txtHeaders($headers, $overwrite); - $mail = $head . $separation . $body; - return $mail; - } - - /** - * 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() function. See &get for more info. - * - * @return mixed The e-mail body or PEAR error object - * @access public - * @since 1.6.0 - */ - 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() function. 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 - * @access public - * @since 1.6.0 - */ - 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)) { - $err = PEAR::raiseError('File is not writable: ' . $filename); - return $err; - } - - // Temporarily reset magic_quotes_runtime and read file contents - if ($magic_quote_setting = get_magic_quotes_runtime()) { - @ini_set('magic_quotes_runtime', 0); - } - - if (!($fh = fopen($filename, 'ab'))) { - $err = PEAR::raiseError('Unable to open file: ' . $filename); - return $err; - } - - // Write message headers into file (skipping Content-* headers) - $head = $this->txtHeaders($headers, $overwrite, true); - if (fwrite($fh, $head) === false) { - $err = PEAR::raiseError('Error writing to file: ' . $filename); - return $err; - } - - fclose($fh); - - if ($magic_quote_setting) { - @ini_set('magic_quotes_runtime', $magic_quote_setting); - } - - // 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. - * - * @param string $filename Output file location - * @param array $params The Build parameters passed to the - * &get() function. See &get for more info. - * - * @return mixed True or PEAR error object - * @access public - * @since 1.6.0 - */ - function saveMessageBody($filename, $params = null) - { - // Check state of file and raise an error properly - if (file_exists($filename) && !is_writable($filename)) { - $err = PEAR::raiseError('File is not writable: ' . $filename); - return $err; - } - - // Temporarily reset magic_quotes_runtime and read file contents - if ($magic_quote_setting = get_magic_quotes_runtime()) { - @ini_set('magic_quotes_runtime', 0); - } - - if (!($fh = fopen($filename, 'ab'))) { - $err = PEAR::raiseError('Unable to open file: ' . $filename); - return $err; - } - - // Write the rest of the message into file - $res = $this->get($params, $filename, true); - - 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 resource $filename Output file where to save the message instead of - * returning it - * @param boolean $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 - * @access public - */ - function &get($params = null, $filename = null, $skip_head = false) - { - if (isset($params)) { - while (list($key, $value) = each($params)) { - $this->_build_params[$key] = $value; - } - } - - 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) && isset($this->_htmlbody)) { - foreach ($this->_html_images as $key => $value) { - $regex = array(); - $regex[] = '#(\s)((?i)src|background|href(?-i))\s*=\s*(["\']?)' . - preg_quote($value['name'], '#') . '\3#'; - $regex[] = '#(?i)url(?-i)\(\s*(["\']?)' . - preg_quote($value['name'], '#') . '\1\s*\)#'; - - $rep = array(); - $rep[] = '\1\2=\3cid:' . $value['cid'] .'\3'; - $rep[] = '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(); - - $null = null; - $attachments = count($this->_parts) ? true : false; - $html_images = count($this->_html_images) ? true : false; - $html = strlen($this->_htmlbody) ? true : false; - $text = (!$html && strlen($this->_txtbody)) ? true : false; - - switch (true) { - case $text && !$attachments: - $message =& $this->_addTextPart($null, $this->_txtbody); - break; - - case !$text && !$html && $attachments: - $message =& $this->_addMixedPart(); - for ($i = 0; $i < count($this->_parts); $i++) { - $this->_addAttachmentPart($message, $this->_parts[$i]); - } - break; - - case $text && $attachments: - $message =& $this->_addMixedPart(); - $this->_addTextPart($message, $this->_txtbody); - for ($i = 0; $i < count($this->_parts); $i++) { - $this->_addAttachmentPart($message, $this->_parts[$i]); - } - break; - - case $html && !$attachments && !$html_images: - if (isset($this->_txtbody)) { - $message =& $this->_addAlternativePart($null); - $this->_addTextPart($message, $this->_txtbody); - $this->_addHtmlPart($message); - } else { - $message =& $this->_addHtmlPart($null); - } - break; - - case $html && !$attachments && $html_images: - // * Content-Type: multipart/alternative; - // * text - // * Content-Type: multipart/related; - // * html - // * image... - if (isset($this->_txtbody)) { - $message =& $this->_addAlternativePart($null); - $this->_addTextPart($message, $this->_txtbody); - - $ht =& $this->_addRelatedPart($message); - $this->_addHtmlPart($ht); - for ($i = 0; $i < count($this->_html_images); $i++) { - $this->_addHtmlImagePart($ht, $this->_html_images[$i]); - } - } else { - // * Content-Type: multipart/related; - // * html - // * image... - $message =& $this->_addRelatedPart($null); - $this->_addHtmlPart($message); - for ($i = 0; $i < count($this->_html_images); $i++) { - $this->_addHtmlImagePart($message, $this->_html_images[$i]); - } - } - /* - // #13444, #9725: the code below was a non-RFC compliant hack - // * Content-Type: multipart/related; - // * Content-Type: multipart/alternative; - // * text - // * html - // * image... - $message =& $this->_addRelatedPart($null); - if (isset($this->_txtbody)) { - $alt =& $this->_addAlternativePart($message); - $this->_addTextPart($alt, $this->_txtbody); - $this->_addHtmlPart($alt); - } else { - $this->_addHtmlPart($message); - } - for ($i = 0; $i < count($this->_html_images); $i++) { - $this->_addHtmlImagePart($message, $this->_html_images[$i]); - } - */ - break; - - case $html && $attachments && !$html_images: - $message =& $this->_addMixedPart(); - if (isset($this->_txtbody)) { - $alt =& $this->_addAlternativePart($message); - $this->_addTextPart($alt, $this->_txtbody); - $this->_addHtmlPart($alt); - } else { - $this->_addHtmlPart($message); - } - for ($i = 0; $i < count($this->_parts); $i++) { - $this->_addAttachmentPart($message, $this->_parts[$i]); - } - break; - - case $html && $attachments && $html_images: - $message =& $this->_addMixedPart(); - if (isset($this->_txtbody)) { - $alt =& $this->_addAlternativePart($message); - $this->_addTextPart($alt, $this->_txtbody); - $rel =& $this->_addRelatedPart($alt); - } else { - $rel =& $this->_addRelatedPart($message); - } - $this->_addHtmlPart($rel); - for ($i = 0; $i < count($this->_html_images); $i++) { - $this->_addHtmlImagePart($rel, $this->_html_images[$i]); - } - for ($i = 0; $i < count($this->_parts); $i++) { - $this->_addAttachmentPart($message, $this->_parts[$i]); - } - break; - - } - - if (!isset($message)) { - $ret = null; - return $ret; - } - - // 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 (PEAR::isError($headers)) { - return $headers; - } - $this->_headers = array_merge($this->_headers, $headers); - $ret = null; - return $ret; - } else { - $output = $message->encode($boundary, $skip_head); - if (PEAR::isError($output)) { - return $output; - } - $this->_headers = array_merge($this->_headers, $output['headers']); - $body = $output['body']; - return $body; - } - } - - /** - * 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 - * @access public - */ - function &headers($xtra_headers = null, $overwrite = false, $skip_content = false) - { - // Add mime version header - $headers['MIME-Version'] = '1.0'; - - // Content-Type and Content-Transfer-Encoding headers should already - // be present if get() was called, but we'll re-set them to make sure - // we got them when called before get() or something in the message - // has been changed after get() [#14780] - if (!$skip_content) { - $headers += $this->_contentHeaders(); - } - - 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); - } - - $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']; - } - - $encodedHeaders = $this->_encodeHeaders($headers); - return $encodedHeaders; - } - - /** - * Get the text version of the headers - * (useful 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 - * @access 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 - * @access public - * @since 1.7.0 - */ - 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; - } - 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 (preg_match('/^multipart\//i', $type)) { - 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 - * @access 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 - * @access 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 - * @access 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 - * @access 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 - * @access 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 - * @access 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 - * @access private - */ - function _encodeHeaders($input, $params = array()) - { - $build_params = $this->_build_params; - while (list($key, $value) = each($params)) { - $build_params[$key] = $value; - } - - 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 { - $input[$hdr_name] = $this->encodeHeader( - $hdr_name, $hdr_value, - $build_params['head_charset'], $build_params['head_encoding'] - ); - } - } - - 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) - * @access public - * @since 1.5.3 - */ - function encodeHeader($name, $value, $charset, $encoding) - { - $mime_part = new Mail_mimePart; - return $mime_part->encodeHeader( - $name, $value, $charset, $encoding, $this->_build_params['eol'] - ); - } - - /** - * Get file's basename (locale independent) - * - * @param string $filename Filename - * - * @return string Basename - * @access private - */ - 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 - * @access private - */ - function _contentHeaders() - { - $attachments = count($this->_parts) ? true : false; - $html_images = count($this->_html_images) ? true : false; - $html = strlen($this->_htmlbody) ? true : false; - $text = (!$html && strlen($this->_txtbody)) ? true : false; - $headers = array(); - - // See get() - switch (true) { - case $text && !$attachments: - $headers['Content-Type'] = 'text/plain'; - break; - - case !$text && !$html && $attachments: - case $text && $attachments: - case $html && $attachments && !$html_images: - case $html && $attachments && $html_images: - $headers['Content-Type'] = 'multipart/mixed'; - break; - - case $html && !$attachments && !$html_images && isset($this->_txtbody): - case $html && !$attachments && $html_images && isset($this->_txtbody): - $headers['Content-Type'] = 'multipart/alternative'; - break; - - case $html && !$attachments && !$html_images && !isset($this->_txtbody): - $headers['Content-Type'] = 'text/html'; - break; - - case $html && !$attachments && $html_images && !isset($this->_txtbody): - $headers['Content-Type'] = 'multipart/related'; - break; - - default: - 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 - $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 - $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 { - // 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 - * @access private - */ - 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']); - - 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'; - } - - // 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'; - } - } - -} // End of class diff --git a/lib/ext/Mail/mimeDecode.php b/lib/ext/Mail/mimeDecode.php deleted file mode 100644 index 677d245..0000000 --- a/lib/ext/Mail/mimeDecode.php +++ /dev/null @@ -1,1003 +0,0 @@ - - * Copyright (c) 2003-2006, PEAR - * 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 - * @author George Schlossnagle - * @author Cipriano Groenendal - * @author Sean Coates - * @copyright 2003-2006 PEAR - * @license http://www.opensource.org/licenses/bsd-license.php BSD License - * @version CVS: $Id$ - * @link http://pear.php.net/package/Mail_mime - */ - - -/** - * require PEAR - * - * This package depends on PEAR to raise errors. - */ -require_once 'PEAR.php'; - - -/** - * The Mail_mimeDecode class is used to decode mail/mime messages - * - * This class will parse a raw mime email and return the structure. - * Returned structure is similar to that returned by imap_fetchstructure(). - * - * +----------------------------- IMPORTANT ------------------------------+ - * | Usage of this class compared to native php extensions such as | - * | mailparse or imap, is slow and may be feature deficient. If available| - * | you are STRONGLY recommended to use the php extensions. | - * +----------------------------------------------------------------------+ - * - * @category Mail - * @package Mail_Mime - * @author Richard Heyes - * @author George Schlossnagle - * @author Cipriano Groenendal - * @author Sean Coates - * @copyright 2003-2006 PEAR - * @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_mimeDecode extends PEAR -{ - /** - * The raw email to decode - * - * @var string - * @access private - */ - var $_input; - - /** - * The header part of the input - * - * @var string - * @access private - */ - var $_header; - - /** - * The body part of the input - * - * @var string - * @access private - */ - var $_body; - - /** - * If an error occurs, this is used to store the message - * - * @var string - * @access private - */ - var $_error; - - /** - * Flag to determine whether to include bodies in the - * returned object. - * - * @var boolean - * @access private - */ - var $_include_bodies; - - /** - * Flag to determine whether to decode bodies - * - * @var boolean - * @access private - */ - var $_decode_bodies; - - /** - * Flag to determine whether to decode headers - * - * @var boolean - * @access private - */ - var $_decode_headers; - - /** - * Flag to determine whether to include attached messages - * as body in the returned object. Depends on $_include_bodies - * - * @var boolean - * @access private - */ - var $_rfc822_bodies; - - /** - * Constructor. - * - * Sets up the object, initialise the variables, and splits and - * stores the header and body of the input. - * - * @param string The input to decode - * @access public - */ - function Mail_mimeDecode($input) - { - list($header, $body) = $this->_splitBodyHeader($input); - - $this->_input = $input; - $this->_header = $header; - $this->_body = $body; - $this->_decode_bodies = false; - $this->_include_bodies = true; - $this->_rfc822_bodies = false; - } - - /** - * Begins the decoding process. If called statically - * it will create an object and call the decode() method - * of it. - * - * @param array An array of various parameters that determine - * various things: - * include_bodies - Whether to include the body in the returned - * object. - * decode_bodies - Whether to decode the bodies - * of the parts. (Transfer encoding) - * decode_headers - Whether to decode headers - * input - If called statically, this will be treated - * as the input - * @return object Decoded results - * @access public - */ - function decode($params = null) - { - // determine if this method has been called statically - $isStatic = empty($this) || !is_a($this, __CLASS__); - - // Have we been called statically? - // If so, create an object and pass details to that. - if ($isStatic AND isset($params['input'])) { - - $obj = new Mail_mimeDecode($params['input']); - $structure = $obj->decode($params); - - // Called statically but no input - } elseif ($isStatic) { - return PEAR::raiseError('Called statically and no input given'); - - // Called via an object - } else { - $this->_include_bodies = isset($params['include_bodies']) ? - $params['include_bodies'] : false; - $this->_decode_bodies = isset($params['decode_bodies']) ? - $params['decode_bodies'] : false; - $this->_decode_headers = isset($params['decode_headers']) ? - $params['decode_headers'] : false; - $this->_rfc822_bodies = isset($params['rfc_822bodies']) ? - $params['rfc_822bodies'] : false; - - $structure = $this->_decode($this->_header, $this->_body); - if ($structure === false) { - $structure = $this->raiseError($this->_error); - } - } - - return $structure; - } - - /** - * Performs the decoding. Decodes the body string passed to it - * If it finds certain content-types it will call itself in a - * recursive fashion - * - * @param string Header section - * @param string Body section - * @return object Results of decoding process - * @access private - */ - function _decode($headers, $body, $default_ctype = 'text/plain') - { - $return = new stdClass; - $return->headers = array(); - $headers = $this->_parseHeaders($headers); - - foreach ($headers as $value) { - $value['value'] = $this->_decode_headers ? $this->_decodeHeader($value['value']) : $value['value']; - if (isset($return->headers[strtolower($value['name'])]) AND !is_array($return->headers[strtolower($value['name'])])) { - $return->headers[strtolower($value['name'])] = array($return->headers[strtolower($value['name'])]); - $return->headers[strtolower($value['name'])][] = $value['value']; - - } elseif (isset($return->headers[strtolower($value['name'])])) { - $return->headers[strtolower($value['name'])][] = $value['value']; - - } else { - $return->headers[strtolower($value['name'])] = $value['value']; - } - } - - - foreach ($headers as $key => $value) { - $headers[$key]['name'] = strtolower($headers[$key]['name']); - switch ($headers[$key]['name']) { - - case 'content-type': - $content_type = $this->_parseHeaderValue($headers[$key]['value']); - - if (preg_match('/([0-9a-z+.-]+)\/([0-9a-z+.-]+)/i', $content_type['value'], $regs)) { - $return->ctype_primary = $regs[1]; - $return->ctype_secondary = $regs[2]; - } - - if (isset($content_type['other'])) { - foreach($content_type['other'] as $p_name => $p_value) { - $return->ctype_parameters[$p_name] = $p_value; - } - } - break; - - case 'content-disposition': - $content_disposition = $this->_parseHeaderValue($headers[$key]['value']); - $return->disposition = $content_disposition['value']; - if (isset($content_disposition['other'])) { - foreach($content_disposition['other'] as $p_name => $p_value) { - $return->d_parameters[$p_name] = $p_value; - } - } - break; - - case 'content-transfer-encoding': - $content_transfer_encoding = $this->_parseHeaderValue($headers[$key]['value']); - break; - } - } - - if (isset($content_type)) { - switch (strtolower($content_type['value'])) { - case 'text/plain': - $encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit'; - $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding) : $body) : null; - break; - - case 'text/html': - $encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit'; - $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding) : $body) : null; - break; - - case 'multipart/parallel': - case 'multipart/appledouble': // Appledouble mail - case 'multipart/report': // RFC1892 - case 'multipart/signed': // PGP - case 'multipart/digest': - case 'multipart/alternative': - case 'multipart/related': - case 'multipart/mixed': - case 'application/vnd.wap.multipart.related': - if(!isset($content_type['other']['boundary'])){ - $this->_error = 'No boundary found for ' . $content_type['value'] . ' part'; - return false; - } - - $default_ctype = (strtolower($content_type['value']) === 'multipart/digest') ? 'message/rfc822' : 'text/plain'; - - $parts = $this->_boundarySplit($body, $content_type['other']['boundary']); - for ($i = 0; $i < count($parts); $i++) { - list($part_header, $part_body) = $this->_splitBodyHeader($parts[$i]); - $part = $this->_decode($part_header, $part_body, $default_ctype); - if($part === false) - $part = $this->raiseError($this->_error); - $return->parts[] = $part; - } - break; - - case 'message/rfc822': - if ($this->_rfc822_bodies) { - $encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit'; - $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding) : $body); - } - $obj = new Mail_mimeDecode($body); - $return->parts[] = $obj->decode(array('include_bodies' => $this->_include_bodies, - 'decode_bodies' => $this->_decode_bodies, - 'decode_headers' => $this->_decode_headers)); - unset($obj); - break; - - default: - if(!isset($content_transfer_encoding['value'])) - $content_transfer_encoding['value'] = '7bit'; - $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $content_transfer_encoding['value']) : $body) : null; - break; - } - - } else { - $ctype = explode('/', $default_ctype); - $return->ctype_primary = $ctype[0]; - $return->ctype_secondary = $ctype[1]; - $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body) : $body) : null; - } - - return $return; - } - - /** - * Given the output of the above function, this will return an - * array of references to the parts, indexed by mime number. - * - * @param object $structure The structure to go through - * @param string $mime_number Internal use only. - * @return array Mime numbers - */ - function &getMimeNumbers(&$structure, $no_refs = false, $mime_number = '', $prepend = '') - { - $return = array(); - if (!empty($structure->parts)) { - if ($mime_number != '') { - $structure->mime_id = $prepend . $mime_number; - $return[$prepend . $mime_number] = &$structure; - } - for ($i = 0; $i < count($structure->parts); $i++) { - - - if (!empty($structure->headers['content-type']) AND substr(strtolower($structure->headers['content-type']), 0, 8) == 'message/') { - $prepend = $prepend . $mime_number . '.'; - $_mime_number = ''; - } else { - $_mime_number = ($mime_number == '' ? $i + 1 : sprintf('%s.%s', $mime_number, $i + 1)); - } - - $arr = &Mail_mimeDecode::getMimeNumbers($structure->parts[$i], $no_refs, $_mime_number, $prepend); - foreach ($arr as $key => $val) { - $no_refs ? $return[$key] = '' : $return[$key] = &$arr[$key]; - } - } - } else { - if ($mime_number == '') { - $mime_number = '1'; - } - $structure->mime_id = $prepend . $mime_number; - $no_refs ? $return[$prepend . $mime_number] = '' : $return[$prepend . $mime_number] = &$structure; - } - - return $return; - } - - /** - * Given a string containing a header and body - * section, this function will split them (at the first - * blank line) and return them. - * - * @param string Input to split apart - * @return array Contains header and body section - * @access private - */ - function _splitBodyHeader($input) - { - if (preg_match("/^(.*?)\r?\n\r?\n(.*)/s", $input, $match)) { - return array($match[1], $match[2]); - } - // bug #17325 - empty bodies are allowed. - we just check that at least one line - // of headers exist.. - if (count(explode("\n",$input))) { - return array($input, ''); - } - $this->_error = 'Could not split header and body'; - return false; - } - - /** - * Parse headers given in $input and return - * as assoc array. - * - * @param string Headers to parse - * @return array Contains parsed headers - * @access private - */ - function _parseHeaders($input) - { - - if ($input !== '') { - // Unfold the input - $input = preg_replace("/\r?\n/", "\r\n", $input); - //#7065 - wrapping.. with encoded stuff.. - probably not needed, - // wrapping space should only get removed if the trailing item on previous line is a - // encoded character - $input = preg_replace("/=\r\n(\t| )+/", '=', $input); - $input = preg_replace("/\r\n(\t| )+/", ' ', $input); - - $headers = explode("\r\n", trim($input)); - - foreach ($headers as $value) { - $hdr_name = substr($value, 0, $pos = strpos($value, ':')); - $hdr_value = substr($value, $pos+1); - if($hdr_value[0] == ' ') - $hdr_value = substr($hdr_value, 1); - - $return[] = array( - 'name' => $hdr_name, - 'value' => $hdr_value - ); - } - } else { - $return = array(); - } - - return $return; - } - - /** - * Function to parse a header value, - * extract first part, and any secondary - * parts (after ;) This function is not as - * robust as it could be. Eg. header comments - * in the wrong place will probably break it. - * - * @param string Header value to parse - * @return array Contains parsed result - * @access private - */ - function _parseHeaderValue($input) - { - - if (($pos = strpos($input, ';')) === false) { - $input = $this->_decode_headers ? $this->_decodeHeader($input) : $input; - $return['value'] = trim($input); - return $return; - } - - - - $value = substr($input, 0, $pos); - $value = $this->_decode_headers ? $this->_decodeHeader($value) : $value; - $return['value'] = trim($value); - $input = trim(substr($input, $pos+1)); - - if (!strlen($input) > 0) { - return $return; - } - // at this point input contains xxxx=".....";zzzz="...." - // since we are dealing with quoted strings, we need to handle this properly.. - $i = 0; - $l = strlen($input); - $key = ''; - $val = false; // our string - including quotes.. - $q = false; // in quote.. - $lq = ''; // last quote.. - - while ($i < $l) { - - $c = $input[$i]; - //var_dump(array('i'=>$i,'c'=>$c,'q'=>$q, 'lq'=>$lq, 'key'=>$key, 'val' =>$val)); - - $escaped = false; - if ($c == '\\') { - $i++; - if ($i == $l-1) { // end of string. - break; - } - $escaped = true; - $c = $input[$i]; - } - - - // state - in key.. - if ($val === false) { - if (!$escaped && $c == '=') { - $val = ''; - $key = trim($key); - $i++; - continue; - } - if (!$escaped && $c == ';') { - if ($key) { // a key without a value.. - $key= trim($key); - $return['other'][$key] = ''; - $return['other'][strtolower($key)] = ''; - } - $key = ''; - } - $key .= $c; - $i++; - continue; - } - - // state - in value.. (as $val is set..) - - if ($q === false) { - // not in quote yet. - if ((!strlen($val) || $lq !== false) && $c == ' ' || $c == "\t") { - $i++; - continue; // skip leading spaces after '=' or after '"' - } - if (!$escaped && ($c == '"' || $c == "'")) { - // start quoted area.. - $q = $c; - // in theory should not happen raw text in value part.. - // but we will handle it as a merged part of the string.. - $val = !strlen(trim($val)) ? '' : trim($val); - $i++; - continue; - } - // got end.... - if (!$escaped && $c == ';') { - - $val = trim($val); - $added = false; - if (preg_match('/\*[0-9]+$/', $key)) { - // this is the extended aaa*0=...;aaa*1=.... code - // it assumes the pieces arrive in order, and are valid... - $key = preg_replace('/\*[0-9]+$/', '', $key); - if (isset($return['other'][$key])) { - $return['other'][$key] .= $val; - if (strtolower($key) != $key) { - $return['other'][strtolower($key)] .= $val; - } - $added = true; - } - // continue and use standard setters.. - } - if (!$added) { - $return['other'][$key] = $val; - $return['other'][strtolower($key)] = $val; - } - $val = false; - $key = ''; - $lq = false; - $i++; - continue; - } - - $val .= $c; - $i++; - continue; - } - - // state - in quote.. - if (!$escaped && $c == $q) { // potential exit state.. - - // end of quoted string.. - $lq = $q; - $q = false; - $i++; - continue; - } - - // normal char inside of quoted string.. - $val.= $c; - $i++; - } - - // do we have anything left.. - if (strlen(trim($key)) || $val !== false) { - - $val = trim($val); - $added = false; - if ($val !== false && preg_match('/\*[0-9]+$/', $key)) { - // no dupes due to our crazy regexp. - $key = preg_replace('/\*[0-9]+$/', '', $key); - if (isset($return['other'][$key])) { - $return['other'][$key] .= $val; - if (strtolower($key) != $key) { - $return['other'][strtolower($key)] .= $val; - } - $added = true; - } - // continue and use standard setters.. - } - if (!$added) { - $return['other'][$key] = $val; - $return['other'][strtolower($key)] = $val; - } - } - // decode values. - foreach($return['other'] as $key =>$val) { - $return['other'][$key] = $this->_decode_headers ? $this->_decodeHeader($val) : $val; - } - //print_r($return); - return $return; - } - - /** - * This function splits the input based - * on the given boundary - * - * @param string Input to parse - * @return array Contains array of resulting mime parts - * @access private - */ - function _boundarySplit($input, $boundary) - { - $parts = array(); - - $bs_possible = substr($boundary, 2, -2); - $bs_check = '\"' . $bs_possible . '\"'; - - if ($boundary == $bs_check) { - $boundary = $bs_possible; - } - $tmp = preg_split("/--".preg_quote($boundary, '/')."((?=\s)|--)/", $input); - - $len = count($tmp) -1; - for ($i = 1; $i < $len; $i++) { - if (strlen(trim($tmp[$i]))) { - $parts[] = $tmp[$i]; - } - } - - // add the last part on if it does not end with the 'closing indicator' - if (!empty($tmp[$len]) && strlen(trim($tmp[$len])) && $tmp[$len][0] != '-') { - $parts[] = $tmp[$len]; - } - return $parts; - } - - /** - * Given a header, this function will decode it - * according to RFC2047. Probably not *exactly* - * conformant, but it does pass all the given - * examples (in RFC2047). - * - * @param string Input header value to decode - * @return string Decoded header value - * @access private - */ - function _decodeHeader($input) - { - // Remove white space between encoded-words - $input = preg_replace('/(=\?[^?]+\?(q|b)\?[^?]*\?=)(\s)+=\?/i', '\1=?', $input); - - // For each encoded-word... - while (preg_match('/(=\?([^?]+)\?(q|b)\?([^?]*)\?=)/i', $input, $matches)) { - - $encoded = $matches[1]; - $charset = $matches[2]; - $encoding = $matches[3]; - $text = $matches[4]; - - switch (strtolower($encoding)) { - case 'b': - $text = base64_decode($text); - break; - - case 'q': - $text = str_replace('_', ' ', $text); - preg_match_all('/=([a-f0-9]{2})/i', $text, $matches); - foreach($matches[1] as $value) - $text = str_replace('='.$value, chr(hexdec($value)), $text); - break; - } - - $input = str_replace($encoded, $text, $input); - } - - return $input; - } - - /** - * Given a body string and an encoding type, - * this function will decode and return it. - * - * @param string Input body to decode - * @param string Encoding type to use. - * @return string Decoded body - * @access private - */ - function _decodeBody($input, $encoding = '7bit') - { - switch (strtolower($encoding)) { - case '7bit': - return $input; - break; - - case 'quoted-printable': - return $this->_quotedPrintableDecode($input); - break; - - case 'base64': - return base64_decode($input); - break; - - default: - return $input; - } - } - - /** - * Given a quoted-printable string, this - * function will decode and return it. - * - * @param string Input body to decode - * @return string Decoded body - * @access private - */ - function _quotedPrintableDecode($input) - { - // Remove soft line breaks - $input = preg_replace("/=\r?\n/", '', $input); - - // Replace encoded characters - $input = preg_replace('/=([a-f0-9]{2})/ie', "chr(hexdec('\\1'))", $input); - - return $input; - } - - /** - * Checks the input for uuencoded files and returns - * an array of them. Can be called statically, eg: - * - * $files =& Mail_mimeDecode::uudecode($some_text); - * - * It will check for the begin 666 ... end syntax - * however and won't just blindly decode whatever you - * pass it. - * - * @param string Input body to look for attahcments in - * @return array Decoded bodies, filenames and permissions - * @access public - * @author Unknown - */ - function &uudecode($input) - { - // Find all uuencoded sections - preg_match_all("/begin ([0-7]{3}) (.+)\r?\n(.+)\r?\nend/Us", $input, $matches); - - for ($j = 0; $j < count($matches[3]); $j++) { - - $str = $matches[3][$j]; - $filename = $matches[2][$j]; - $fileperm = $matches[1][$j]; - - $file = ''; - $str = preg_split("/\r?\n/", trim($str)); - $strlen = count($str); - - for ($i = 0; $i < $strlen; $i++) { - $pos = 1; - $d = 0; - $len=(int)(((ord(substr($str[$i],0,1)) -32) - ' ') & 077); - - while (($d + 3 <= $len) AND ($pos + 4 <= strlen($str[$i]))) { - $c0 = (ord(substr($str[$i],$pos,1)) ^ 0x20); - $c1 = (ord(substr($str[$i],$pos+1,1)) ^ 0x20); - $c2 = (ord(substr($str[$i],$pos+2,1)) ^ 0x20); - $c3 = (ord(substr($str[$i],$pos+3,1)) ^ 0x20); - $file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4)); - - $file .= chr(((($c1 - ' ') & 077) << 4) | ((($c2 - ' ') & 077) >> 2)); - - $file .= chr(((($c2 - ' ') & 077) << 6) | (($c3 - ' ') & 077)); - - $pos += 4; - $d += 3; - } - - if (($d + 2 <= $len) && ($pos + 3 <= strlen($str[$i]))) { - $c0 = (ord(substr($str[$i],$pos,1)) ^ 0x20); - $c1 = (ord(substr($str[$i],$pos+1,1)) ^ 0x20); - $c2 = (ord(substr($str[$i],$pos+2,1)) ^ 0x20); - $file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4)); - - $file .= chr(((($c1 - ' ') & 077) << 4) | ((($c2 - ' ') & 077) >> 2)); - - $pos += 3; - $d += 2; - } - - if (($d + 1 <= $len) && ($pos + 2 <= strlen($str[$i]))) { - $c0 = (ord(substr($str[$i],$pos,1)) ^ 0x20); - $c1 = (ord(substr($str[$i],$pos+1,1)) ^ 0x20); - $file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4)); - - } - } - $files[] = array('filename' => $filename, 'fileperm' => $fileperm, 'filedata' => $file); - } - - return $files; - } - - /** - * getSendArray() returns the arguments required for Mail::send() - * used to build the arguments for a mail::send() call - * - * Usage: - * $mailtext = Full email (for example generated by a template) - * $decoder = new Mail_mimeDecode($mailtext); - * $parts = $decoder->getSendArray(); - * if (!PEAR::isError($parts) { - * list($recipents,$headers,$body) = $parts; - * $mail = Mail::factory('smtp'); - * $mail->send($recipents,$headers,$body); - * } else { - * echo $parts->message; - * } - * @return mixed array of recipeint, headers,body or Pear_Error - * @access public - * @author Alan Knowles - */ - function getSendArray() - { - // prevent warning if this is not set - $this->_decode_headers = FALSE; - $headerlist =$this->_parseHeaders($this->_header); - $to = ""; - if (!$headerlist) { - return $this->raiseError("Message did not contain headers"); - } - foreach($headerlist as $item) { - $header[$item['name']] = $item['value']; - switch (strtolower($item['name'])) { - case "to": - case "cc": - case "bcc": - $to .= ",".$item['value']; - default: - break; - } - } - if ($to == "") { - return $this->raiseError("Message did not contain any recipents"); - } - $to = substr($to,1); - return array($to,$header,$this->_body); - } - - /** - * Returns a xml copy of the output of - * Mail_mimeDecode::decode. Pass the output in as the - * argument. This function can be called statically. Eg: - * - * $output = $obj->decode(); - * $xml = Mail_mimeDecode::getXML($output); - * - * The DTD used for this should have been in the package. Or - * alternatively you can get it from cvs, or here: - * http://www.phpguru.org/xmail/xmail.dtd. - * - * @param object Input to convert to xml. This should be the - * output of the Mail_mimeDecode::decode function - * @return string XML version of input - * @access public - */ - function getXML($input) - { - $crlf = "\r\n"; - $output = '' . $crlf . - '' . $crlf . - '' . $crlf . - Mail_mimeDecode::_getXML($input) . - ''; - - return $output; - } - - /** - * Function that does the actual conversion to xml. Does a single - * mimepart at a time. - * - * @param object Input to convert to xml. This is a mimepart object. - * It may or may not contain subparts. - * @param integer Number of tabs to indent - * @return string XML version of input - * @access private - */ - function _getXML($input, $indent = 1) - { - $htab = "\t"; - $crlf = "\r\n"; - $output = ''; - $headers = @(array)$input->headers; - - foreach ($headers as $hdr_name => $hdr_value) { - - // Multiple headers with this name - if (is_array($headers[$hdr_name])) { - for ($i = 0; $i < count($hdr_value); $i++) { - $output .= Mail_mimeDecode::_getXML_helper($hdr_name, $hdr_value[$i], $indent); - } - - // Only one header of this sort - } else { - $output .= Mail_mimeDecode::_getXML_helper($hdr_name, $hdr_value, $indent); - } - } - - if (!empty($input->parts)) { - for ($i = 0; $i < count($input->parts); $i++) { - $output .= $crlf . str_repeat($htab, $indent) . '' . $crlf . - Mail_mimeDecode::_getXML($input->parts[$i], $indent+1) . - str_repeat($htab, $indent) . '' . $crlf; - } - } elseif (isset($input->body)) { - $output .= $crlf . str_repeat($htab, $indent) . 'body . ']]>' . $crlf; - } - - return $output; - } - - /** - * Helper function to _getXML(). Returns xml of a header. - * - * @param string Name of header - * @param string Value of header - * @param integer Number of tabs to indent - * @return string XML version of input - * @access private - */ - function _getXML_helper($hdr_name, $hdr_value, $indent) - { - $htab = "\t"; - $crlf = "\r\n"; - $return = ''; - - $new_hdr_value = ($hdr_name != 'received') ? Mail_mimeDecode::_parseHeaderValue($hdr_value) : array('value' => $hdr_value); - $new_hdr_name = str_replace(' ', '-', ucwords(str_replace('-', ' ', $hdr_name))); - - // Sort out any parameters - if (!empty($new_hdr_value['other'])) { - foreach ($new_hdr_value['other'] as $paramname => $paramvalue) { - $params[] = str_repeat($htab, $indent) . $htab . '' . $crlf . - str_repeat($htab, $indent) . $htab . $htab . '' . htmlspecialchars($paramname) . '' . $crlf . - str_repeat($htab, $indent) . $htab . $htab . '' . htmlspecialchars($paramvalue) . '' . $crlf . - str_repeat($htab, $indent) . $htab . '' . $crlf; - } - - $params = implode('', $params); - } else { - $params = ''; - } - - $return = str_repeat($htab, $indent) . '
    ' . $crlf . - str_repeat($htab, $indent) . $htab . '' . htmlspecialchars($new_hdr_name) . '' . $crlf . - str_repeat($htab, $indent) . $htab . '' . htmlspecialchars($new_hdr_value['value']) . '' . $crlf . - $params . - str_repeat($htab, $indent) . '
    ' . $crlf; - - return $return; - } - -} // End of class diff --git a/lib/ext/Mail/mimePart.php b/lib/ext/Mail/mimePart.php deleted file mode 100644 index 292227f..0000000 --- a/lib/ext/Mail/mimePart.php +++ /dev/null @@ -1,1228 +0,0 @@ - - * Copyright (c) 2003-2006, PEAR - * 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 - * @author Cipriano Groenendal - * @author Sean Coates - * @author Aleksander Machniak - * @copyright 2003-2006 PEAR - * @license http://www.opensource.org/licenses/bsd-license.php BSD License - * @version 1.8.5 - * @link http://pear.php.net/package/Mail_mime - */ - - -/** - * 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 - * @author Cipriano Groenendal - * @author Sean Coates - * @author Aleksander Machniak - * @copyright 2003-2006 PEAR - * @license http://www.opensource.org/licenses/bsd-license.php BSD License - * @version Release: 1.8.5 - * @link http://pear.php.net/package/Mail_mime - */ -class Mail_mimePart -{ - /** - * The encoding type of this part - * - * @var string - * @access private - */ - var $_encoding; - - /** - * An array of subparts - * - * @var array - * @access private - */ - var $_subparts; - - /** - * The output of this part after being built - * - * @var string - * @access private - */ - var $_encoded; - - /** - * Headers for this part - * - * @var array - * @access private - */ - var $_headers; - - /** - * The body of this part (not encoded) - * - * @var string - * @access private - */ - var $_body; - - /** - * The location of file with body of this part (not encoded) - * - * @var string - * @access private - */ - var $_body_file; - - /** - * The end-of-line sequence - * - * @var string - * @access private - */ - var $_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 : - * body_file - Location of file with part's body (instead of $body) - * - * @access public - */ - function Mail_mimePart($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; - - // 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 An associative array containing two elements, - * body and headers. The headers element is itself - * an indexed array. On error returns PEAR error object. - * @access 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'] = ''; - - for ($i = 0; $i < count($this->_subparts); $i++) { - $encoded['body'] .= '--' . $boundary . $eol; - $tmp = $this->_subparts[$i]->encode(); - if (PEAR::isError($tmp)) { - 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 ($magic_quote_setting = get_magic_quotes_runtime()) { - @ini_set('magic_quotes_runtime', 0); - } - $body = $this->_getEncodedDataFromFile($this->_body_file, $this->_encoding); - if ($magic_quote_setting) { - @ini_set('magic_quotes_runtime', $magic_quote_setting); - } - - if (PEAR::isError($body)) { - 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. File must exist. - * Data will be appended to the file. - * - * @param string $filename Output file location - * @param string $boundary Pre-defined boundary string - * @param boolean $skip_head True if you don't want to save headers - * - * @return array An associative array containing message headers - * or PEAR error object - * @access public - * @since 1.6.0 - */ - function encodeToFile($filename, $boundary=null, $skip_head=false) - { - if (file_exists($filename) && !is_writable($filename)) { - $err = PEAR::raiseError('File is not writeable: ' . $filename); - return $err; - } - - if (!($fh = fopen($filename, 'ab'))) { - $err = PEAR::raiseError('Unable to open file: ' . $filename); - return $err; - } - - // Temporarily reset magic_quotes_runtime for file reads and writes - if ($magic_quote_setting = get_magic_quotes_runtime()) { - @ini_set('magic_quotes_runtime', 0); - } - - $res = $this->_encodePartToFile($fh, $boundary, $skip_head); - - fclose($fh); - - if ($magic_quote_setting) { - @ini_set('magic_quotes_runtime', $magic_quote_setting); - } - - return PEAR::isError($res) ? $res : $this->_headers; - } - - /** - * Encodes given email part into file - * - * @param string $fh Output file handle - * @param string $boundary Pre-defined boundary string - * @param boolean $skip_head True if you don't want to save headers - * - * @return array True on sucess or PEAR error object - * @access private - */ - 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)) { - for ($i = 0; $i < count($this->_subparts); $i++) { - fwrite($fh, $f_eol . '--' . $boundary . $eol); - $res = $this->_subparts[$i]->_encodePartToFile($fh); - if (PEAR::isError($res)) { - return $res; - } - $f_eol = $eol; - } - - fwrite($fh, $eol . '--' . $boundary . '--' . $eol); - - } else if ($this->_body) { - fwrite($fh, $f_eol . $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 (PEAR::isError($res)) { - return $res; - } - } - - return true; - } - - /** - * Adds a subpart to current mime part and returns - * a reference to it - * - * @param string $body The body of the subpart, if any. - * @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. It is - * crucial if using multipart/* in your subparts that - * you use =& in your script when calling this function, - * otherwise you will not be able to add further subparts. - * @access public - */ - function &addSubpart($body, $params) - { - $this->_subparts[] = new Mail_mimePart($body, $params); - return $this->_subparts[count($this->_subparts) - 1]; - } - - /** - * 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 - * @access private - */ - function _getEncodedData($data, $encoding) - { - switch ($encoding) { - case 'quoted-printable': - return $this->_quotedPrintableEncode($data); - 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 - * @access private - */ - function _getEncodedDataFromFile($filename, $encoding, $fh=null) - { - if (!is_readable($filename)) { - $err = PEAR::raiseError('Unable to read file: ' . $filename); - return $err; - } - - if (!($fd = fopen($filename, 'rb'))) { - $err = PEAR::raiseError('Could not open file: ' . $filename); - return $err; - } - - $data = ''; - - switch ($encoding) { - case 'quoted-printable': - while (!feof($fd)) { - $buffer = $this->_quotedPrintableEncode(fgets($fd)); - 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 - * - * @return string Encoded data - * - * @access private - */ - function _quotedPrintableEncode($input , $line_max = 76) - { - $eol = $this->_eol; - /* - // 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 = ''; - - while (list($idx, $line) = each($lines)) { - $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)) { - $char = $escape . sprintf('%02X', $dec); - } elseif (($dec == 46) && (($newline == '') - || ((strlen($newline) + strlen("=2E")) >= $line_max)) - ) { - // 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'; - } - - // Note, when changing this line, also change the ($dec == 46) - // check line, as it mimics this line due to Bug #11731 - // EOL is not counted - if ((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 - * - * @access private - */ - 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 - * @access private - */ - 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}\""; - } - - /** - * 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) - * @access public - * @since 1.6.1 - */ - 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', - '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'; - } - - // Structured header (make sure addr-spec inside is not encoded) - if (!empty($separator)) { - // Simple e-mail address regexp - $email_regexp = '([^\s<]+|("[^\r\n"]+"))@\S+'; - - $parts = Mail_mimePart::_explodeQuotedString($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('#([\x80-\xFF]){1}#', $word)) { - if ($word[0] == '"' && $word[strlen($word)-1] == '"') { - // de-quote quoted-string, encoding changes - // string to atom - $search = array("\\\"", "\\\\"); - $replace = array("\"", "\\"); - $word = str_replace($search, $replace, $word); - $word = substr($word, 1, -1); - } - // 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 { - // 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('#([\x80-\xFF]){1}#', $value)) { - if ($value[0] == '"' && $value[strlen($value)-1] == '"') { - // de-quote quoted-string, encoding changes - // string to atom - $search = array("\\\"", "\\\\"); - $replace = array("\"", "\\"); - $value = str_replace($search, $replace, $value); - $value = substr($value, 1, -1); - } - $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 - * @access private - */ - function _explodeQuotedString($delimiter, $string) - { - $result = array(); - $strlen = strlen($string); - - for ($q=$p=$i=0; $i < $strlen; $i++) { - if ($string[$i] == "\"" - && (empty($string[$i-1]) || $string[$i-1] != "\\") - ) { - $q = $q ? false : true; - } else if (!$q && 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 - * @access public - * @since 1.6.1 - */ - 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 seperated 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 seperated 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 - * @access public - * @since 1.6.0 - */ - 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 - * @access public - * @since 1.8.0 - */ - 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); - - // 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, $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, $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, $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 - * @access private - */ - 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 - * @access private - */ - function _encodeReplaceCallback($matches) - { - return sprintf('%%%02X', ord($matches[1])); - } - -} // End of class diff --git a/lib/ext/Net/IDNA2.php b/lib/ext/Net/IDNA2.php deleted file mode 100644 index 8c366fb..0000000 --- a/lib/ext/Net/IDNA2.php +++ /dev/null @@ -1,3402 +0,0 @@ - - * @author Matthias Sommerfeld - * @author Stefan Neufeind - * @version $Id: IDNA2.php 305344 2010-11-14 23:52:42Z neufeind $ - */ -class Net_IDNA2 -{ - // {{{ npdata - /** - * These Unicode codepoints are - * mapped to nothing, See RFC3454 for details - * - * @static - * @var array - * @access private - */ - private static $_np_map_nothing = array( - 0xAD, - 0x34F, - 0x1806, - 0x180B, - 0x180C, - 0x180D, - 0x200B, - 0x200C, - 0x200D, - 0x2060, - 0xFE00, - 0xFE01, - 0xFE02, - 0xFE03, - 0xFE04, - 0xFE05, - 0xFE06, - 0xFE07, - 0xFE08, - 0xFE09, - 0xFE0A, - 0xFE0B, - 0xFE0C, - 0xFE0D, - 0xFE0E, - 0xFE0F, - 0xFEFF - ); - - /** - * Prohibited codepints - * - * @static - * @var array - * @access private - */ - private static $_general_prohibited = array( - 0, - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 0xA, - 0xB, - 0xC, - 0xD, - 0xE, - 0xF, - 0x10, - 0x11, - 0x12, - 0x13, - 0x14, - 0x15, - 0x16, - 0x17, - 0x18, - 0x19, - 0x1A, - 0x1B, - 0x1C, - 0x1D, - 0x1E, - 0x1F, - 0x20, - 0x21, - 0x22, - 0x23, - 0x24, - 0x25, - 0x26, - 0x27, - 0x28, - 0x29, - 0x2A, - 0x2B, - 0x2C, - 0x2F, - 0x3B, - 0x3C, - 0x3D, - 0x3E, - 0x3F, - 0x40, - 0x5B, - 0x5C, - 0x5D, - 0x5E, - 0x5F, - 0x60, - 0x7B, - 0x7C, - 0x7D, - 0x7E, - 0x7F, - 0x3002 - ); - - /** - * Codepints prohibited by Nameprep - * @static - * @var array - * @access private - */ - private static $_np_prohibit = array( - 0xA0, - 0x1680, - 0x2000, - 0x2001, - 0x2002, - 0x2003, - 0x2004, - 0x2005, - 0x2006, - 0x2007, - 0x2008, - 0x2009, - 0x200A, - 0x200B, - 0x202F, - 0x205F, - 0x3000, - 0x6DD, - 0x70F, - 0x180E, - 0x200C, - 0x200D, - 0x2028, - 0x2029, - 0xFEFF, - 0xFFF9, - 0xFFFA, - 0xFFFB, - 0xFFFC, - 0xFFFE, - 0xFFFF, - 0x1FFFE, - 0x1FFFF, - 0x2FFFE, - 0x2FFFF, - 0x3FFFE, - 0x3FFFF, - 0x4FFFE, - 0x4FFFF, - 0x5FFFE, - 0x5FFFF, - 0x6FFFE, - 0x6FFFF, - 0x7FFFE, - 0x7FFFF, - 0x8FFFE, - 0x8FFFF, - 0x9FFFE, - 0x9FFFF, - 0xAFFFE, - 0xAFFFF, - 0xBFFFE, - 0xBFFFF, - 0xCFFFE, - 0xCFFFF, - 0xDFFFE, - 0xDFFFF, - 0xEFFFE, - 0xEFFFF, - 0xFFFFE, - 0xFFFFF, - 0x10FFFE, - 0x10FFFF, - 0xFFF9, - 0xFFFA, - 0xFFFB, - 0xFFFC, - 0xFFFD, - 0x340, - 0x341, - 0x200E, - 0x200F, - 0x202A, - 0x202B, - 0x202C, - 0x202D, - 0x202E, - 0x206A, - 0x206B, - 0x206C, - 0x206D, - 0x206E, - 0x206F, - 0xE0001 - ); - - /** - * Codepoint ranges prohibited by nameprep - * - * @static - * @var array - * @access private - */ - private static $_np_prohibit_ranges = array( - array(0x80, 0x9F ), - array(0x2060, 0x206F ), - array(0x1D173, 0x1D17A ), - array(0xE000, 0xF8FF ), - array(0xF0000, 0xFFFFD ), - array(0x100000, 0x10FFFD), - array(0xFDD0, 0xFDEF ), - array(0xD800, 0xDFFF ), - array(0x2FF0, 0x2FFB ), - array(0xE0020, 0xE007F ) - ); - - /** - * Replacement mappings (casemapping, replacement sequences, ...) - * - * @static - * @var array - * @access private - */ - private static $_np_replacemaps = array( - 0x41 => array(0x61), - 0x42 => array(0x62), - 0x43 => array(0x63), - 0x44 => array(0x64), - 0x45 => array(0x65), - 0x46 => array(0x66), - 0x47 => array(0x67), - 0x48 => array(0x68), - 0x49 => array(0x69), - 0x4A => array(0x6A), - 0x4B => array(0x6B), - 0x4C => array(0x6C), - 0x4D => array(0x6D), - 0x4E => array(0x6E), - 0x4F => array(0x6F), - 0x50 => array(0x70), - 0x51 => array(0x71), - 0x52 => array(0x72), - 0x53 => array(0x73), - 0x54 => array(0x74), - 0x55 => array(0x75), - 0x56 => array(0x76), - 0x57 => array(0x77), - 0x58 => array(0x78), - 0x59 => array(0x79), - 0x5A => array(0x7A), - 0xB5 => array(0x3BC), - 0xC0 => array(0xE0), - 0xC1 => array(0xE1), - 0xC2 => array(0xE2), - 0xC3 => array(0xE3), - 0xC4 => array(0xE4), - 0xC5 => array(0xE5), - 0xC6 => array(0xE6), - 0xC7 => array(0xE7), - 0xC8 => array(0xE8), - 0xC9 => array(0xE9), - 0xCA => array(0xEA), - 0xCB => array(0xEB), - 0xCC => array(0xEC), - 0xCD => array(0xED), - 0xCE => array(0xEE), - 0xCF => array(0xEF), - 0xD0 => array(0xF0), - 0xD1 => array(0xF1), - 0xD2 => array(0xF2), - 0xD3 => array(0xF3), - 0xD4 => array(0xF4), - 0xD5 => array(0xF5), - 0xD6 => array(0xF6), - 0xD8 => array(0xF8), - 0xD9 => array(0xF9), - 0xDA => array(0xFA), - 0xDB => array(0xFB), - 0xDC => array(0xFC), - 0xDD => array(0xFD), - 0xDE => array(0xFE), - 0xDF => array(0x73, 0x73), - 0x100 => array(0x101), - 0x102 => array(0x103), - 0x104 => array(0x105), - 0x106 => array(0x107), - 0x108 => array(0x109), - 0x10A => array(0x10B), - 0x10C => array(0x10D), - 0x10E => array(0x10F), - 0x110 => array(0x111), - 0x112 => array(0x113), - 0x114 => array(0x115), - 0x116 => array(0x117), - 0x118 => array(0x119), - 0x11A => array(0x11B), - 0x11C => array(0x11D), - 0x11E => array(0x11F), - 0x120 => array(0x121), - 0x122 => array(0x123), - 0x124 => array(0x125), - 0x126 => array(0x127), - 0x128 => array(0x129), - 0x12A => array(0x12B), - 0x12C => array(0x12D), - 0x12E => array(0x12F), - 0x130 => array(0x69, 0x307), - 0x132 => array(0x133), - 0x134 => array(0x135), - 0x136 => array(0x137), - 0x139 => array(0x13A), - 0x13B => array(0x13C), - 0x13D => array(0x13E), - 0x13F => array(0x140), - 0x141 => array(0x142), - 0x143 => array(0x144), - 0x145 => array(0x146), - 0x147 => array(0x148), - 0x149 => array(0x2BC, 0x6E), - 0x14A => array(0x14B), - 0x14C => array(0x14D), - 0x14E => array(0x14F), - 0x150 => array(0x151), - 0x152 => array(0x153), - 0x154 => array(0x155), - 0x156 => array(0x157), - 0x158 => array(0x159), - 0x15A => array(0x15B), - 0x15C => array(0x15D), - 0x15E => array(0x15F), - 0x160 => array(0x161), - 0x162 => array(0x163), - 0x164 => array(0x165), - 0x166 => array(0x167), - 0x168 => array(0x169), - 0x16A => array(0x16B), - 0x16C => array(0x16D), - 0x16E => array(0x16F), - 0x170 => array(0x171), - 0x172 => array(0x173), - 0x174 => array(0x175), - 0x176 => array(0x177), - 0x178 => array(0xFF), - 0x179 => array(0x17A), - 0x17B => array(0x17C), - 0x17D => array(0x17E), - 0x17F => array(0x73), - 0x181 => array(0x253), - 0x182 => array(0x183), - 0x184 => array(0x185), - 0x186 => array(0x254), - 0x187 => array(0x188), - 0x189 => array(0x256), - 0x18A => array(0x257), - 0x18B => array(0x18C), - 0x18E => array(0x1DD), - 0x18F => array(0x259), - 0x190 => array(0x25B), - 0x191 => array(0x192), - 0x193 => array(0x260), - 0x194 => array(0x263), - 0x196 => array(0x269), - 0x197 => array(0x268), - 0x198 => array(0x199), - 0x19C => array(0x26F), - 0x19D => array(0x272), - 0x19F => array(0x275), - 0x1A0 => array(0x1A1), - 0x1A2 => array(0x1A3), - 0x1A4 => array(0x1A5), - 0x1A6 => array(0x280), - 0x1A7 => array(0x1A8), - 0x1A9 => array(0x283), - 0x1AC => array(0x1AD), - 0x1AE => array(0x288), - 0x1AF => array(0x1B0), - 0x1B1 => array(0x28A), - 0x1B2 => array(0x28B), - 0x1B3 => array(0x1B4), - 0x1B5 => array(0x1B6), - 0x1B7 => array(0x292), - 0x1B8 => array(0x1B9), - 0x1BC => array(0x1BD), - 0x1C4 => array(0x1C6), - 0x1C5 => array(0x1C6), - 0x1C7 => array(0x1C9), - 0x1C8 => array(0x1C9), - 0x1CA => array(0x1CC), - 0x1CB => array(0x1CC), - 0x1CD => array(0x1CE), - 0x1CF => array(0x1D0), - 0x1D1 => array(0x1D2), - 0x1D3 => array(0x1D4), - 0x1D5 => array(0x1D6), - 0x1D7 => array(0x1D8), - 0x1D9 => array(0x1DA), - 0x1DB => array(0x1DC), - 0x1DE => array(0x1DF), - 0x1E0 => array(0x1E1), - 0x1E2 => array(0x1E3), - 0x1E4 => array(0x1E5), - 0x1E6 => array(0x1E7), - 0x1E8 => array(0x1E9), - 0x1EA => array(0x1EB), - 0x1EC => array(0x1ED), - 0x1EE => array(0x1EF), - 0x1F0 => array(0x6A, 0x30C), - 0x1F1 => array(0x1F3), - 0x1F2 => array(0x1F3), - 0x1F4 => array(0x1F5), - 0x1F6 => array(0x195), - 0x1F7 => array(0x1BF), - 0x1F8 => array(0x1F9), - 0x1FA => array(0x1FB), - 0x1FC => array(0x1FD), - 0x1FE => array(0x1FF), - 0x200 => array(0x201), - 0x202 => array(0x203), - 0x204 => array(0x205), - 0x206 => array(0x207), - 0x208 => array(0x209), - 0x20A => array(0x20B), - 0x20C => array(0x20D), - 0x20E => array(0x20F), - 0x210 => array(0x211), - 0x212 => array(0x213), - 0x214 => array(0x215), - 0x216 => array(0x217), - 0x218 => array(0x219), - 0x21A => array(0x21B), - 0x21C => array(0x21D), - 0x21E => array(0x21F), - 0x220 => array(0x19E), - 0x222 => array(0x223), - 0x224 => array(0x225), - 0x226 => array(0x227), - 0x228 => array(0x229), - 0x22A => array(0x22B), - 0x22C => array(0x22D), - 0x22E => array(0x22F), - 0x230 => array(0x231), - 0x232 => array(0x233), - 0x345 => array(0x3B9), - 0x37A => array(0x20, 0x3B9), - 0x386 => array(0x3AC), - 0x388 => array(0x3AD), - 0x389 => array(0x3AE), - 0x38A => array(0x3AF), - 0x38C => array(0x3CC), - 0x38E => array(0x3CD), - 0x38F => array(0x3CE), - 0x390 => array(0x3B9, 0x308, 0x301), - 0x391 => array(0x3B1), - 0x392 => array(0x3B2), - 0x393 => array(0x3B3), - 0x394 => array(0x3B4), - 0x395 => array(0x3B5), - 0x396 => array(0x3B6), - 0x397 => array(0x3B7), - 0x398 => array(0x3B8), - 0x399 => array(0x3B9), - 0x39A => array(0x3BA), - 0x39B => array(0x3BB), - 0x39C => array(0x3BC), - 0x39D => array(0x3BD), - 0x39E => array(0x3BE), - 0x39F => array(0x3BF), - 0x3A0 => array(0x3C0), - 0x3A1 => array(0x3C1), - 0x3A3 => array(0x3C3), - 0x3A4 => array(0x3C4), - 0x3A5 => array(0x3C5), - 0x3A6 => array(0x3C6), - 0x3A7 => array(0x3C7), - 0x3A8 => array(0x3C8), - 0x3A9 => array(0x3C9), - 0x3AA => array(0x3CA), - 0x3AB => array(0x3CB), - 0x3B0 => array(0x3C5, 0x308, 0x301), - 0x3C2 => array(0x3C3), - 0x3D0 => array(0x3B2), - 0x3D1 => array(0x3B8), - 0x3D2 => array(0x3C5), - 0x3D3 => array(0x3CD), - 0x3D4 => array(0x3CB), - 0x3D5 => array(0x3C6), - 0x3D6 => array(0x3C0), - 0x3D8 => array(0x3D9), - 0x3DA => array(0x3DB), - 0x3DC => array(0x3DD), - 0x3DE => array(0x3DF), - 0x3E0 => array(0x3E1), - 0x3E2 => array(0x3E3), - 0x3E4 => array(0x3E5), - 0x3E6 => array(0x3E7), - 0x3E8 => array(0x3E9), - 0x3EA => array(0x3EB), - 0x3EC => array(0x3ED), - 0x3EE => array(0x3EF), - 0x3F0 => array(0x3BA), - 0x3F1 => array(0x3C1), - 0x3F2 => array(0x3C3), - 0x3F4 => array(0x3B8), - 0x3F5 => array(0x3B5), - 0x400 => array(0x450), - 0x401 => array(0x451), - 0x402 => array(0x452), - 0x403 => array(0x453), - 0x404 => array(0x454), - 0x405 => array(0x455), - 0x406 => array(0x456), - 0x407 => array(0x457), - 0x408 => array(0x458), - 0x409 => array(0x459), - 0x40A => array(0x45A), - 0x40B => array(0x45B), - 0x40C => array(0x45C), - 0x40D => array(0x45D), - 0x40E => array(0x45E), - 0x40F => array(0x45F), - 0x410 => array(0x430), - 0x411 => array(0x431), - 0x412 => array(0x432), - 0x413 => array(0x433), - 0x414 => array(0x434), - 0x415 => array(0x435), - 0x416 => array(0x436), - 0x417 => array(0x437), - 0x418 => array(0x438), - 0x419 => array(0x439), - 0x41A => array(0x43A), - 0x41B => array(0x43B), - 0x41C => array(0x43C), - 0x41D => array(0x43D), - 0x41E => array(0x43E), - 0x41F => array(0x43F), - 0x420 => array(0x440), - 0x421 => array(0x441), - 0x422 => array(0x442), - 0x423 => array(0x443), - 0x424 => array(0x444), - 0x425 => array(0x445), - 0x426 => array(0x446), - 0x427 => array(0x447), - 0x428 => array(0x448), - 0x429 => array(0x449), - 0x42A => array(0x44A), - 0x42B => array(0x44B), - 0x42C => array(0x44C), - 0x42D => array(0x44D), - 0x42E => array(0x44E), - 0x42F => array(0x44F), - 0x460 => array(0x461), - 0x462 => array(0x463), - 0x464 => array(0x465), - 0x466 => array(0x467), - 0x468 => array(0x469), - 0x46A => array(0x46B), - 0x46C => array(0x46D), - 0x46E => array(0x46F), - 0x470 => array(0x471), - 0x472 => array(0x473), - 0x474 => array(0x475), - 0x476 => array(0x477), - 0x478 => array(0x479), - 0x47A => array(0x47B), - 0x47C => array(0x47D), - 0x47E => array(0x47F), - 0x480 => array(0x481), - 0x48A => array(0x48B), - 0x48C => array(0x48D), - 0x48E => array(0x48F), - 0x490 => array(0x491), - 0x492 => array(0x493), - 0x494 => array(0x495), - 0x496 => array(0x497), - 0x498 => array(0x499), - 0x49A => array(0x49B), - 0x49C => array(0x49D), - 0x49E => array(0x49F), - 0x4A0 => array(0x4A1), - 0x4A2 => array(0x4A3), - 0x4A4 => array(0x4A5), - 0x4A6 => array(0x4A7), - 0x4A8 => array(0x4A9), - 0x4AA => array(0x4AB), - 0x4AC => array(0x4AD), - 0x4AE => array(0x4AF), - 0x4B0 => array(0x4B1), - 0x4B2 => array(0x4B3), - 0x4B4 => array(0x4B5), - 0x4B6 => array(0x4B7), - 0x4B8 => array(0x4B9), - 0x4BA => array(0x4BB), - 0x4BC => array(0x4BD), - 0x4BE => array(0x4BF), - 0x4C1 => array(0x4C2), - 0x4C3 => array(0x4C4), - 0x4C5 => array(0x4C6), - 0x4C7 => array(0x4C8), - 0x4C9 => array(0x4CA), - 0x4CB => array(0x4CC), - 0x4CD => array(0x4CE), - 0x4D0 => array(0x4D1), - 0x4D2 => array(0x4D3), - 0x4D4 => array(0x4D5), - 0x4D6 => array(0x4D7), - 0x4D8 => array(0x4D9), - 0x4DA => array(0x4DB), - 0x4DC => array(0x4DD), - 0x4DE => array(0x4DF), - 0x4E0 => array(0x4E1), - 0x4E2 => array(0x4E3), - 0x4E4 => array(0x4E5), - 0x4E6 => array(0x4E7), - 0x4E8 => array(0x4E9), - 0x4EA => array(0x4EB), - 0x4EC => array(0x4ED), - 0x4EE => array(0x4EF), - 0x4F0 => array(0x4F1), - 0x4F2 => array(0x4F3), - 0x4F4 => array(0x4F5), - 0x4F8 => array(0x4F9), - 0x500 => array(0x501), - 0x502 => array(0x503), - 0x504 => array(0x505), - 0x506 => array(0x507), - 0x508 => array(0x509), - 0x50A => array(0x50B), - 0x50C => array(0x50D), - 0x50E => array(0x50F), - 0x531 => array(0x561), - 0x532 => array(0x562), - 0x533 => array(0x563), - 0x534 => array(0x564), - 0x535 => array(0x565), - 0x536 => array(0x566), - 0x537 => array(0x567), - 0x538 => array(0x568), - 0x539 => array(0x569), - 0x53A => array(0x56A), - 0x53B => array(0x56B), - 0x53C => array(0x56C), - 0x53D => array(0x56D), - 0x53E => array(0x56E), - 0x53F => array(0x56F), - 0x540 => array(0x570), - 0x541 => array(0x571), - 0x542 => array(0x572), - 0x543 => array(0x573), - 0x544 => array(0x574), - 0x545 => array(0x575), - 0x546 => array(0x576), - 0x547 => array(0x577), - 0x548 => array(0x578), - 0x549 => array(0x579), - 0x54A => array(0x57A), - 0x54B => array(0x57B), - 0x54C => array(0x57C), - 0x54D => array(0x57D), - 0x54E => array(0x57E), - 0x54F => array(0x57F), - 0x550 => array(0x580), - 0x551 => array(0x581), - 0x552 => array(0x582), - 0x553 => array(0x583), - 0x554 => array(0x584), - 0x555 => array(0x585), - 0x556 => array(0x586), - 0x587 => array(0x565, 0x582), - 0x1E00 => array(0x1E01), - 0x1E02 => array(0x1E03), - 0x1E04 => array(0x1E05), - 0x1E06 => array(0x1E07), - 0x1E08 => array(0x1E09), - 0x1E0A => array(0x1E0B), - 0x1E0C => array(0x1E0D), - 0x1E0E => array(0x1E0F), - 0x1E10 => array(0x1E11), - 0x1E12 => array(0x1E13), - 0x1E14 => array(0x1E15), - 0x1E16 => array(0x1E17), - 0x1E18 => array(0x1E19), - 0x1E1A => array(0x1E1B), - 0x1E1C => array(0x1E1D), - 0x1E1E => array(0x1E1F), - 0x1E20 => array(0x1E21), - 0x1E22 => array(0x1E23), - 0x1E24 => array(0x1E25), - 0x1E26 => array(0x1E27), - 0x1E28 => array(0x1E29), - 0x1E2A => array(0x1E2B), - 0x1E2C => array(0x1E2D), - 0x1E2E => array(0x1E2F), - 0x1E30 => array(0x1E31), - 0x1E32 => array(0x1E33), - 0x1E34 => array(0x1E35), - 0x1E36 => array(0x1E37), - 0x1E38 => array(0x1E39), - 0x1E3A => array(0x1E3B), - 0x1E3C => array(0x1E3D), - 0x1E3E => array(0x1E3F), - 0x1E40 => array(0x1E41), - 0x1E42 => array(0x1E43), - 0x1E44 => array(0x1E45), - 0x1E46 => array(0x1E47), - 0x1E48 => array(0x1E49), - 0x1E4A => array(0x1E4B), - 0x1E4C => array(0x1E4D), - 0x1E4E => array(0x1E4F), - 0x1E50 => array(0x1E51), - 0x1E52 => array(0x1E53), - 0x1E54 => array(0x1E55), - 0x1E56 => array(0x1E57), - 0x1E58 => array(0x1E59), - 0x1E5A => array(0x1E5B), - 0x1E5C => array(0x1E5D), - 0x1E5E => array(0x1E5F), - 0x1E60 => array(0x1E61), - 0x1E62 => array(0x1E63), - 0x1E64 => array(0x1E65), - 0x1E66 => array(0x1E67), - 0x1E68 => array(0x1E69), - 0x1E6A => array(0x1E6B), - 0x1E6C => array(0x1E6D), - 0x1E6E => array(0x1E6F), - 0x1E70 => array(0x1E71), - 0x1E72 => array(0x1E73), - 0x1E74 => array(0x1E75), - 0x1E76 => array(0x1E77), - 0x1E78 => array(0x1E79), - 0x1E7A => array(0x1E7B), - 0x1E7C => array(0x1E7D), - 0x1E7E => array(0x1E7F), - 0x1E80 => array(0x1E81), - 0x1E82 => array(0x1E83), - 0x1E84 => array(0x1E85), - 0x1E86 => array(0x1E87), - 0x1E88 => array(0x1E89), - 0x1E8A => array(0x1E8B), - 0x1E8C => array(0x1E8D), - 0x1E8E => array(0x1E8F), - 0x1E90 => array(0x1E91), - 0x1E92 => array(0x1E93), - 0x1E94 => array(0x1E95), - 0x1E96 => array(0x68, 0x331), - 0x1E97 => array(0x74, 0x308), - 0x1E98 => array(0x77, 0x30A), - 0x1E99 => array(0x79, 0x30A), - 0x1E9A => array(0x61, 0x2BE), - 0x1E9B => array(0x1E61), - 0x1EA0 => array(0x1EA1), - 0x1EA2 => array(0x1EA3), - 0x1EA4 => array(0x1EA5), - 0x1EA6 => array(0x1EA7), - 0x1EA8 => array(0x1EA9), - 0x1EAA => array(0x1EAB), - 0x1EAC => array(0x1EAD), - 0x1EAE => array(0x1EAF), - 0x1EB0 => array(0x1EB1), - 0x1EB2 => array(0x1EB3), - 0x1EB4 => array(0x1EB5), - 0x1EB6 => array(0x1EB7), - 0x1EB8 => array(0x1EB9), - 0x1EBA => array(0x1EBB), - 0x1EBC => array(0x1EBD), - 0x1EBE => array(0x1EBF), - 0x1EC0 => array(0x1EC1), - 0x1EC2 => array(0x1EC3), - 0x1EC4 => array(0x1EC5), - 0x1EC6 => array(0x1EC7), - 0x1EC8 => array(0x1EC9), - 0x1ECA => array(0x1ECB), - 0x1ECC => array(0x1ECD), - 0x1ECE => array(0x1ECF), - 0x1ED0 => array(0x1ED1), - 0x1ED2 => array(0x1ED3), - 0x1ED4 => array(0x1ED5), - 0x1ED6 => array(0x1ED7), - 0x1ED8 => array(0x1ED9), - 0x1EDA => array(0x1EDB), - 0x1EDC => array(0x1EDD), - 0x1EDE => array(0x1EDF), - 0x1EE0 => array(0x1EE1), - 0x1EE2 => array(0x1EE3), - 0x1EE4 => array(0x1EE5), - 0x1EE6 => array(0x1EE7), - 0x1EE8 => array(0x1EE9), - 0x1EEA => array(0x1EEB), - 0x1EEC => array(0x1EED), - 0x1EEE => array(0x1EEF), - 0x1EF0 => array(0x1EF1), - 0x1EF2 => array(0x1EF3), - 0x1EF4 => array(0x1EF5), - 0x1EF6 => array(0x1EF7), - 0x1EF8 => array(0x1EF9), - 0x1F08 => array(0x1F00), - 0x1F09 => array(0x1F01), - 0x1F0A => array(0x1F02), - 0x1F0B => array(0x1F03), - 0x1F0C => array(0x1F04), - 0x1F0D => array(0x1F05), - 0x1F0E => array(0x1F06), - 0x1F0F => array(0x1F07), - 0x1F18 => array(0x1F10), - 0x1F19 => array(0x1F11), - 0x1F1A => array(0x1F12), - 0x1F1B => array(0x1F13), - 0x1F1C => array(0x1F14), - 0x1F1D => array(0x1F15), - 0x1F28 => array(0x1F20), - 0x1F29 => array(0x1F21), - 0x1F2A => array(0x1F22), - 0x1F2B => array(0x1F23), - 0x1F2C => array(0x1F24), - 0x1F2D => array(0x1F25), - 0x1F2E => array(0x1F26), - 0x1F2F => array(0x1F27), - 0x1F38 => array(0x1F30), - 0x1F39 => array(0x1F31), - 0x1F3A => array(0x1F32), - 0x1F3B => array(0x1F33), - 0x1F3C => array(0x1F34), - 0x1F3D => array(0x1F35), - 0x1F3E => array(0x1F36), - 0x1F3F => array(0x1F37), - 0x1F48 => array(0x1F40), - 0x1F49 => array(0x1F41), - 0x1F4A => array(0x1F42), - 0x1F4B => array(0x1F43), - 0x1F4C => array(0x1F44), - 0x1F4D => array(0x1F45), - 0x1F50 => array(0x3C5, 0x313), - 0x1F52 => array(0x3C5, 0x313, 0x300), - 0x1F54 => array(0x3C5, 0x313, 0x301), - 0x1F56 => array(0x3C5, 0x313, 0x342), - 0x1F59 => array(0x1F51), - 0x1F5B => array(0x1F53), - 0x1F5D => array(0x1F55), - 0x1F5F => array(0x1F57), - 0x1F68 => array(0x1F60), - 0x1F69 => array(0x1F61), - 0x1F6A => array(0x1F62), - 0x1F6B => array(0x1F63), - 0x1F6C => array(0x1F64), - 0x1F6D => array(0x1F65), - 0x1F6E => array(0x1F66), - 0x1F6F => array(0x1F67), - 0x1F80 => array(0x1F00, 0x3B9), - 0x1F81 => array(0x1F01, 0x3B9), - 0x1F82 => array(0x1F02, 0x3B9), - 0x1F83 => array(0x1F03, 0x3B9), - 0x1F84 => array(0x1F04, 0x3B9), - 0x1F85 => array(0x1F05, 0x3B9), - 0x1F86 => array(0x1F06, 0x3B9), - 0x1F87 => array(0x1F07, 0x3B9), - 0x1F88 => array(0x1F00, 0x3B9), - 0x1F89 => array(0x1F01, 0x3B9), - 0x1F8A => array(0x1F02, 0x3B9), - 0x1F8B => array(0x1F03, 0x3B9), - 0x1F8C => array(0x1F04, 0x3B9), - 0x1F8D => array(0x1F05, 0x3B9), - 0x1F8E => array(0x1F06, 0x3B9), - 0x1F8F => array(0x1F07, 0x3B9), - 0x1F90 => array(0x1F20, 0x3B9), - 0x1F91 => array(0x1F21, 0x3B9), - 0x1F92 => array(0x1F22, 0x3B9), - 0x1F93 => array(0x1F23, 0x3B9), - 0x1F94 => array(0x1F24, 0x3B9), - 0x1F95 => array(0x1F25, 0x3B9), - 0x1F96 => array(0x1F26, 0x3B9), - 0x1F97 => array(0x1F27, 0x3B9), - 0x1F98 => array(0x1F20, 0x3B9), - 0x1F99 => array(0x1F21, 0x3B9), - 0x1F9A => array(0x1F22, 0x3B9), - 0x1F9B => array(0x1F23, 0x3B9), - 0x1F9C => array(0x1F24, 0x3B9), - 0x1F9D => array(0x1F25, 0x3B9), - 0x1F9E => array(0x1F26, 0x3B9), - 0x1F9F => array(0x1F27, 0x3B9), - 0x1FA0 => array(0x1F60, 0x3B9), - 0x1FA1 => array(0x1F61, 0x3B9), - 0x1FA2 => array(0x1F62, 0x3B9), - 0x1FA3 => array(0x1F63, 0x3B9), - 0x1FA4 => array(0x1F64, 0x3B9), - 0x1FA5 => array(0x1F65, 0x3B9), - 0x1FA6 => array(0x1F66, 0x3B9), - 0x1FA7 => array(0x1F67, 0x3B9), - 0x1FA8 => array(0x1F60, 0x3B9), - 0x1FA9 => array(0x1F61, 0x3B9), - 0x1FAA => array(0x1F62, 0x3B9), - 0x1FAB => array(0x1F63, 0x3B9), - 0x1FAC => array(0x1F64, 0x3B9), - 0x1FAD => array(0x1F65, 0x3B9), - 0x1FAE => array(0x1F66, 0x3B9), - 0x1FAF => array(0x1F67, 0x3B9), - 0x1FB2 => array(0x1F70, 0x3B9), - 0x1FB3 => array(0x3B1, 0x3B9), - 0x1FB4 => array(0x3AC, 0x3B9), - 0x1FB6 => array(0x3B1, 0x342), - 0x1FB7 => array(0x3B1, 0x342, 0x3B9), - 0x1FB8 => array(0x1FB0), - 0x1FB9 => array(0x1FB1), - 0x1FBA => array(0x1F70), - 0x1FBB => array(0x1F71), - 0x1FBC => array(0x3B1, 0x3B9), - 0x1FBE => array(0x3B9), - 0x1FC2 => array(0x1F74, 0x3B9), - 0x1FC3 => array(0x3B7, 0x3B9), - 0x1FC4 => array(0x3AE, 0x3B9), - 0x1FC6 => array(0x3B7, 0x342), - 0x1FC7 => array(0x3B7, 0x342, 0x3B9), - 0x1FC8 => array(0x1F72), - 0x1FC9 => array(0x1F73), - 0x1FCA => array(0x1F74), - 0x1FCB => array(0x1F75), - 0x1FCC => array(0x3B7, 0x3B9), - 0x1FD2 => array(0x3B9, 0x308, 0x300), - 0x1FD3 => array(0x3B9, 0x308, 0x301), - 0x1FD6 => array(0x3B9, 0x342), - 0x1FD7 => array(0x3B9, 0x308, 0x342), - 0x1FD8 => array(0x1FD0), - 0x1FD9 => array(0x1FD1), - 0x1FDA => array(0x1F76), - 0x1FDB => array(0x1F77), - 0x1FE2 => array(0x3C5, 0x308, 0x300), - 0x1FE3 => array(0x3C5, 0x308, 0x301), - 0x1FE4 => array(0x3C1, 0x313), - 0x1FE6 => array(0x3C5, 0x342), - 0x1FE7 => array(0x3C5, 0x308, 0x342), - 0x1FE8 => array(0x1FE0), - 0x1FE9 => array(0x1FE1), - 0x1FEA => array(0x1F7A), - 0x1FEB => array(0x1F7B), - 0x1FEC => array(0x1FE5), - 0x1FF2 => array(0x1F7C, 0x3B9), - 0x1FF3 => array(0x3C9, 0x3B9), - 0x1FF4 => array(0x3CE, 0x3B9), - 0x1FF6 => array(0x3C9, 0x342), - 0x1FF7 => array(0x3C9, 0x342, 0x3B9), - 0x1FF8 => array(0x1F78), - 0x1FF9 => array(0x1F79), - 0x1FFA => array(0x1F7C), - 0x1FFB => array(0x1F7D), - 0x1FFC => array(0x3C9, 0x3B9), - 0x20A8 => array(0x72, 0x73), - 0x2102 => array(0x63), - 0x2103 => array(0xB0, 0x63), - 0x2107 => array(0x25B), - 0x2109 => array(0xB0, 0x66), - 0x210B => array(0x68), - 0x210C => array(0x68), - 0x210D => array(0x68), - 0x2110 => array(0x69), - 0x2111 => array(0x69), - 0x2112 => array(0x6C), - 0x2115 => array(0x6E), - 0x2116 => array(0x6E, 0x6F), - 0x2119 => array(0x70), - 0x211A => array(0x71), - 0x211B => array(0x72), - 0x211C => array(0x72), - 0x211D => array(0x72), - 0x2120 => array(0x73, 0x6D), - 0x2121 => array(0x74, 0x65, 0x6C), - 0x2122 => array(0x74, 0x6D), - 0x2124 => array(0x7A), - 0x2126 => array(0x3C9), - 0x2128 => array(0x7A), - 0x212A => array(0x6B), - 0x212B => array(0xE5), - 0x212C => array(0x62), - 0x212D => array(0x63), - 0x2130 => array(0x65), - 0x2131 => array(0x66), - 0x2133 => array(0x6D), - 0x213E => array(0x3B3), - 0x213F => array(0x3C0), - 0x2145 => array(0x64), - 0x2160 => array(0x2170), - 0x2161 => array(0x2171), - 0x2162 => array(0x2172), - 0x2163 => array(0x2173), - 0x2164 => array(0x2174), - 0x2165 => array(0x2175), - 0x2166 => array(0x2176), - 0x2167 => array(0x2177), - 0x2168 => array(0x2178), - 0x2169 => array(0x2179), - 0x216A => array(0x217A), - 0x216B => array(0x217B), - 0x216C => array(0x217C), - 0x216D => array(0x217D), - 0x216E => array(0x217E), - 0x216F => array(0x217F), - 0x24B6 => array(0x24D0), - 0x24B7 => array(0x24D1), - 0x24B8 => array(0x24D2), - 0x24B9 => array(0x24D3), - 0x24BA => array(0x24D4), - 0x24BB => array(0x24D5), - 0x24BC => array(0x24D6), - 0x24BD => array(0x24D7), - 0x24BE => array(0x24D8), - 0x24BF => array(0x24D9), - 0x24C0 => array(0x24DA), - 0x24C1 => array(0x24DB), - 0x24C2 => array(0x24DC), - 0x24C3 => array(0x24DD), - 0x24C4 => array(0x24DE), - 0x24C5 => array(0x24DF), - 0x24C6 => array(0x24E0), - 0x24C7 => array(0x24E1), - 0x24C8 => array(0x24E2), - 0x24C9 => array(0x24E3), - 0x24CA => array(0x24E4), - 0x24CB => array(0x24E5), - 0x24CC => array(0x24E6), - 0x24CD => array(0x24E7), - 0x24CE => array(0x24E8), - 0x24CF => array(0x24E9), - 0x3371 => array(0x68, 0x70, 0x61), - 0x3373 => array(0x61, 0x75), - 0x3375 => array(0x6F, 0x76), - 0x3380 => array(0x70, 0x61), - 0x3381 => array(0x6E, 0x61), - 0x3382 => array(0x3BC, 0x61), - 0x3383 => array(0x6D, 0x61), - 0x3384 => array(0x6B, 0x61), - 0x3385 => array(0x6B, 0x62), - 0x3386 => array(0x6D, 0x62), - 0x3387 => array(0x67, 0x62), - 0x338A => array(0x70, 0x66), - 0x338B => array(0x6E, 0x66), - 0x338C => array(0x3BC, 0x66), - 0x3390 => array(0x68, 0x7A), - 0x3391 => array(0x6B, 0x68, 0x7A), - 0x3392 => array(0x6D, 0x68, 0x7A), - 0x3393 => array(0x67, 0x68, 0x7A), - 0x3394 => array(0x74, 0x68, 0x7A), - 0x33A9 => array(0x70, 0x61), - 0x33AA => array(0x6B, 0x70, 0x61), - 0x33AB => array(0x6D, 0x70, 0x61), - 0x33AC => array(0x67, 0x70, 0x61), - 0x33B4 => array(0x70, 0x76), - 0x33B5 => array(0x6E, 0x76), - 0x33B6 => array(0x3BC, 0x76), - 0x33B7 => array(0x6D, 0x76), - 0x33B8 => array(0x6B, 0x76), - 0x33B9 => array(0x6D, 0x76), - 0x33BA => array(0x70, 0x77), - 0x33BB => array(0x6E, 0x77), - 0x33BC => array(0x3BC, 0x77), - 0x33BD => array(0x6D, 0x77), - 0x33BE => array(0x6B, 0x77), - 0x33BF => array(0x6D, 0x77), - 0x33C0 => array(0x6B, 0x3C9), - 0x33C1 => array(0x6D, 0x3C9), - /* 0x33C2 => array(0x61, 0x2E, 0x6D, 0x2E), */ - 0x33C3 => array(0x62, 0x71), - 0x33C6 => array(0x63, 0x2215, 0x6B, 0x67), - 0x33C7 => array(0x63, 0x6F, 0x2E), - 0x33C8 => array(0x64, 0x62), - 0x33C9 => array(0x67, 0x79), - 0x33CB => array(0x68, 0x70), - 0x33CD => array(0x6B, 0x6B), - 0x33CE => array(0x6B, 0x6D), - 0x33D7 => array(0x70, 0x68), - 0x33D9 => array(0x70, 0x70, 0x6D), - 0x33DA => array(0x70, 0x72), - 0x33DC => array(0x73, 0x76), - 0x33DD => array(0x77, 0x62), - 0xFB00 => array(0x66, 0x66), - 0xFB01 => array(0x66, 0x69), - 0xFB02 => array(0x66, 0x6C), - 0xFB03 => array(0x66, 0x66, 0x69), - 0xFB04 => array(0x66, 0x66, 0x6C), - 0xFB05 => array(0x73, 0x74), - 0xFB06 => array(0x73, 0x74), - 0xFB13 => array(0x574, 0x576), - 0xFB14 => array(0x574, 0x565), - 0xFB15 => array(0x574, 0x56B), - 0xFB16 => array(0x57E, 0x576), - 0xFB17 => array(0x574, 0x56D), - 0xFF21 => array(0xFF41), - 0xFF22 => array(0xFF42), - 0xFF23 => array(0xFF43), - 0xFF24 => array(0xFF44), - 0xFF25 => array(0xFF45), - 0xFF26 => array(0xFF46), - 0xFF27 => array(0xFF47), - 0xFF28 => array(0xFF48), - 0xFF29 => array(0xFF49), - 0xFF2A => array(0xFF4A), - 0xFF2B => array(0xFF4B), - 0xFF2C => array(0xFF4C), - 0xFF2D => array(0xFF4D), - 0xFF2E => array(0xFF4E), - 0xFF2F => array(0xFF4F), - 0xFF30 => array(0xFF50), - 0xFF31 => array(0xFF51), - 0xFF32 => array(0xFF52), - 0xFF33 => array(0xFF53), - 0xFF34 => array(0xFF54), - 0xFF35 => array(0xFF55), - 0xFF36 => array(0xFF56), - 0xFF37 => array(0xFF57), - 0xFF38 => array(0xFF58), - 0xFF39 => array(0xFF59), - 0xFF3A => array(0xFF5A), - 0x10400 => array(0x10428), - 0x10401 => array(0x10429), - 0x10402 => array(0x1042A), - 0x10403 => array(0x1042B), - 0x10404 => array(0x1042C), - 0x10405 => array(0x1042D), - 0x10406 => array(0x1042E), - 0x10407 => array(0x1042F), - 0x10408 => array(0x10430), - 0x10409 => array(0x10431), - 0x1040A => array(0x10432), - 0x1040B => array(0x10433), - 0x1040C => array(0x10434), - 0x1040D => array(0x10435), - 0x1040E => array(0x10436), - 0x1040F => array(0x10437), - 0x10410 => array(0x10438), - 0x10411 => array(0x10439), - 0x10412 => array(0x1043A), - 0x10413 => array(0x1043B), - 0x10414 => array(0x1043C), - 0x10415 => array(0x1043D), - 0x10416 => array(0x1043E), - 0x10417 => array(0x1043F), - 0x10418 => array(0x10440), - 0x10419 => array(0x10441), - 0x1041A => array(0x10442), - 0x1041B => array(0x10443), - 0x1041C => array(0x10444), - 0x1041D => array(0x10445), - 0x1041E => array(0x10446), - 0x1041F => array(0x10447), - 0x10420 => array(0x10448), - 0x10421 => array(0x10449), - 0x10422 => array(0x1044A), - 0x10423 => array(0x1044B), - 0x10424 => array(0x1044C), - 0x10425 => array(0x1044D), - 0x1D400 => array(0x61), - 0x1D401 => array(0x62), - 0x1D402 => array(0x63), - 0x1D403 => array(0x64), - 0x1D404 => array(0x65), - 0x1D405 => array(0x66), - 0x1D406 => array(0x67), - 0x1D407 => array(0x68), - 0x1D408 => array(0x69), - 0x1D409 => array(0x6A), - 0x1D40A => array(0x6B), - 0x1D40B => array(0x6C), - 0x1D40C => array(0x6D), - 0x1D40D => array(0x6E), - 0x1D40E => array(0x6F), - 0x1D40F => array(0x70), - 0x1D410 => array(0x71), - 0x1D411 => array(0x72), - 0x1D412 => array(0x73), - 0x1D413 => array(0x74), - 0x1D414 => array(0x75), - 0x1D415 => array(0x76), - 0x1D416 => array(0x77), - 0x1D417 => array(0x78), - 0x1D418 => array(0x79), - 0x1D419 => array(0x7A), - 0x1D434 => array(0x61), - 0x1D435 => array(0x62), - 0x1D436 => array(0x63), - 0x1D437 => array(0x64), - 0x1D438 => array(0x65), - 0x1D439 => array(0x66), - 0x1D43A => array(0x67), - 0x1D43B => array(0x68), - 0x1D43C => array(0x69), - 0x1D43D => array(0x6A), - 0x1D43E => array(0x6B), - 0x1D43F => array(0x6C), - 0x1D440 => array(0x6D), - 0x1D441 => array(0x6E), - 0x1D442 => array(0x6F), - 0x1D443 => array(0x70), - 0x1D444 => array(0x71), - 0x1D445 => array(0x72), - 0x1D446 => array(0x73), - 0x1D447 => array(0x74), - 0x1D448 => array(0x75), - 0x1D449 => array(0x76), - 0x1D44A => array(0x77), - 0x1D44B => array(0x78), - 0x1D44C => array(0x79), - 0x1D44D => array(0x7A), - 0x1D468 => array(0x61), - 0x1D469 => array(0x62), - 0x1D46A => array(0x63), - 0x1D46B => array(0x64), - 0x1D46C => array(0x65), - 0x1D46D => array(0x66), - 0x1D46E => array(0x67), - 0x1D46F => array(0x68), - 0x1D470 => array(0x69), - 0x1D471 => array(0x6A), - 0x1D472 => array(0x6B), - 0x1D473 => array(0x6C), - 0x1D474 => array(0x6D), - 0x1D475 => array(0x6E), - 0x1D476 => array(0x6F), - 0x1D477 => array(0x70), - 0x1D478 => array(0x71), - 0x1D479 => array(0x72), - 0x1D47A => array(0x73), - 0x1D47B => array(0x74), - 0x1D47C => array(0x75), - 0x1D47D => array(0x76), - 0x1D47E => array(0x77), - 0x1D47F => array(0x78), - 0x1D480 => array(0x79), - 0x1D481 => array(0x7A), - 0x1D49C => array(0x61), - 0x1D49E => array(0x63), - 0x1D49F => array(0x64), - 0x1D4A2 => array(0x67), - 0x1D4A5 => array(0x6A), - 0x1D4A6 => array(0x6B), - 0x1D4A9 => array(0x6E), - 0x1D4AA => array(0x6F), - 0x1D4AB => array(0x70), - 0x1D4AC => array(0x71), - 0x1D4AE => array(0x73), - 0x1D4AF => array(0x74), - 0x1D4B0 => array(0x75), - 0x1D4B1 => array(0x76), - 0x1D4B2 => array(0x77), - 0x1D4B3 => array(0x78), - 0x1D4B4 => array(0x79), - 0x1D4B5 => array(0x7A), - 0x1D4D0 => array(0x61), - 0x1D4D1 => array(0x62), - 0x1D4D2 => array(0x63), - 0x1D4D3 => array(0x64), - 0x1D4D4 => array(0x65), - 0x1D4D5 => array(0x66), - 0x1D4D6 => array(0x67), - 0x1D4D7 => array(0x68), - 0x1D4D8 => array(0x69), - 0x1D4D9 => array(0x6A), - 0x1D4DA => array(0x6B), - 0x1D4DB => array(0x6C), - 0x1D4DC => array(0x6D), - 0x1D4DD => array(0x6E), - 0x1D4DE => array(0x6F), - 0x1D4DF => array(0x70), - 0x1D4E0 => array(0x71), - 0x1D4E1 => array(0x72), - 0x1D4E2 => array(0x73), - 0x1D4E3 => array(0x74), - 0x1D4E4 => array(0x75), - 0x1D4E5 => array(0x76), - 0x1D4E6 => array(0x77), - 0x1D4E7 => array(0x78), - 0x1D4E8 => array(0x79), - 0x1D4E9 => array(0x7A), - 0x1D504 => array(0x61), - 0x1D505 => array(0x62), - 0x1D507 => array(0x64), - 0x1D508 => array(0x65), - 0x1D509 => array(0x66), - 0x1D50A => array(0x67), - 0x1D50D => array(0x6A), - 0x1D50E => array(0x6B), - 0x1D50F => array(0x6C), - 0x1D510 => array(0x6D), - 0x1D511 => array(0x6E), - 0x1D512 => array(0x6F), - 0x1D513 => array(0x70), - 0x1D514 => array(0x71), - 0x1D516 => array(0x73), - 0x1D517 => array(0x74), - 0x1D518 => array(0x75), - 0x1D519 => array(0x76), - 0x1D51A => array(0x77), - 0x1D51B => array(0x78), - 0x1D51C => array(0x79), - 0x1D538 => array(0x61), - 0x1D539 => array(0x62), - 0x1D53B => array(0x64), - 0x1D53C => array(0x65), - 0x1D53D => array(0x66), - 0x1D53E => array(0x67), - 0x1D540 => array(0x69), - 0x1D541 => array(0x6A), - 0x1D542 => array(0x6B), - 0x1D543 => array(0x6C), - 0x1D544 => array(0x6D), - 0x1D546 => array(0x6F), - 0x1D54A => array(0x73), - 0x1D54B => array(0x74), - 0x1D54C => array(0x75), - 0x1D54D => array(0x76), - 0x1D54E => array(0x77), - 0x1D54F => array(0x78), - 0x1D550 => array(0x79), - 0x1D56C => array(0x61), - 0x1D56D => array(0x62), - 0x1D56E => array(0x63), - 0x1D56F => array(0x64), - 0x1D570 => array(0x65), - 0x1D571 => array(0x66), - 0x1D572 => array(0x67), - 0x1D573 => array(0x68), - 0x1D574 => array(0x69), - 0x1D575 => array(0x6A), - 0x1D576 => array(0x6B), - 0x1D577 => array(0x6C), - 0x1D578 => array(0x6D), - 0x1D579 => array(0x6E), - 0x1D57A => array(0x6F), - 0x1D57B => array(0x70), - 0x1D57C => array(0x71), - 0x1D57D => array(0x72), - 0x1D57E => array(0x73), - 0x1D57F => array(0x74), - 0x1D580 => array(0x75), - 0x1D581 => array(0x76), - 0x1D582 => array(0x77), - 0x1D583 => array(0x78), - 0x1D584 => array(0x79), - 0x1D585 => array(0x7A), - 0x1D5A0 => array(0x61), - 0x1D5A1 => array(0x62), - 0x1D5A2 => array(0x63), - 0x1D5A3 => array(0x64), - 0x1D5A4 => array(0x65), - 0x1D5A5 => array(0x66), - 0x1D5A6 => array(0x67), - 0x1D5A7 => array(0x68), - 0x1D5A8 => array(0x69), - 0x1D5A9 => array(0x6A), - 0x1D5AA => array(0x6B), - 0x1D5AB => array(0x6C), - 0x1D5AC => array(0x6D), - 0x1D5AD => array(0x6E), - 0x1D5AE => array(0x6F), - 0x1D5AF => array(0x70), - 0x1D5B0 => array(0x71), - 0x1D5B1 => array(0x72), - 0x1D5B2 => array(0x73), - 0x1D5B3 => array(0x74), - 0x1D5B4 => array(0x75), - 0x1D5B5 => array(0x76), - 0x1D5B6 => array(0x77), - 0x1D5B7 => array(0x78), - 0x1D5B8 => array(0x79), - 0x1D5B9 => array(0x7A), - 0x1D5D4 => array(0x61), - 0x1D5D5 => array(0x62), - 0x1D5D6 => array(0x63), - 0x1D5D7 => array(0x64), - 0x1D5D8 => array(0x65), - 0x1D5D9 => array(0x66), - 0x1D5DA => array(0x67), - 0x1D5DB => array(0x68), - 0x1D5DC => array(0x69), - 0x1D5DD => array(0x6A), - 0x1D5DE => array(0x6B), - 0x1D5DF => array(0x6C), - 0x1D5E0 => array(0x6D), - 0x1D5E1 => array(0x6E), - 0x1D5E2 => array(0x6F), - 0x1D5E3 => array(0x70), - 0x1D5E4 => array(0x71), - 0x1D5E5 => array(0x72), - 0x1D5E6 => array(0x73), - 0x1D5E7 => array(0x74), - 0x1D5E8 => array(0x75), - 0x1D5E9 => array(0x76), - 0x1D5EA => array(0x77), - 0x1D5EB => array(0x78), - 0x1D5EC => array(0x79), - 0x1D5ED => array(0x7A), - 0x1D608 => array(0x61), - 0x1D609 => array(0x62), - 0x1D60A => array(0x63), - 0x1D60B => array(0x64), - 0x1D60C => array(0x65), - 0x1D60D => array(0x66), - 0x1D60E => array(0x67), - 0x1D60F => array(0x68), - 0x1D610 => array(0x69), - 0x1D611 => array(0x6A), - 0x1D612 => array(0x6B), - 0x1D613 => array(0x6C), - 0x1D614 => array(0x6D), - 0x1D615 => array(0x6E), - 0x1D616 => array(0x6F), - 0x1D617 => array(0x70), - 0x1D618 => array(0x71), - 0x1D619 => array(0x72), - 0x1D61A => array(0x73), - 0x1D61B => array(0x74), - 0x1D61C => array(0x75), - 0x1D61D => array(0x76), - 0x1D61E => array(0x77), - 0x1D61F => array(0x78), - 0x1D620 => array(0x79), - 0x1D621 => array(0x7A), - 0x1D63C => array(0x61), - 0x1D63D => array(0x62), - 0x1D63E => array(0x63), - 0x1D63F => array(0x64), - 0x1D640 => array(0x65), - 0x1D641 => array(0x66), - 0x1D642 => array(0x67), - 0x1D643 => array(0x68), - 0x1D644 => array(0x69), - 0x1D645 => array(0x6A), - 0x1D646 => array(0x6B), - 0x1D647 => array(0x6C), - 0x1D648 => array(0x6D), - 0x1D649 => array(0x6E), - 0x1D64A => array(0x6F), - 0x1D64B => array(0x70), - 0x1D64C => array(0x71), - 0x1D64D => array(0x72), - 0x1D64E => array(0x73), - 0x1D64F => array(0x74), - 0x1D650 => array(0x75), - 0x1D651 => array(0x76), - 0x1D652 => array(0x77), - 0x1D653 => array(0x78), - 0x1D654 => array(0x79), - 0x1D655 => array(0x7A), - 0x1D670 => array(0x61), - 0x1D671 => array(0x62), - 0x1D672 => array(0x63), - 0x1D673 => array(0x64), - 0x1D674 => array(0x65), - 0x1D675 => array(0x66), - 0x1D676 => array(0x67), - 0x1D677 => array(0x68), - 0x1D678 => array(0x69), - 0x1D679 => array(0x6A), - 0x1D67A => array(0x6B), - 0x1D67B => array(0x6C), - 0x1D67C => array(0x6D), - 0x1D67D => array(0x6E), - 0x1D67E => array(0x6F), - 0x1D67F => array(0x70), - 0x1D680 => array(0x71), - 0x1D681 => array(0x72), - 0x1D682 => array(0x73), - 0x1D683 => array(0x74), - 0x1D684 => array(0x75), - 0x1D685 => array(0x76), - 0x1D686 => array(0x77), - 0x1D687 => array(0x78), - 0x1D688 => array(0x79), - 0x1D689 => array(0x7A), - 0x1D6A8 => array(0x3B1), - 0x1D6A9 => array(0x3B2), - 0x1D6AA => array(0x3B3), - 0x1D6AB => array(0x3B4), - 0x1D6AC => array(0x3B5), - 0x1D6AD => array(0x3B6), - 0x1D6AE => array(0x3B7), - 0x1D6AF => array(0x3B8), - 0x1D6B0 => array(0x3B9), - 0x1D6B1 => array(0x3BA), - 0x1D6B2 => array(0x3BB), - 0x1D6B3 => array(0x3BC), - 0x1D6B4 => array(0x3BD), - 0x1D6B5 => array(0x3BE), - 0x1D6B6 => array(0x3BF), - 0x1D6B7 => array(0x3C0), - 0x1D6B8 => array(0x3C1), - 0x1D6B9 => array(0x3B8), - 0x1D6BA => array(0x3C3), - 0x1D6BB => array(0x3C4), - 0x1D6BC => array(0x3C5), - 0x1D6BD => array(0x3C6), - 0x1D6BE => array(0x3C7), - 0x1D6BF => array(0x3C8), - 0x1D6C0 => array(0x3C9), - 0x1D6D3 => array(0x3C3), - 0x1D6E2 => array(0x3B1), - 0x1D6E3 => array(0x3B2), - 0x1D6E4 => array(0x3B3), - 0x1D6E5 => array(0x3B4), - 0x1D6E6 => array(0x3B5), - 0x1D6E7 => array(0x3B6), - 0x1D6E8 => array(0x3B7), - 0x1D6E9 => array(0x3B8), - 0x1D6EA => array(0x3B9), - 0x1D6EB => array(0x3BA), - 0x1D6EC => array(0x3BB), - 0x1D6ED => array(0x3BC), - 0x1D6EE => array(0x3BD), - 0x1D6EF => array(0x3BE), - 0x1D6F0 => array(0x3BF), - 0x1D6F1 => array(0x3C0), - 0x1D6F2 => array(0x3C1), - 0x1D6F3 => array(0x3B8), - 0x1D6F4 => array(0x3C3), - 0x1D6F5 => array(0x3C4), - 0x1D6F6 => array(0x3C5), - 0x1D6F7 => array(0x3C6), - 0x1D6F8 => array(0x3C7), - 0x1D6F9 => array(0x3C8), - 0x1D6FA => array(0x3C9), - 0x1D70D => array(0x3C3), - 0x1D71C => array(0x3B1), - 0x1D71D => array(0x3B2), - 0x1D71E => array(0x3B3), - 0x1D71F => array(0x3B4), - 0x1D720 => array(0x3B5), - 0x1D721 => array(0x3B6), - 0x1D722 => array(0x3B7), - 0x1D723 => array(0x3B8), - 0x1D724 => array(0x3B9), - 0x1D725 => array(0x3BA), - 0x1D726 => array(0x3BB), - 0x1D727 => array(0x3BC), - 0x1D728 => array(0x3BD), - 0x1D729 => array(0x3BE), - 0x1D72A => array(0x3BF), - 0x1D72B => array(0x3C0), - 0x1D72C => array(0x3C1), - 0x1D72D => array(0x3B8), - 0x1D72E => array(0x3C3), - 0x1D72F => array(0x3C4), - 0x1D730 => array(0x3C5), - 0x1D731 => array(0x3C6), - 0x1D732 => array(0x3C7), - 0x1D733 => array(0x3C8), - 0x1D734 => array(0x3C9), - 0x1D747 => array(0x3C3), - 0x1D756 => array(0x3B1), - 0x1D757 => array(0x3B2), - 0x1D758 => array(0x3B3), - 0x1D759 => array(0x3B4), - 0x1D75A => array(0x3B5), - 0x1D75B => array(0x3B6), - 0x1D75C => array(0x3B7), - 0x1D75D => array(0x3B8), - 0x1D75E => array(0x3B9), - 0x1D75F => array(0x3BA), - 0x1D760 => array(0x3BB), - 0x1D761 => array(0x3BC), - 0x1D762 => array(0x3BD), - 0x1D763 => array(0x3BE), - 0x1D764 => array(0x3BF), - 0x1D765 => array(0x3C0), - 0x1D766 => array(0x3C1), - 0x1D767 => array(0x3B8), - 0x1D768 => array(0x3C3), - 0x1D769 => array(0x3C4), - 0x1D76A => array(0x3C5), - 0x1D76B => array(0x3C6), - 0x1D76C => array(0x3C7), - 0x1D76D => array(0x3C8), - 0x1D76E => array(0x3C9), - 0x1D781 => array(0x3C3), - 0x1D790 => array(0x3B1), - 0x1D791 => array(0x3B2), - 0x1D792 => array(0x3B3), - 0x1D793 => array(0x3B4), - 0x1D794 => array(0x3B5), - 0x1D795 => array(0x3B6), - 0x1D796 => array(0x3B7), - 0x1D797 => array(0x3B8), - 0x1D798 => array(0x3B9), - 0x1D799 => array(0x3BA), - 0x1D79A => array(0x3BB), - 0x1D79B => array(0x3BC), - 0x1D79C => array(0x3BD), - 0x1D79D => array(0x3BE), - 0x1D79E => array(0x3BF), - 0x1D79F => array(0x3C0), - 0x1D7A0 => array(0x3C1), - 0x1D7A1 => array(0x3B8), - 0x1D7A2 => array(0x3C3), - 0x1D7A3 => array(0x3C4), - 0x1D7A4 => array(0x3C5), - 0x1D7A5 => array(0x3C6), - 0x1D7A6 => array(0x3C7), - 0x1D7A7 => array(0x3C8), - 0x1D7A8 => array(0x3C9), - 0x1D7BB => array(0x3C3), - 0x3F9 => array(0x3C3), - 0x1D2C => array(0x61), - 0x1D2D => array(0xE6), - 0x1D2E => array(0x62), - 0x1D30 => array(0x64), - 0x1D31 => array(0x65), - 0x1D32 => array(0x1DD), - 0x1D33 => array(0x67), - 0x1D34 => array(0x68), - 0x1D35 => array(0x69), - 0x1D36 => array(0x6A), - 0x1D37 => array(0x6B), - 0x1D38 => array(0x6C), - 0x1D39 => array(0x6D), - 0x1D3A => array(0x6E), - 0x1D3C => array(0x6F), - 0x1D3D => array(0x223), - 0x1D3E => array(0x70), - 0x1D3F => array(0x72), - 0x1D40 => array(0x74), - 0x1D41 => array(0x75), - 0x1D42 => array(0x77), - 0x213B => array(0x66, 0x61, 0x78), - 0x3250 => array(0x70, 0x74, 0x65), - 0x32CC => array(0x68, 0x67), - 0x32CE => array(0x65, 0x76), - 0x32CF => array(0x6C, 0x74, 0x64), - 0x337A => array(0x69, 0x75), - 0x33DE => array(0x76, 0x2215, 0x6D), - 0x33DF => array(0x61, 0x2215, 0x6D) - ); - - /** - * Normalization Combining Classes; Code Points not listed - * got Combining Class 0. - * - * @static - * @var array - * @access private - */ - private static $_np_norm_combcls = array( - 0x334 => 1, - 0x335 => 1, - 0x336 => 1, - 0x337 => 1, - 0x338 => 1, - 0x93C => 7, - 0x9BC => 7, - 0xA3C => 7, - 0xABC => 7, - 0xB3C => 7, - 0xCBC => 7, - 0x1037 => 7, - 0x3099 => 8, - 0x309A => 8, - 0x94D => 9, - 0x9CD => 9, - 0xA4D => 9, - 0xACD => 9, - 0xB4D => 9, - 0xBCD => 9, - 0xC4D => 9, - 0xCCD => 9, - 0xD4D => 9, - 0xDCA => 9, - 0xE3A => 9, - 0xF84 => 9, - 0x1039 => 9, - 0x1714 => 9, - 0x1734 => 9, - 0x17D2 => 9, - 0x5B0 => 10, - 0x5B1 => 11, - 0x5B2 => 12, - 0x5B3 => 13, - 0x5B4 => 14, - 0x5B5 => 15, - 0x5B6 => 16, - 0x5B7 => 17, - 0x5B8 => 18, - 0x5B9 => 19, - 0x5BB => 20, - 0x5Bc => 21, - 0x5BD => 22, - 0x5BF => 23, - 0x5C1 => 24, - 0x5C2 => 25, - 0xFB1E => 26, - 0x64B => 27, - 0x64C => 28, - 0x64D => 29, - 0x64E => 30, - 0x64F => 31, - 0x650 => 32, - 0x651 => 33, - 0x652 => 34, - 0x670 => 35, - 0x711 => 36, - 0xC55 => 84, - 0xC56 => 91, - 0xE38 => 103, - 0xE39 => 103, - 0xE48 => 107, - 0xE49 => 107, - 0xE4A => 107, - 0xE4B => 107, - 0xEB8 => 118, - 0xEB9 => 118, - 0xEC8 => 122, - 0xEC9 => 122, - 0xECA => 122, - 0xECB => 122, - 0xF71 => 129, - 0xF72 => 130, - 0xF7A => 130, - 0xF7B => 130, - 0xF7C => 130, - 0xF7D => 130, - 0xF80 => 130, - 0xF74 => 132, - 0x321 => 202, - 0x322 => 202, - 0x327 => 202, - 0x328 => 202, - 0x31B => 216, - 0xF39 => 216, - 0x1D165 => 216, - 0x1D166 => 216, - 0x1D16E => 216, - 0x1D16F => 216, - 0x1D170 => 216, - 0x1D171 => 216, - 0x1D172 => 216, - 0x302A => 218, - 0x316 => 220, - 0x317 => 220, - 0x318 => 220, - 0x319 => 220, - 0x31C => 220, - 0x31D => 220, - 0x31E => 220, - 0x31F => 220, - 0x320 => 220, - 0x323 => 220, - 0x324 => 220, - 0x325 => 220, - 0x326 => 220, - 0x329 => 220, - 0x32A => 220, - 0x32B => 220, - 0x32C => 220, - 0x32D => 220, - 0x32E => 220, - 0x32F => 220, - 0x330 => 220, - 0x331 => 220, - 0x332 => 220, - 0x333 => 220, - 0x339 => 220, - 0x33A => 220, - 0x33B => 220, - 0x33C => 220, - 0x347 => 220, - 0x348 => 220, - 0x349 => 220, - 0x34D => 220, - 0x34E => 220, - 0x353 => 220, - 0x354 => 220, - 0x355 => 220, - 0x356 => 220, - 0x591 => 220, - 0x596 => 220, - 0x59B => 220, - 0x5A3 => 220, - 0x5A4 => 220, - 0x5A5 => 220, - 0x5A6 => 220, - 0x5A7 => 220, - 0x5AA => 220, - 0x655 => 220, - 0x656 => 220, - 0x6E3 => 220, - 0x6EA => 220, - 0x6ED => 220, - 0x731 => 220, - 0x734 => 220, - 0x737 => 220, - 0x738 => 220, - 0x739 => 220, - 0x73B => 220, - 0x73C => 220, - 0x73E => 220, - 0x742 => 220, - 0x744 => 220, - 0x746 => 220, - 0x748 => 220, - 0x952 => 220, - 0xF18 => 220, - 0xF19 => 220, - 0xF35 => 220, - 0xF37 => 220, - 0xFC6 => 220, - 0x193B => 220, - 0x20E8 => 220, - 0x1D17B => 220, - 0x1D17C => 220, - 0x1D17D => 220, - 0x1D17E => 220, - 0x1D17F => 220, - 0x1D180 => 220, - 0x1D181 => 220, - 0x1D182 => 220, - 0x1D18A => 220, - 0x1D18B => 220, - 0x59A => 222, - 0x5AD => 222, - 0x1929 => 222, - 0x302D => 222, - 0x302E => 224, - 0x302F => 224, - 0x1D16D => 226, - 0x5AE => 228, - 0x18A9 => 228, - 0x302B => 228, - 0x300 => 230, - 0x301 => 230, - 0x302 => 230, - 0x303 => 230, - 0x304 => 230, - 0x305 => 230, - 0x306 => 230, - 0x307 => 230, - 0x308 => 230, - 0x309 => 230, - 0x30A => 230, - 0x30B => 230, - 0x30C => 230, - 0x30D => 230, - 0x30E => 230, - 0x30F => 230, - 0x310 => 230, - 0x311 => 230, - 0x312 => 230, - 0x313 => 230, - 0x314 => 230, - 0x33D => 230, - 0x33E => 230, - 0x33F => 230, - 0x340 => 230, - 0x341 => 230, - 0x342 => 230, - 0x343 => 230, - 0x344 => 230, - 0x346 => 230, - 0x34A => 230, - 0x34B => 230, - 0x34C => 230, - 0x350 => 230, - 0x351 => 230, - 0x352 => 230, - 0x357 => 230, - 0x363 => 230, - 0x364 => 230, - 0x365 => 230, - 0x366 => 230, - 0x367 => 230, - 0x368 => 230, - 0x369 => 230, - 0x36A => 230, - 0x36B => 230, - 0x36C => 230, - 0x36D => 230, - 0x36E => 230, - 0x36F => 230, - 0x483 => 230, - 0x484 => 230, - 0x485 => 230, - 0x486 => 230, - 0x592 => 230, - 0x593 => 230, - 0x594 => 230, - 0x595 => 230, - 0x597 => 230, - 0x598 => 230, - 0x599 => 230, - 0x59C => 230, - 0x59D => 230, - 0x59E => 230, - 0x59F => 230, - 0x5A0 => 230, - 0x5A1 => 230, - 0x5A8 => 230, - 0x5A9 => 230, - 0x5AB => 230, - 0x5AC => 230, - 0x5AF => 230, - 0x5C4 => 230, - 0x610 => 230, - 0x611 => 230, - 0x612 => 230, - 0x613 => 230, - 0x614 => 230, - 0x615 => 230, - 0x653 => 230, - 0x654 => 230, - 0x657 => 230, - 0x658 => 230, - 0x6D6 => 230, - 0x6D7 => 230, - 0x6D8 => 230, - 0x6D9 => 230, - 0x6DA => 230, - 0x6DB => 230, - 0x6DC => 230, - 0x6DF => 230, - 0x6E0 => 230, - 0x6E1 => 230, - 0x6E2 => 230, - 0x6E4 => 230, - 0x6E7 => 230, - 0x6E8 => 230, - 0x6EB => 230, - 0x6EC => 230, - 0x730 => 230, - 0x732 => 230, - 0x733 => 230, - 0x735 => 230, - 0x736 => 230, - 0x73A => 230, - 0x73D => 230, - 0x73F => 230, - 0x740 => 230, - 0x741 => 230, - 0x743 => 230, - 0x745 => 230, - 0x747 => 230, - 0x749 => 230, - 0x74A => 230, - 0x951 => 230, - 0x953 => 230, - 0x954 => 230, - 0xF82 => 230, - 0xF83 => 230, - 0xF86 => 230, - 0xF87 => 230, - 0x170D => 230, - 0x193A => 230, - 0x20D0 => 230, - 0x20D1 => 230, - 0x20D4 => 230, - 0x20D5 => 230, - 0x20D6 => 230, - 0x20D7 => 230, - 0x20DB => 230, - 0x20DC => 230, - 0x20E1 => 230, - 0x20E7 => 230, - 0x20E9 => 230, - 0xFE20 => 230, - 0xFE21 => 230, - 0xFE22 => 230, - 0xFE23 => 230, - 0x1D185 => 230, - 0x1D186 => 230, - 0x1D187 => 230, - 0x1D189 => 230, - 0x1D188 => 230, - 0x1D1AA => 230, - 0x1D1AB => 230, - 0x1D1AC => 230, - 0x1D1AD => 230, - 0x315 => 232, - 0x31A => 232, - 0x302C => 232, - 0x35F => 233, - 0x362 => 233, - 0x35D => 234, - 0x35E => 234, - 0x360 => 234, - 0x361 => 234, - 0x345 => 240 - ); - // }}} - - // {{{ properties - /** - * @var string - * @access private - */ - private $_punycode_prefix = 'xn--'; - - /** - * @access private - */ - private $_invalid_ucs = 0x80000000; - - /** - * @access private - */ - private $_max_ucs = 0x10FFFF; - - /** - * @var int - * @access private - */ - private $_base = 36; - - /** - * @var int - * @access private - */ - private $_tmin = 1; - - /** - * @var int - * @access private - */ - private $_tmax = 26; - - /** - * @var int - * @access private - */ - private $_skew = 38; - - /** - * @var int - * @access private - */ - private $_damp = 700; - - /** - * @var int - * @access private - */ - private $_initial_bias = 72; - - /** - * @var int - * @access private - */ - private $_initial_n = 0x80; - - /** - * @var int - * @access private - */ - private $_slast; - - /** - * @access private - */ - private $_sbase = 0xAC00; - - /** - * @access private - */ - private $_lbase = 0x1100; - - /** - * @access private - */ - private $_vbase = 0x1161; - - /** - * @access private - */ - private $_tbase = 0x11a7; - - /** - * @var int - * @access private - */ - private $_lcount = 19; - - /** - * @var int - * @access private - */ - private $_vcount = 21; - - /** - * @var int - * @access private - */ - private $_tcount = 28; - - /** - * vcount * tcount - * - * @var int - * @access private - */ - private $_ncount = 588; - - /** - * lcount * tcount * vcount - * - * @var int - * @access private - */ - private $_scount = 11172; - - /** - * Default encoding for encode()'s input and decode()'s output is UTF-8; - * Other possible encodings are ucs4_string and ucs4_array - * See {@link setParams()} for how to select these - * - * @var bool - * @access private - */ - private $_api_encoding = 'utf8'; - - /** - * Overlong UTF-8 encodings are forbidden - * - * @var bool - * @access private - */ - private $_allow_overlong = false; - - /** - * Behave strict or not - * - * @var bool - * @access private - */ - private $_strict_mode = false; - - /** - * IDNA-version to use - * - * Values are "2003" and "2008". - * Defaults to "2003", since that was the original version and for - * compatibility with previous versions of this library. - * If you need to encode "new" characters like the German "Eszett", - * please switch to 2008 first before encoding. - * - * @var bool - * @access private - */ - private $_version = '2003'; - - /** - * Cached value indicating whether or not mbstring function overloading is - * on for strlen - * - * This is cached for optimal performance. - * - * @var boolean - * @see Net_IDNA2::_byteLength() - */ - private static $_mb_string_overload = null; - // }}} - - - // {{{ constructor - /** - * Constructor - * - * @param array $options Options to initialise the object with - * - * @access public - * @see setParams() - */ - public function __construct($options = null) - { - $this->_slast = $this->_sbase + $this->_lcount * $this->_vcount * $this->_tcount; - - if (is_array($options)) { - $this->setParams($options); - } - - // populate mbstring overloading cache if not set - if (self::$_mb_string_overload === null) { - self::$_mb_string_overload = (extension_loaded('mbstring') - && (ini_get('mbstring.func_overload') & 0x02) === 0x02); - } - } - // }}} - - - /** - * Sets a new option value. Available options and values: - * - * [utf8 - Use either UTF-8 or ISO-8859-1 as input (true for UTF-8, false - * otherwise); The output is always UTF-8] - * [overlong - Unicode does not allow unnecessarily long encodings of chars, - * to allow this, set this parameter to true, else to false; - * default is false.] - * [strict - true: strict mode, good for registration purposes - Causes errors - * on failures; false: loose mode, ideal for "wildlife" applications - * by silently ignoring errors and returning the original input instead] - * - * @param mixed $option Parameter to set (string: single parameter; array of Parameter => Value pairs) - * @param string $value Value to use (if parameter 1 is a string) - * - * @return boolean true on success, false otherwise - * @access public - */ - public function setParams($option, $value = false) - { - if (!is_array($option)) { - $option = array($option => $value); - } - - foreach ($option as $k => $v) { - switch ($k) { - case 'encoding': - switch ($v) { - case 'utf8': - case 'ucs4_string': - case 'ucs4_array': - $this->_api_encoding = $v; - break; - - default: - throw new InvalidArgumentException('Set Parameter: Unknown parameter '.$v.' for option '.$k); - } - - break; - - case 'overlong': - $this->_allow_overlong = ($v) ? true : false; - break; - - case 'strict': - $this->_strict_mode = ($v) ? true : false; - break; - - case 'version': - if (in_array($v, array('2003', '2008'))) { - $this->_version = $v; - } else { - throw new InvalidArgumentException('Set Parameter: Invalid parameter '.$v.' for option '.$k); - } - break; - - default: - return false; - } - } - - return true; - } - - /** - * Encode a given UTF-8 domain name. - * - * @param string $decoded Domain name (UTF-8 or UCS-4) - * @param string $one_time_encoding Desired input encoding, see {@link set_parameter} - * If not given will use default-encoding - * - * @return string Encoded Domain name (ACE string) - * @return mixed processed string - * @throws Exception - * @access public - */ - public function encode($decoded, $one_time_encoding = false) - { - // Forcing conversion of input to UCS4 array - // If one time encoding is given, use this, else the objects property - switch (($one_time_encoding) ? $one_time_encoding : $this->_api_encoding) { - case 'utf8': - $decoded = $this->_utf8_to_ucs4($decoded); - break; - case 'ucs4_string': - $decoded = $this->_ucs4_string_to_ucs4($decoded); - case 'ucs4_array': // No break; before this line. Catch case, but do nothing - break; - default: - throw new InvalidArgumentException('Unsupported input format'); - } - - // No input, no output, what else did you expect? - if (empty($decoded)) return ''; - - // Anchors for iteration - $last_begin = 0; - // Output string - $output = ''; - - foreach ($decoded as $k => $v) { - // Make sure to use just the plain dot - switch($v) { - case 0x3002: - case 0xFF0E: - case 0xFF61: - $decoded[$k] = 0x2E; - // It's right, no break here - // The codepoints above have to be converted to dots anyway - - // Stumbling across an anchoring character - case 0x2E: - case 0x2F: - case 0x3A: - case 0x3F: - case 0x40: - // Neither email addresses nor URLs allowed in strict mode - if ($this->_strict_mode) { - throw new InvalidArgumentException('Neither email addresses nor URLs are allowed in strict mode.'); - } - // Skip first char - if ($k) { - $encoded = ''; - $encoded = $this->_encode(array_slice($decoded, $last_begin, (($k)-$last_begin))); - if ($encoded) { - $output .= $encoded; - } else { - $output .= $this->_ucs4_to_utf8(array_slice($decoded, $last_begin, (($k)-$last_begin))); - } - $output .= chr($decoded[$k]); - } - $last_begin = $k + 1; - } - } - // Catch the rest of the string - if ($last_begin) { - $inp_len = sizeof($decoded); - $encoded = ''; - $encoded = $this->_encode(array_slice($decoded, $last_begin, (($inp_len)-$last_begin))); - if ($encoded) { - $output .= $encoded; - } else { - $output .= $this->_ucs4_to_utf8(array_slice($decoded, $last_begin, (($inp_len)-$last_begin))); - } - return $output; - } - - if ($output = $this->_encode($decoded)) { - return $output; - } - - return $this->_ucs4_to_utf8($decoded); - } - - /** - * Decode a given ACE domain name. - * - * @param string $input Domain name (ACE string) - * @param string $one_time_encoding Desired output encoding, see {@link set_parameter} - * - * @return string Decoded Domain name (UTF-8 or UCS-4) - * @throws Exception - * @access public - */ - public function decode($input, $one_time_encoding = false) - { - // Optionally set - if ($one_time_encoding) { - switch ($one_time_encoding) { - case 'utf8': - case 'ucs4_string': - case 'ucs4_array': - break; - default: - throw new InvalidArgumentException('Unknown encoding '.$one_time_encoding); - } - } - // Make sure to drop any newline characters around - $input = trim($input); - - // Negotiate input and try to determine, wether it is a plain string, - // an email address or something like a complete URL - if (strpos($input, '@')) { // Maybe it is an email address - // No no in strict mode - if ($this->_strict_mode) { - throw new InvalidArgumentException('Only simple domain name parts can be handled in strict mode'); - } - list($email_pref, $input) = explode('@', $input, 2); - $arr = explode('.', $input); - foreach ($arr as $k => $v) { - $conv = $this->_decode($v); - if ($conv) $arr[$k] = $conv; - } - $return = $email_pref . '@' . join('.', $arr); - } elseif (preg_match('![:\./]!', $input)) { // Or a complete domain name (with or without paths / parameters) - // No no in strict mode - if ($this->_strict_mode) { - throw new InvalidArgumentException('Only simple domain name parts can be handled in strict mode'); - } - - $parsed = parse_url($input); - if (isset($parsed['host'])) { - $arr = explode('.', $parsed['host']); - foreach ($arr as $k => $v) { - $conv = $this->_decode($v); - if ($conv) $arr[$k] = $conv; - } - $parsed['host'] = join('.', $arr); - if (isset($parsed['scheme'])) { - $parsed['scheme'] .= (strtolower($parsed['scheme']) == 'mailto') ? ':' : '://'; - } - $return = $this->_unparse_url($parsed); - } else { // parse_url seems to have failed, try without it - $arr = explode('.', $input); - foreach ($arr as $k => $v) { - $conv = $this->_decode($v); - if ($conv) $arr[$k] = $conv; - } - $return = join('.', $arr); - } - } else { // Otherwise we consider it being a pure domain name string - $return = $this->_decode($input); - } - // The output is UTF-8 by default, other output formats need conversion here - // If one time encoding is given, use this, else the objects property - switch (($one_time_encoding) ? $one_time_encoding : $this->_api_encoding) { - case 'utf8': - return $return; - break; - case 'ucs4_string': - return $this->_ucs4_to_ucs4_string($this->_utf8_to_ucs4($return)); - break; - case 'ucs4_array': - return $this->_utf8_to_ucs4($return); - break; - default: - throw new InvalidArgumentException('Unsupported output format'); - } - } - - - // {{{ private - /** - * Opposite function to parse_url() - * - * Inspired by code from comments of php.net-documentation for parse_url() - * - * @param array $parts_arr parts (strings) as returned by parse_url() - * - * @return string - * @access private - */ - private function _unparse_url($parts_arr) - { - if (!empty($parts_arr['scheme'])) { - $ret_url = $parts_arr['scheme']; - } - if (!empty($parts_arr['user'])) { - $ret_url .= $parts_arr['user']; - if (!empty($parts_arr['pass'])) { - $ret_url .= ':' . $parts_arr['pass']; - } - $ret_url .= '@'; - } - $ret_url .= $parts_arr['host']; - if (!empty($parts_arr['port'])) { - $ret_url .= ':' . $parts_arr['port']; - } - $ret_url .= $parts_arr['path']; - if (!empty($parts_arr['query'])) { - $ret_url .= '?' . $parts_arr['query']; - } - if (!empty($parts_arr['fragment'])) { - $ret_url .= '#' . $parts_arr['fragment']; - } - return $ret_url; - } - - /** - * The actual encoding algorithm. - * - * @param string $decoded Decoded string which should be encoded - * - * @return string Encoded string - * @throws Exception - * @access private - */ - private function _encode($decoded) - { - // We cannot encode a domain name containing the Punycode prefix - $extract = self::_byteLength($this->_punycode_prefix); - $check_pref = $this->_utf8_to_ucs4($this->_punycode_prefix); - $check_deco = array_slice($decoded, 0, $extract); - - if ($check_pref == $check_deco) { - throw new InvalidArgumentException('This is already a punycode string'); - } - - // We will not try to encode strings consisting of basic code points only - $encodable = false; - foreach ($decoded as $k => $v) { - if ($v > 0x7a) { - $encodable = true; - break; - } - } - if (!$encodable) { - if ($this->_strict_mode) { - throw new InvalidArgumentException('The given string does not contain encodable chars'); - } - - return false; - } - - // Do NAMEPREP - $decoded = $this->_nameprep($decoded); - - $deco_len = count($decoded); - - // Empty array - if (!$deco_len) { - return false; - } - - // How many chars have been consumed - $codecount = 0; - - // Start with the prefix; copy it to output - $encoded = $this->_punycode_prefix; - - $encoded = ''; - // Copy all basic code points to output - for ($i = 0; $i < $deco_len; ++$i) { - $test = $decoded[$i]; - // Will match [0-9a-zA-Z-] - if ((0x2F < $test && $test < 0x40) - || (0x40 < $test && $test < 0x5B) - || (0x60 < $test && $test <= 0x7B) - || (0x2D == $test) - ) { - $encoded .= chr($decoded[$i]); - $codecount++; - } - } - - // All codepoints were basic ones - if ($codecount == $deco_len) { - return $encoded; - } - - // Start with the prefix; copy it to output - $encoded = $this->_punycode_prefix . $encoded; - - // If we have basic code points in output, add an hyphen to the end - if ($codecount) { - $encoded .= '-'; - } - - // Now find and encode all non-basic code points - $is_first = true; - $cur_code = $this->_initial_n; - $bias = $this->_initial_bias; - $delta = 0; - - while ($codecount < $deco_len) { - // Find the smallest code point >= the current code point and - // remember the last ouccrence of it in the input - for ($i = 0, $next_code = $this->_max_ucs; $i < $deco_len; $i++) { - if ($decoded[$i] >= $cur_code && $decoded[$i] <= $next_code) { - $next_code = $decoded[$i]; - } - } - - $delta += ($next_code - $cur_code) * ($codecount + 1); - $cur_code = $next_code; - - // Scan input again and encode all characters whose code point is $cur_code - for ($i = 0; $i < $deco_len; $i++) { - if ($decoded[$i] < $cur_code) { - $delta++; - } else if ($decoded[$i] == $cur_code) { - for ($q = $delta, $k = $this->_base; 1; $k += $this->_base) { - $t = ($k <= $bias)? - $this->_tmin : - (($k >= $bias + $this->_tmax)? $this->_tmax : $k - $bias); - - if ($q < $t) { - break; - } - - $encoded .= $this->_encodeDigit(ceil($t + (($q - $t) % ($this->_base - $t)))); - $q = ($q - $t) / ($this->_base - $t); - } - - $encoded .= $this->_encodeDigit($q); - $bias = $this->_adapt($delta, $codecount + 1, $is_first); - $codecount++; - $delta = 0; - $is_first = false; - } - } - - $delta++; - $cur_code++; - } - - return $encoded; - } - - /** - * The actual decoding algorithm. - * - * @param string $encoded Encoded string which should be decoded - * - * @return string Decoded string - * @throws Exception - * @access private - */ - private function _decode($encoded) - { - // We do need to find the Punycode prefix - if (!preg_match('!^' . preg_quote($this->_punycode_prefix, '!') . '!', $encoded)) { - return false; - } - - $encode_test = preg_replace('!^' . preg_quote($this->_punycode_prefix, '!') . '!', '', $encoded); - - // If nothing left after removing the prefix, it is hopeless - if (!$encode_test) { - return false; - } - - // Find last occurence of the delimiter - $delim_pos = strrpos($encoded, '-'); - - if ($delim_pos > self::_byteLength($this->_punycode_prefix)) { - for ($k = self::_byteLength($this->_punycode_prefix); $k < $delim_pos; ++$k) { - $decoded[] = ord($encoded{$k}); - } - } else { - $decoded = array(); - } - - $deco_len = count($decoded); - $enco_len = self::_byteLength($encoded); - - // Wandering through the strings; init - $is_first = true; - $bias = $this->_initial_bias; - $idx = 0; - $char = $this->_initial_n; - - for ($enco_idx = ($delim_pos)? ($delim_pos + 1) : 0; $enco_idx < $enco_len; ++$deco_len) { - for ($old_idx = $idx, $w = 1, $k = $this->_base; 1 ; $k += $this->_base) { - $digit = $this->_decodeDigit($encoded{$enco_idx++}); - $idx += $digit * $w; - - $t = ($k <= $bias) ? - $this->_tmin : - (($k >= $bias + $this->_tmax)? $this->_tmax : ($k - $bias)); - - if ($digit < $t) { - break; - } - - $w = (int)($w * ($this->_base - $t)); - } - - $bias = $this->_adapt($idx - $old_idx, $deco_len + 1, $is_first); - $is_first = false; - $char += (int) ($idx / ($deco_len + 1)); - $idx %= ($deco_len + 1); - - if ($deco_len > 0) { - // Make room for the decoded char - for ($i = $deco_len; $i > $idx; $i--) { - $decoded[$i] = $decoded[($i - 1)]; - } - } - - $decoded[$idx++] = $char; - } - - return $this->_ucs4_to_utf8($decoded); - } - - /** - * Adapt the bias according to the current code point and position. - * - * @param int $delta ... - * @param int $npoints ... - * @param boolean $is_first ... - * - * @return int - * @access private - */ - private function _adapt($delta, $npoints, $is_first) - { - $delta = (int) ($is_first ? ($delta / $this->_damp) : ($delta / 2)); - $delta += (int) ($delta / $npoints); - - for ($k = 0; $delta > (($this->_base - $this->_tmin) * $this->_tmax) / 2; $k += $this->_base) { - $delta = (int) ($delta / ($this->_base - $this->_tmin)); - } - - return (int) ($k + ($this->_base - $this->_tmin + 1) * $delta / ($delta + $this->_skew)); - } - - /** - * Encoding a certain digit. - * - * @param int $d One digit to encode - * - * @return char Encoded digit - * @access private - */ - private function _encodeDigit($d) - { - return chr($d + 22 + 75 * ($d < 26)); - } - - /** - * Decode a certain digit. - * - * @param char $cp One digit (character) to decode - * - * @return int Decoded digit - * @access private - */ - private function _decodeDigit($cp) - { - $cp = ord($cp); - return ($cp - 48 < 10)? $cp - 22 : (($cp - 65 < 26)? $cp - 65 : (($cp - 97 < 26)? $cp - 97 : $this->_base)); - } - - /** - * Do Nameprep according to RFC3491 and RFC3454. - * - * @param array $input Unicode Characters - * - * @return string Unicode Characters, Nameprep'd - * @throws Exception - * @access private - */ - private function _nameprep($input) - { - $output = array(); - - // Walking through the input array, performing the required steps on each of - // the input chars and putting the result into the output array - // While mapping required chars we apply the cannonical ordering - - foreach ($input as $v) { - // Map to nothing == skip that code point - if (in_array($v, self::$_np_map_nothing)) { - continue; - } - - // Try to find prohibited input - if (in_array($v, self::$_np_prohibit) || in_array($v, self::$_general_prohibited)) { - throw new Net_IDNA2_Exception_Nameprep('Prohibited input U+' . sprintf('%08X', $v)); - } - - foreach (self::$_np_prohibit_ranges as $range) { - if ($range[0] <= $v && $v <= $range[1]) { - throw new Net_IDNA2_Exception_Nameprep('Prohibited input U+' . sprintf('%08X', $v)); - } - } - - // Hangul syllable decomposition - if (0xAC00 <= $v && $v <= 0xD7AF) { - foreach ($this->_hangulDecompose($v) as $out) { - $output[] = $out; - } - } else if (($this->_version == '2003') && isset(self::$_np_replacemaps[$v])) { - // There's a decomposition mapping for that code point - // Decompositions only in version 2003 (original) of IDNA - foreach ($this->_applyCannonicalOrdering(self::$_np_replacemaps[$v]) as $out) { - $output[] = $out; - } - } else { - $output[] = $v; - } - } - - // Combine code points - - $last_class = 0; - $last_starter = 0; - $out_len = count($output); - - for ($i = 0; $i < $out_len; ++$i) { - $class = $this->_getCombiningClass($output[$i]); - - if ((!$last_class || $last_class != $class) && $class) { - // Try to match - $seq_len = $i - $last_starter; - $out = $this->_combine(array_slice($output, $last_starter, $seq_len)); - - // On match: Replace the last starter with the composed character and remove - // the now redundant non-starter(s) - if ($out) { - $output[$last_starter] = $out; - - if (count($out) != $seq_len) { - for ($j = $i + 1; $j < $out_len; ++$j) { - $output[$j - 1] = $output[$j]; - } - - unset($output[$out_len]); - } - - // Rewind the for loop by one, since there can be more possible compositions - $i--; - $out_len--; - $last_class = ($i == $last_starter)? 0 : $this->_getCombiningClass($output[$i - 1]); - - continue; - } - } - - // The current class is 0 - if (!$class) { - $last_starter = $i; - } - - $last_class = $class; - } - - return $output; - } - - /** - * Decomposes a Hangul syllable - * (see http://www.unicode.org/unicode/reports/tr15/#Hangul). - * - * @param integer $char 32bit UCS4 code point - * - * @return array Either Hangul Syllable decomposed or original 32bit - * value as one value array - * @access private - */ - private function _hangulDecompose($char) - { - $sindex = $char - $this->_sbase; - - if ($sindex < 0 || $sindex >= $this->_scount) { - return array($char); - } - - $result = array(); - $T = $this->_tbase + $sindex % $this->_tcount; - $result[] = (int)($this->_lbase + $sindex / $this->_ncount); - $result[] = (int)($this->_vbase + ($sindex % $this->_ncount) / $this->_tcount); - - if ($T != $this->_tbase) { - $result[] = $T; - } - - return $result; - } - - /** - * Ccomposes a Hangul syllable - * (see http://www.unicode.org/unicode/reports/tr15/#Hangul). - * - * @param array $input Decomposed UCS4 sequence - * - * @return array UCS4 sequence with syllables composed - * @access private - */ - private function _hangulCompose($input) - { - $inp_len = count($input); - - if (!$inp_len) { - return array(); - } - - $result = array(); - $last = $input[0]; - $result[] = $last; // copy first char from input to output - - for ($i = 1; $i < $inp_len; ++$i) { - $char = $input[$i]; - - // Find out, wether two current characters from L and V - $lindex = $last - $this->_lbase; - - if (0 <= $lindex && $lindex < $this->_lcount) { - $vindex = $char - $this->_vbase; - - if (0 <= $vindex && $vindex < $this->_vcount) { - // create syllable of form LV - $last = ($this->_sbase + ($lindex * $this->_vcount + $vindex) * $this->_tcount); - $out_off = count($result) - 1; - $result[$out_off] = $last; // reset last - - // discard char - continue; - } - } - - // Find out, wether two current characters are LV and T - $sindex = $last - $this->_sbase; - - if (0 <= $sindex && $sindex < $this->_scount && ($sindex % $this->_tcount) == 0) { - $tindex = $char - $this->_tbase; - - if (0 <= $tindex && $tindex <= $this->_tcount) { - // create syllable of form LVT - $last += $tindex; - $out_off = count($result) - 1; - $result[$out_off] = $last; // reset last - - // discard char - continue; - } - } - - // if neither case was true, just add the character - $last = $char; - $result[] = $char; - } - - return $result; - } - - /** - * Returns the combining class of a certain wide char. - * - * @param integer $char Wide char to check (32bit integer) - * - * @return integer Combining class if found, else 0 - * @access private - */ - private function _getCombiningClass($char) - { - return isset(self::$_np_norm_combcls[$char])? self::$_np_norm_combcls[$char] : 0; - } - - /** - * Apllies the cannonical ordering of a decomposed UCS4 sequence. - * - * @param array $input Decomposed UCS4 sequence - * - * @return array Ordered USC4 sequence - * @access private - */ - private function _applyCannonicalOrdering($input) - { - $swap = true; - $size = count($input); - - while ($swap) { - $swap = false; - $last = $this->_getCombiningClass($input[0]); - - for ($i = 0; $i < $size - 1; ++$i) { - $next = $this->_getCombiningClass($input[$i + 1]); - - if ($next != 0 && $last > $next) { - // Move item leftward until it fits - for ($j = $i + 1; $j > 0; --$j) { - if ($this->_getCombiningClass($input[$j - 1]) <= $next) { - break; - } - - $t = $input[$j]; - $input[$j] = $input[$j - 1]; - $input[$j - 1] = $t; - $swap = 1; - } - - // Reentering the loop looking at the old character again - $next = $last; - } - - $last = $next; - } - } - - return $input; - } - - /** - * Do composition of a sequence of starter and non-starter. - * - * @param array $input UCS4 Decomposed sequence - * - * @return array Ordered USC4 sequence - * @access private - */ - private function _combine($input) - { - $inp_len = count($input); - - // Is it a Hangul syllable? - if (1 != $inp_len) { - $hangul = $this->_hangulCompose($input); - - // This place is probably wrong - if (count($hangul) != $inp_len) { - return $hangul; - } - } - - foreach (self::$_np_replacemaps as $np_src => $np_target) { - if ($np_target[0] != $input[0]) { - continue; - } - - if (count($np_target) != $inp_len) { - continue; - } - - $hit = false; - - foreach ($input as $k2 => $v2) { - if ($v2 == $np_target[$k2]) { - $hit = true; - } else { - $hit = false; - break; - } - } - - if ($hit) { - return $np_src; - } - } - - return false; - } - - /** - * This converts an UTF-8 encoded string to its UCS-4 (array) representation - * By talking about UCS-4 we mean arrays of 32bit integers representing - * each of the "chars". This is due to PHP not being able to handle strings with - * bit depth different from 8. This applies to the reverse method _ucs4_to_utf8(), too. - * The following UTF-8 encodings are supported: - * - * bytes bits representation - * 1 7 0xxxxxxx - * 2 11 110xxxxx 10xxxxxx - * 3 16 1110xxxx 10xxxxxx 10xxxxxx - * 4 21 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx - * 5 26 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx - * 6 31 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx - * - * Each x represents a bit that can be used to store character data. - * - * @param string $input utf8-encoded string - * - * @return array ucs4-encoded array - * @throws Exception - * @access private - */ - private function _utf8_to_ucs4($input) - { - $output = array(); - $out_len = 0; - $inp_len = self::_byteLength($input, '8bit'); - $mode = 'next'; - $test = 'none'; - for ($k = 0; $k < $inp_len; ++$k) { - $v = ord($input{$k}); // Extract byte from input string - - if ($v < 128) { // We found an ASCII char - put into stirng as is - $output[$out_len] = $v; - ++$out_len; - if ('add' == $mode) { - throw new UnexpectedValueException('Conversion from UTF-8 to UCS-4 failed: malformed input at byte '.$k); - } - continue; - } - if ('next' == $mode) { // Try to find the next start byte; determine the width of the Unicode char - $start_byte = $v; - $mode = 'add'; - $test = 'range'; - if ($v >> 5 == 6) { // &110xxxxx 10xxxxx - $next_byte = 0; // Tells, how many times subsequent bitmasks must rotate 6bits to the left - $v = ($v - 192) << 6; - } elseif ($v >> 4 == 14) { // &1110xxxx 10xxxxxx 10xxxxxx - $next_byte = 1; - $v = ($v - 224) << 12; - } elseif ($v >> 3 == 30) { // &11110xxx 10xxxxxx 10xxxxxx 10xxxxxx - $next_byte = 2; - $v = ($v - 240) << 18; - } elseif ($v >> 2 == 62) { // &111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx - $next_byte = 3; - $v = ($v - 248) << 24; - } elseif ($v >> 1 == 126) { // &1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx - $next_byte = 4; - $v = ($v - 252) << 30; - } else { - throw new UnexpectedValueException('This might be UTF-8, but I don\'t understand it at byte '.$k); - } - if ('add' == $mode) { - $output[$out_len] = (int) $v; - ++$out_len; - continue; - } - } - if ('add' == $mode) { - if (!$this->_allow_overlong && $test == 'range') { - $test = 'none'; - if (($v < 0xA0 && $start_byte == 0xE0) || ($v < 0x90 && $start_byte == 0xF0) || ($v > 0x8F && $start_byte == 0xF4)) { - throw new OutOfRangeException('Bogus UTF-8 character detected (out of legal range) at byte '.$k); - } - } - if ($v >> 6 == 2) { // Bit mask must be 10xxxxxx - $v = ($v - 128) << ($next_byte * 6); - $output[($out_len - 1)] += $v; - --$next_byte; - } else { - throw new UnexpectedValueException('Conversion from UTF-8 to UCS-4 failed: malformed input at byte '.$k); - } - if ($next_byte < 0) { - $mode = 'next'; - } - } - } // for - return $output; - } - - /** - * Convert UCS-4 array into UTF-8 string - * - * @param array $input ucs4-encoded array - * - * @return string utf8-encoded string - * @throws Exception - * @access private - */ - private function _ucs4_to_utf8($input) - { - $output = ''; - - foreach ($input as $v) { - // $v = ord($v); - - if ($v < 128) { - // 7bit are transferred literally - $output .= chr($v); - } else if ($v < 1 << 11) { - // 2 bytes - $output .= chr(192 + ($v >> 6)) - . chr(128 + ($v & 63)); - } else if ($v < 1 << 16) { - // 3 bytes - $output .= chr(224 + ($v >> 12)) - . chr(128 + (($v >> 6) & 63)) - . chr(128 + ($v & 63)); - } else if ($v < 1 << 21) { - // 4 bytes - $output .= chr(240 + ($v >> 18)) - . chr(128 + (($v >> 12) & 63)) - . chr(128 + (($v >> 6) & 63)) - . chr(128 + ($v & 63)); - } else if ($v < 1 << 26) { - // 5 bytes - $output .= chr(248 + ($v >> 24)) - . chr(128 + (($v >> 18) & 63)) - . chr(128 + (($v >> 12) & 63)) - . chr(128 + (($v >> 6) & 63)) - . chr(128 + ($v & 63)); - } else if ($v < 1 << 31) { - // 6 bytes - $output .= chr(252 + ($v >> 30)) - . chr(128 + (($v >> 24) & 63)) - . chr(128 + (($v >> 18) & 63)) - . chr(128 + (($v >> 12) & 63)) - . chr(128 + (($v >> 6) & 63)) - . chr(128 + ($v & 63)); - } else { - throw new UnexpectedValueException('Conversion from UCS-4 to UTF-8 failed: malformed input'); - } - } - - return $output; - } - - /** - * Convert UCS-4 array into UCS-4 string - * - * @param array $input ucs4-encoded array - * - * @return string ucs4-encoded string - * @throws Exception - * @access private - */ - private function _ucs4_to_ucs4_string($input) - { - $output = ''; - // Take array values and split output to 4 bytes per value - // The bit mask is 255, which reads &11111111 - foreach ($input as $v) { - $output .= ($v & (255 << 24) >> 24) . ($v & (255 << 16) >> 16) . ($v & (255 << 8) >> 8) . ($v & 255); - } - return $output; - } - - /** - * Convert UCS-4 string into UCS-4 array - * - * @param string $input ucs4-encoded string - * - * @return array ucs4-encoded array - * @throws InvalidArgumentException - * @access private - */ - private function _ucs4_string_to_ucs4($input) - { - $output = array(); - - $inp_len = self::_byteLength($input); - // Input length must be dividable by 4 - if ($inp_len % 4) { - throw new InvalidArgumentException('Input UCS4 string is broken'); - } - - // Empty input - return empty output - if (!$inp_len) { - return $output; - } - - for ($i = 0, $out_len = -1; $i < $inp_len; ++$i) { - // Increment output position every 4 input bytes - if (!$i % 4) { - $out_len++; - $output[$out_len] = 0; - } - $output[$out_len] += ord($input{$i}) << (8 * (3 - ($i % 4) ) ); - } - return $output; - } - - /** - * Echo hex representation of UCS4 sequence. - * - * @param array $input UCS4 sequence - * @param boolean $include_bit Include bitmask in output - * - * @return void - * @static - * @access private - */ - private static function _showHex($input, $include_bit = false) - { - foreach ($input as $k => $v) { - echo '[', $k, '] => ', sprintf('%X', $v); - - if ($include_bit) { - echo ' (', Net_IDNA2::_showBitmask($v), ')'; - } - - echo "\n"; - } - } - - /** - * Gives you a bit representation of given Byte (8 bits), Word (16 bits) or DWord (32 bits) - * Output width is automagically determined - * - * @param int $octet ... - * - * @return string Bitmask-representation - * @static - * @access private - */ - private static function _showBitmask($octet) - { - if ($octet >= (1 << 16)) { - $w = 31; - } else if ($octet >= (1 << 8)) { - $w = 15; - } else { - $w = 7; - } - - $return = ''; - - for ($i = $w; $i > -1; $i--) { - $return .= ($octet & (1 << $i))? '1' : '0'; - } - - return $return; - } - - /** - * Gets the length of a string in bytes even if mbstring function - * overloading is turned on - * - * @param string $string the string for which to get the length. - * - * @return integer the length of the string in bytes. - * - * @see Net_IDNA2::$_mb_string_overload - */ - private static function _byteLength($string) - { - if (self::$_mb_string_overload) { - return mb_strlen($string, '8bit'); - } - return strlen((binary)$string); - } - - // }}}} - - // {{{ factory - /** - * Attempts to return a concrete IDNA instance for either php4 or php5. - * - * @param array $params Set of paramaters - * - * @return Net_IDNA2 - * @access public - */ - function getInstance($params = array()) - { - return new Net_IDNA2($params); - } - // }}} - - // {{{ singleton - /** - * Attempts to return a concrete IDNA instance for either php4 or php5, - * only creating a new instance if no IDNA instance with the same - * parameters currently exists. - * - * @param array $params Set of paramaters - * - * @return object Net_IDNA2 - * @access public - */ - function singleton($params = array()) - { - static $instances; - if (!isset($instances)) { - $instances = array(); - } - - $signature = serialize($params); - if (!isset($instances[$signature])) { - $instances[$signature] = Net_IDNA2::getInstance($params); - } - - return $instances[$signature]; - } - // }}} -} - -?> diff --git a/lib/ext/Net/IDNA2/Exception.php b/lib/ext/Net/IDNA2/Exception.php deleted file mode 100644 index 72cb1ae..0000000 --- a/lib/ext/Net/IDNA2/Exception.php +++ /dev/null @@ -1,4 +0,0 @@ - | -// | Jon Parise | -// | Damian Alejandro Fernandez Sosa | -// +----------------------------------------------------------------------+ -// -// $Id$ - -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 - * @author Jon Parise - * @author Damian Alejandro Fernandez Sosa - * - * @example basic.php A basic implementation of the Net_SMTP package. - */ -class Net_SMTP -{ - /** - * The server to connect to. - * @var string - * @access public - */ - var $host = 'localhost'; - - /** - * The port to connect to. - * @var int - * @access public - */ - var $port = 25; - - /** - * The value to give when sending EHLO or HELO. - * @var string - * @access public - */ - var $localhost = 'localhost'; - - /** - * List of supported authentication methods, in preferential order. - * @var array - * @access public - */ - var $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 - * @access public - */ - var $pipelining = false; - - /** - * Number of pipelined commands. - * @var int - * @access private - */ - var $_pipelined_commands = 0; - - /** - * Should debugging output be enabled? - * @var boolean - * @access private - */ - var $_debug = false; - - /** - * Debug output handler. - * @var callback - * @access private - */ - var $_debug_handler = null; - - /** - * The socket resource being used to connect to the SMTP server. - * @var resource - * @access private - */ - var $_socket = null; - - /** - * Array of socket options that will be passed to Net_Socket::connect(). - * @see stream_context_create() - * @var array - * @access private - */ - var $_socket_options = null; - - /** - * The socket I/O timeout value in seconds. - * @var int - * @access private - */ - var $_timeout = 0; - - /** - * The most recent server response code. - * @var int - * @access private - */ - var $_code = -1; - - /** - * The most recent server response arguments. - * @var array - * @access private - */ - var $_arguments = array(); - - /** - * Stores the SMTP server's greeting string. - * @var string - * @access private - */ - var $_greeting = null; - - /** - * Stores detected features of the SMTP server. - * @var array - * @access private - */ - var $_esmtp = array(); - - /** - * 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 $pipeling Use SMTP command pipelining - * @param integer $timeout Socket I/O timeout in seconds. - * @param array $socket_options Socket stream_context_create() options. - * - * @access public - * @since 1.0 - */ - function Net_SMTP($host = null, $port = null, $localhost = null, - $pipelining = false, $timeout = 0, $socket_options = 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; - - /* 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') === true) { - $this->setAuthMethod('CRAM-MD5', array($this, '_authCram_MD5')); - $this->setAuthMethod('DIGEST-MD5', array($this, '_authDigest_MD5')); - } - - /* These standard authentication methods are always available. */ - $this->setAuthMethod('LOGIN', array($this, '_authLogin'), false); - $this->setAuthMethod('PLAIN', array($this, '_authPlain'), 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. - * - * @access public - * @since 1.5.0 - */ - 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. - * - * @access public - * @since 1.1.0 - */ - 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. - * - * @access private - * @since 1.3.3 - */ - 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. - * - * @access private - * @since 1.1.0 - */ - 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", - null, PEAR_ERROR_RETURN); - } - - 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. - * - * @access private - * @since 1.1.0 - */ - function _put($command, $args = '') - { - if (!empty($args)) { - $command .= ' ' . $args; - } - - if (strcspn($command, "\r\n") !== strlen($command)) { - return PEAR::raiseError('Commands cannot contain newlines', - null, PEAR_ERROR_RETURN); - } - - 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. - * - * @access private - * @since 1.1.0 - * - * @see getResponse - */ - 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', - null, PEAR_ERROR_RETURN); - } - - /* 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, PEAR_ERROR_RETURN); - } - - /** - * 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. - * - * @access public - * @since 1.6.0 - */ - 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. - * - * @access public - * @since 1.1.0 - */ - 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. - * - * @access public - * @since 1.3.3 - */ - 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. - * @access public - * @since 1.0 - */ - 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. - * - * @return mixed Returns a PEAR_Error with an error message on any - * kind of failure, or true on success. - * @access public - * @since 1.0 - */ - function disconnect() - { - if (PEAR::isError($error = $this->_put('QUIT'))) { - return $error; - } - if (PEAR::isError($error = $this->_parseResponse(221))) { - return $error; - } - if (PEAR::isError($error = $this->_socket->disconnect())) { - return PEAR::raiseError('Failed to disconnect socket: ' . - $error->getMessage()); - } - - 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. - * - * @access private - * @since 1.1.0 - */ - function _negotiate() - { - if (PEAR::isError($error = $this->_put('EHLO', $this->localhost))) { - return $error; - } - - if (PEAR::isError($this->_parseResponse(250))) { - /* If we receive a 503 response, we're already authenticated. */ - if ($this->_code === 503) { - return true; - } - - /* 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, - PEAR_ERROR_RETURN); - } - - return true; - } - - foreach ($this->_arguments as $argument) { - $verb = strtok($argument, ' '); - $arguments = substr($argument, strlen($verb) + 1, - strlen($argument) - strlen($verb) - 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. - * @access private - * @since 1.1.0 - */ - 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', - null, PEAR_ERROR_RETURN); - } - - /** - * Attempt to do SMTP authentication. - * - * @param string The userid to authenticate as. - * @param string The password to authenticate with. - * @param string The requested authentication method. If none is - * specified, the best supported method will be used. - * @param bool Flag indicating whether or not TLS should be attempted. - * @param string 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. - * @access public - * @since 1.0 - */ - 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 && 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 (PEAR::isError($result = $this->_socket->enableCrypto(true, STREAM_CRYPTO_METHOD_TLS_CLIENT))) { - return $result; - } elseif ($result !== true) { - return PEAR::raiseError('STARTTLS failed'); - } - - /* Send EHLO again to recieve the AUTH string from the - * SMTP server. */ - $this->_negotiate(); - } - - 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 The authentication method name (e.g. 'PLAIN') - * @param mixed The authentication callback (given as the name of a - * function or as an (object, method name) array). - * @param bool 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. - * - * @access public - * @since 1.6.0 - */ - 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 The userid to authenticate as. - * @param string The password to authenticate with. - * @param string The optional authorization proxy identifier. - * - * @return mixed Returns a PEAR_Error with an error message on any - * kind of failure, or true on success. - * @access private - * @since 1.1.0 - */ - function _authDigest_MD5($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; - } - - $challenge = base64_decode($this->_arguments[0]); - $digest = &Auth_SASL::factory('digestmd5'); - $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 The userid to authenticate as. - * @param string The password to authenticate with. - * @param string The optional authorization proxy identifier. - * - * @return mixed Returns a PEAR_Error with an error message on any - * kind of failure, or true on success. - * @access private - * @since 1.1.0 - */ - function _authCRAM_MD5($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; - } - - $challenge = base64_decode($this->_arguments[0]); - $cram = &Auth_SASL::factory('crammd5'); - $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 The userid to authenticate as. - * @param string The password to authenticate with. - * @param string The optional authorization proxy identifier. - * - * @return mixed Returns a PEAR_Error with an error message on any - * kind of failure, or true on success. - * @access private - * @since 1.1.0 - */ - 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 The userid to authenticate as. - * @param string The password to authenticate with. - * @param string The optional authorization proxy identifier. - * - * @return mixed Returns a PEAR_Error with an error message on any - * kind of failure, or true on success. - * @access private - * @since 1.1.0 - */ - 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; - } - - /** - * Send the HELO command. - * - * @param string 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. - * @access public - * @since 1.0 - */ - 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. - * @access public - * @since 1.3 - */ - 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. - * @access public - * @since 1.0 - */ - function mailFrom($sender, $params = null) - { - $args = "FROM:<$sender>"; - - /* Support the deprecated array form of $params. */ - if (is_array($params) && isset($params['verp'])) { - /* XVERP */ - if ($params['verp'] === true) { - $args .= ' XVERP'; - - /* XVERP=something */ - } 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. - * - * @access public - * @since 1.0 - */ - 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. - * - * @access public - * @since 1.2 - */ - function quotedata(&$data) - { - /* Change Unix (\n) and Mac (\r) linefeeds into - * Internet-standard CRLF (\r\n) linefeeds. */ - $data = preg_replace(array('/(?_esmtp['SIZE'])) ? $this->_esmtp['SIZE'] : 0; - if ($limit > 0 && $size >= $limit) { - $this->disconnect(); - 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; - } - } - - /* 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; - } - } - } 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; - } - } - - /* Finally, send the DATA terminator sequence. */ - if (PEAR::isError($result = $this->_send("\r\n.\r\n"))) { - 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 The reverse path to send. - * - * @return mixed Returns a PEAR_Error with an error message on any - * kind of failure, or true on success. - * @access public - * @since 1.2.6 - */ - 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; - } - - /** - * Backwards-compatibility wrapper for sendFrom(). - * - * @param string The reverse path to send. - * - * @return mixed Returns a PEAR_Error with an error message on any - * kind of failure, or true on success. - * - * @access public - * @since 1.0 - * @deprecated 1.2.6 - */ - function send_from($path) - { - return sendFrom($path); - } - - /** - * Send the SOML FROM: command. - * - * @param string The reverse path to send. - * - * @return mixed Returns a PEAR_Error with an error message on any - * kind of failure, or true on success. - * @access public - * @since 1.2.6 - */ - 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; - } - - /** - * Backwards-compatibility wrapper for somlFrom(). - * - * @param string The reverse path to send. - * - * @return mixed Returns a PEAR_Error with an error message on any - * kind of failure, or true on success. - * - * @access public - * @since 1.0 - * @deprecated 1.2.6 - */ - function soml_from($path) - { - return somlFrom($path); - } - - /** - * Send the SAML FROM: command. - * - * @param string The reverse path to send. - * - * @return mixed Returns a PEAR_Error with an error message on any - * kind of failure, or true on success. - * @access public - * @since 1.2.6 - */ - 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; - } - - /** - * Backwards-compatibility wrapper for samlFrom(). - * - * @param string The reverse path to send. - * - * @return mixed Returns a PEAR_Error with an error message on any - * kind of failure, or true on success. - * - * @access public - * @since 1.0 - * @deprecated 1.2.6 - */ - function saml_from($path) - { - return samlFrom($path); - } - - /** - * Send the RSET command. - * - * @return mixed Returns a PEAR_Error with an error message on any - * kind of failure, or true on success. - * @access public - * @since 1.0 - */ - 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 The string to verify - * - * @return mixed Returns a PEAR_Error with an error message on any - * kind of failure, or true on success. - * @access public - * @since 1.0 - */ - 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. - * @access public - * @since 1.0 - */ - 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. - * - * @access public - * @since 1.0 - */ - function identifySender() - { - return true; - } - -} diff --git a/lib/ext/Net/Socket.php b/lib/ext/Net/Socket.php deleted file mode 100644 index dd1047c..0000000 --- a/lib/ext/Net/Socket.php +++ /dev/null @@ -1,653 +0,0 @@ - - * Chuck Hagenbuch - * - * @category Net - * @package Net_Socket - * @author Stig Bakken - * @author Chuck Hagenbuch - * @copyright 1997-2003 The PHP Group - * @license http://www.php.net/license/2_02.txt PHP 2.02 - * @version CVS: $Id$ - * @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 - * @author Chuck Hagenbuch - * @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 connections before assuming - * there's no more data. Defaults to no timeout. - * @var integer $timeout - */ - var $timeout = false; - - /** - * 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. - * @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) How long to wait for data. - * @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'); - } elseif (strspn($addr, '.0123456789') == strlen($addr) || - strstr($addr, '/') !== false) { - $this->addr = $addr; - } else { - $this->addr = @gethostbyname($addr); - } - - $this->port = $port % 65536; - - if ($persistent !== null) { - $this->persistent = $persistent; - } - - if ($timeout !== null) { - $this->timeout = $timeout; - } - - $openfunc = $this->persistent ? 'pfsockopen' : 'fsockopen'; - $errno = 0; - $errstr = ''; - - $old_track_errors = @ini_set('track_errors', 1); - - if ($options && function_exists('stream_context_create')) { - if ($this->timeout) { - $timeout = $this->timeout; - } else { - $timeout = 0; - } - $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 { - if ($this->timeout) { - $fp = @$openfunc($this->addr, $this->port, $errno, - $errstr, $this->timeout); - } else { - $fp = @$openfunc($this->addr, $this->port, $errno, $errstr); - } - } - - 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; - - 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. - * - * @access public - * @return mixed true on success or a PEAR_Error instance otherwise - */ - function setTimeout($seconds, $microseconds) - { - if (!is_resource($this->fp)) { - return $this->raiseError('not connected'); - } - - return socket_set_timeout($this->fp, $seconds, $microseconds); - } - - /** - * 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: - * - *

    - * timed_out (bool) - The socket timed out waiting for data
    - * blocked (bool) - The socket was blocked
    - * eof (bool) - Indicates EOF event
    - * unread_bytes (int) - Number of bytes left in the socket buffer
    - *

    - * - * @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 socket_get_status($this->fp); - } - - /** - * Get a specified line of data - * - * @param int $size ?? - * - * @access public - * @return $size bytes of data from the socket, or a PEAR_Error if - * not connected. - */ - 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. - */ - function write($data, $blocksize = null) - { - if (!is_resource($this->fp)) { - return $this->raiseError('not connected'); - } - - if (is_null($blocksize) && !OS_WINDOWS) { - return @fwrite($this->fp, $data); - } else { - if (is_null($blocksize)) { - $blocksize = 1024; - } - - $pos = 0; - $size = strlen($data); - while ($pos < $size) { - $written = @fwrite($this->fp, substr($data, $pos, $blocksize)); - if (!$written) { - 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 fputs result, or an error - */ - 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); - } - } - -} diff --git a/lib/ext/Net/URL2.php b/lib/ext/Net/URL2.php deleted file mode 100644 index 9989404..0000000 --- a/lib/ext/Net/URL2.php +++ /dev/null @@ -1,942 +0,0 @@ - - * @copyright 2007-2009 Peytz & Co. A/S - * @license http://www.opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: URL2.php 309223 2011-03-14 14:26:32Z till $ - * @link http://www.rfc-editor.org/rfc/rfc3986.txt - */ - -/** - * Represents a URL as per RFC 3986. - * - * @category Networking - * @package Net_URL2 - * @author Christian Schmidt - * @copyright 2007-2009 Peytz & Co. A/S - * @license http://www.opensource.org/licenses/bsd-license.php New BSD License - * @version Release: @package_version@ - * @link http://pear.php.net/package/Net_URL2 - */ -class Net_URL2 -{ - /** - * Do strict parsing in resolve() (see RFC 3986, section 5.2.2). Default - * is true. - */ - const OPTION_STRICT = 'strict'; - - /** - * Represent arrays in query using PHP's [] notation. Default is true. - */ - const OPTION_USE_BRACKETS = 'use_brackets'; - - /** - * URL-encode query variable keys. Default is true. - */ - const OPTION_ENCODE_KEYS = 'encode_keys'; - - /** - * Query variable separators when parsing the query string. Every character - * is considered a separator. Default is "&". - */ - const OPTION_SEPARATOR_INPUT = 'input_separator'; - - /** - * Query variable separator used when generating the query string. Default - * is "&". - */ - const OPTION_SEPARATOR_OUTPUT = 'output_separator'; - - /** - * Default options corresponds to how PHP handles $_GET. - */ - private $_options = array( - self::OPTION_STRICT => true, - self::OPTION_USE_BRACKETS => true, - self::OPTION_ENCODE_KEYS => true, - self::OPTION_SEPARATOR_INPUT => '&', - self::OPTION_SEPARATOR_OUTPUT => '&', - ); - - /** - * @var string|bool - */ - private $_scheme = false; - - /** - * @var string|bool - */ - private $_userinfo = false; - - /** - * @var string|bool - */ - private $_host = false; - - /** - * @var string|bool - */ - private $_port = false; - - /** - * @var string - */ - private $_path = ''; - - /** - * @var string|bool - */ - private $_query = false; - - /** - * @var string|bool - */ - private $_fragment = false; - - /** - * Constructor. - * - * @param string $url an absolute or relative URL - * @param array $options an array of OPTION_xxx constants - * - * @return $this - * @uses self::parseUrl() - */ - public function __construct($url, array $options = array()) - { - foreach ($options as $optionName => $value) { - if (array_key_exists($optionName, $this->_options)) { - $this->_options[$optionName] = $value; - } - } - - $this->parseUrl($url); - } - - /** - * Magic Setter. - * - * This method will magically set the value of a private variable ($var) - * with the value passed as the args - * - * @param string $var The private variable to set. - * @param mixed $arg An argument of any type. - * @return void - */ - public function __set($var, $arg) - { - $method = 'set' . $var; - if (method_exists($this, $method)) { - $this->$method($arg); - } - } - - /** - * Magic Getter. - * - * This is the magic get method to retrieve the private variable - * that was set by either __set() or it's setter... - * - * @param string $var The property name to retrieve. - * @return mixed $this->$var Either a boolean false if the - * property is not set or the value - * of the private property. - */ - public function __get($var) - { - $method = 'get' . $var; - if (method_exists($this, $method)) { - return $this->$method(); - } - - return false; - } - - /** - * Returns the scheme, e.g. "http" or "urn", or false if there is no - * scheme specified, i.e. if this is a relative URL. - * - * @return string|bool - */ - public function getScheme() - { - return $this->_scheme; - } - - /** - * Sets the scheme, e.g. "http" or "urn". Specify false if there is no - * scheme specified, i.e. if this is a relative URL. - * - * @param string|bool $scheme e.g. "http" or "urn", or false if there is no - * scheme specified, i.e. if this is a relative - * URL - * - * @return $this - * @see getScheme() - */ - public function setScheme($scheme) - { - $this->_scheme = $scheme; - return $this; - } - - /** - * Returns the user part of the userinfo part (the part preceding the first - * ":"), or false if there is no userinfo part. - * - * @return string|bool - */ - public function getUser() - { - return $this->_userinfo !== false - ? preg_replace('@:.*$@', '', $this->_userinfo) - : false; - } - - /** - * Returns the password part of the userinfo part (the part after the first - * ":"), or false if there is no userinfo part (i.e. the URL does not - * contain "@" in front of the hostname) or the userinfo part does not - * contain ":". - * - * @return string|bool - */ - public function getPassword() - { - return $this->_userinfo !== false - ? substr(strstr($this->_userinfo, ':'), 1) - : false; - } - - /** - * Returns the userinfo part, or false if there is none, i.e. if the - * authority part does not contain "@". - * - * @return string|bool - */ - public function getUserinfo() - { - return $this->_userinfo; - } - - /** - * Sets the userinfo part. If two arguments are passed, they are combined - * in the userinfo part as username ":" password. - * - * @param string|bool $userinfo userinfo or username - * @param string|bool $password optional password, or false - * - * @return $this - */ - public function setUserinfo($userinfo, $password = false) - { - $this->_userinfo = $userinfo; - if ($password !== false) { - $this->_userinfo .= ':' . $password; - } - return $this; - } - - /** - * Returns the host part, or false if there is no authority part, e.g. - * relative URLs. - * - * @return string|bool a hostname, an IP address, or false - */ - public function getHost() - { - return $this->_host; - } - - /** - * Sets the host part. Specify false if there is no authority part, e.g. - * relative URLs. - * - * @param string|bool $host a hostname, an IP address, or false - * - * @return $this - */ - public function setHost($host) - { - $this->_host = $host; - return $this; - } - - /** - * Returns the port number, or false if there is no port number specified, - * i.e. if the default port is to be used. - * - * @return string|bool - */ - public function getPort() - { - return $this->_port; - } - - /** - * Sets the port number. Specify false if there is no port number specified, - * i.e. if the default port is to be used. - * - * @param string|bool $port a port number, or false - * - * @return $this - */ - public function setPort($port) - { - $this->_port = $port; - return $this; - } - - /** - * Returns the authority part, i.e. [ userinfo "@" ] host [ ":" port ], or - * false if there is no authority. - * - * @return string|bool - */ - public function getAuthority() - { - if (!$this->_host) { - return false; - } - - $authority = ''; - - if ($this->_userinfo !== false) { - $authority .= $this->_userinfo . '@'; - } - - $authority .= $this->_host; - - if ($this->_port !== false) { - $authority .= ':' . $this->_port; - } - - return $authority; - } - - /** - * Sets the authority part, i.e. [ userinfo "@" ] host [ ":" port ]. Specify - * false if there is no authority. - * - * @param string|false $authority a hostname or an IP addresse, possibly - * with userinfo prefixed and port number - * appended, e.g. "foo:bar@example.org:81". - * - * @return $this - */ - public function setAuthority($authority) - { - $this->_userinfo = false; - $this->_host = false; - $this->_port = false; - if (preg_match('@^(([^\@]*)\@)?([^:]+)(:(\d*))?$@', $authority, $reg)) { - if ($reg[1]) { - $this->_userinfo = $reg[2]; - } - - $this->_host = $reg[3]; - if (isset($reg[5])) { - $this->_port = $reg[5]; - } - } - return $this; - } - - /** - * Returns the path part (possibly an empty string). - * - * @return string - */ - public function getPath() - { - return $this->_path; - } - - /** - * Sets the path part (possibly an empty string). - * - * @param string $path a path - * - * @return $this - */ - public function setPath($path) - { - $this->_path = $path; - return $this; - } - - /** - * Returns the query string (excluding the leading "?"), or false if "?" - * is not present in the URL. - * - * @return string|bool - * @see self::getQueryVariables() - */ - public function getQuery() - { - return $this->_query; - } - - /** - * Sets the query string (excluding the leading "?"). Specify false if "?" - * is not present in the URL. - * - * @param string|bool $query a query string, e.g. "foo=1&bar=2" - * - * @return $this - * @see self::setQueryVariables() - */ - public function setQuery($query) - { - $this->_query = $query; - return $this; - } - - /** - * Returns the fragment name, or false if "#" is not present in the URL. - * - * @return string|bool - */ - public function getFragment() - { - return $this->_fragment; - } - - /** - * Sets the fragment name. Specify false if "#" is not present in the URL. - * - * @param string|bool $fragment a fragment excluding the leading "#", or - * false - * - * @return $this - */ - public function setFragment($fragment) - { - $this->_fragment = $fragment; - return $this; - } - - /** - * Returns the query string like an array as the variables would appear in - * $_GET in a PHP script. If the URL does not contain a "?", an empty array - * is returned. - * - * @return array - */ - public function getQueryVariables() - { - $pattern = '/[' . - preg_quote($this->getOption(self::OPTION_SEPARATOR_INPUT), '/') . - ']/'; - $parts = preg_split($pattern, $this->_query, -1, PREG_SPLIT_NO_EMPTY); - $return = array(); - - foreach ($parts as $part) { - if (strpos($part, '=') !== false) { - list($key, $value) = explode('=', $part, 2); - } else { - $key = $part; - $value = null; - } - - if ($this->getOption(self::OPTION_ENCODE_KEYS)) { - $key = rawurldecode($key); - } - $value = rawurldecode($value); - - if ($this->getOption(self::OPTION_USE_BRACKETS) && - preg_match('#^(.*)\[([0-9a-z_-]*)\]#i', $key, $matches)) { - - $key = $matches[1]; - $idx = $matches[2]; - - // Ensure is an array - if (empty($return[$key]) || !is_array($return[$key])) { - $return[$key] = array(); - } - - // Add data - if ($idx === '') { - $return[$key][] = $value; - } else { - $return[$key][$idx] = $value; - } - } elseif (!$this->getOption(self::OPTION_USE_BRACKETS) - && !empty($return[$key]) - ) { - $return[$key] = (array) $return[$key]; - $return[$key][] = $value; - } else { - $return[$key] = $value; - } - } - - return $return; - } - - /** - * Sets the query string to the specified variable in the query string. - * - * @param array $array (name => value) array - * - * @return $this - */ - public function setQueryVariables(array $array) - { - if (!$array) { - $this->_query = false; - } else { - $this->_query = $this->buildQuery( - $array, - $this->getOption(self::OPTION_SEPARATOR_OUTPUT) - ); - } - return $this; - } - - /** - * Sets the specified variable in the query string. - * - * @param string $name variable name - * @param mixed $value variable value - * - * @return $this - */ - public function setQueryVariable($name, $value) - { - $array = $this->getQueryVariables(); - $array[$name] = $value; - $this->setQueryVariables($array); - return $this; - } - - /** - * Removes the specifed variable from the query string. - * - * @param string $name a query string variable, e.g. "foo" in "?foo=1" - * - * @return void - */ - public function unsetQueryVariable($name) - { - $array = $this->getQueryVariables(); - unset($array[$name]); - $this->setQueryVariables($array); - } - - /** - * Returns a string representation of this URL. - * - * @return string - */ - public function getURL() - { - // See RFC 3986, section 5.3 - $url = ""; - - if ($this->_scheme !== false) { - $url .= $this->_scheme . ':'; - } - - $authority = $this->getAuthority(); - if ($authority !== false) { - $url .= '//' . $authority; - } - $url .= $this->_path; - - if ($this->_query !== false) { - $url .= '?' . $this->_query; - } - - if ($this->_fragment !== false) { - $url .= '#' . $this->_fragment; - } - - return $url; - } - - /** - * Returns a string representation of this URL. - * - * @return string - * @see toString() - */ - public function __toString() - { - return $this->getURL(); - } - - /** - * Returns a normalized string representation of this URL. This is useful - * for comparison of URLs. - * - * @return string - */ - public function getNormalizedURL() - { - $url = clone $this; - $url->normalize(); - return $url->getUrl(); - } - - /** - * Returns a normalized Net_URL2 instance. - * - * @return Net_URL2 - */ - public function normalize() - { - // See RFC 3886, section 6 - - // Schemes are case-insensitive - if ($this->_scheme) { - $this->_scheme = strtolower($this->_scheme); - } - - // Hostnames are case-insensitive - if ($this->_host) { - $this->_host = strtolower($this->_host); - } - - // Remove default port number for known schemes (RFC 3986, section 6.2.3) - if ($this->_port && - $this->_scheme && - $this->_port == getservbyname($this->_scheme, 'tcp')) { - - $this->_port = false; - } - - // Normalize case of %XX percentage-encodings (RFC 3986, section 6.2.2.1) - foreach (array('_userinfo', '_host', '_path') as $part) { - if ($this->$part) { - $this->$part = preg_replace('/%[0-9a-f]{2}/ie', - 'strtoupper("\0")', - $this->$part); - } - } - - // Path segment normalization (RFC 3986, section 6.2.2.3) - $this->_path = self::removeDotSegments($this->_path); - - // Scheme based normalization (RFC 3986, section 6.2.3) - if ($this->_host && !$this->_path) { - $this->_path = '/'; - } - } - - /** - * Returns whether this instance represents an absolute URL. - * - * @return bool - */ - public function isAbsolute() - { - return (bool) $this->_scheme; - } - - /** - * Returns an Net_URL2 instance representing an absolute URL relative to - * this URL. - * - * @param Net_URL2|string $reference relative URL - * - * @return Net_URL2 - */ - public function resolve($reference) - { - if (!$reference instanceof Net_URL2) { - $reference = new self($reference); - } - if (!$this->isAbsolute()) { - throw new Exception('Base-URL must be absolute'); - } - - // A non-strict parser may ignore a scheme in the reference if it is - // identical to the base URI's scheme. - if (!$this->getOption(self::OPTION_STRICT) && $reference->_scheme == $this->_scheme) { - $reference->_scheme = false; - } - - $target = new self(''); - if ($reference->_scheme !== false) { - $target->_scheme = $reference->_scheme; - $target->setAuthority($reference->getAuthority()); - $target->_path = self::removeDotSegments($reference->_path); - $target->_query = $reference->_query; - } else { - $authority = $reference->getAuthority(); - if ($authority !== false) { - $target->setAuthority($authority); - $target->_path = self::removeDotSegments($reference->_path); - $target->_query = $reference->_query; - } else { - if ($reference->_path == '') { - $target->_path = $this->_path; - if ($reference->_query !== false) { - $target->_query = $reference->_query; - } else { - $target->_query = $this->_query; - } - } else { - if (substr($reference->_path, 0, 1) == '/') { - $target->_path = self::removeDotSegments($reference->_path); - } else { - // Merge paths (RFC 3986, section 5.2.3) - if ($this->_host !== false && $this->_path == '') { - $target->_path = '/' . $this->_path; - } else { - $i = strrpos($this->_path, '/'); - if ($i !== false) { - $target->_path = substr($this->_path, 0, $i + 1); - } - $target->_path .= $reference->_path; - } - $target->_path = self::removeDotSegments($target->_path); - } - $target->_query = $reference->_query; - } - $target->setAuthority($this->getAuthority()); - } - $target->_scheme = $this->_scheme; - } - - $target->_fragment = $reference->_fragment; - - return $target; - } - - /** - * Removes dots as described in RFC 3986, section 5.2.4, e.g. - * "/foo/../bar/baz" => "/bar/baz" - * - * @param string $path a path - * - * @return string a path - */ - public static function removeDotSegments($path) - { - $output = ''; - - // Make sure not to be trapped in an infinite loop due to a bug in this - // method - $j = 0; - while ($path && $j++ < 100) { - if (substr($path, 0, 2) == './') { - // Step 2.A - $path = substr($path, 2); - } elseif (substr($path, 0, 3) == '../') { - // Step 2.A - $path = substr($path, 3); - } elseif (substr($path, 0, 3) == '/./' || $path == '/.') { - // Step 2.B - $path = '/' . substr($path, 3); - } elseif (substr($path, 0, 4) == '/../' || $path == '/..') { - // Step 2.C - $path = '/' . substr($path, 4); - $i = strrpos($output, '/'); - $output = $i === false ? '' : substr($output, 0, $i); - } elseif ($path == '.' || $path == '..') { - // Step 2.D - $path = ''; - } else { - // Step 2.E - $i = strpos($path, '/'); - if ($i === 0) { - $i = strpos($path, '/', 1); - } - if ($i === false) { - $i = strlen($path); - } - $output .= substr($path, 0, $i); - $path = substr($path, $i); - } - } - - return $output; - } - - /** - * Percent-encodes all non-alphanumeric characters except these: _ . - ~ - * Similar to PHP's rawurlencode(), except that it also encodes ~ in PHP - * 5.2.x and earlier. - * - * @param $raw the string to encode - * @return string - */ - public static function urlencode($string) - { - $encoded = rawurlencode($string); - - // This is only necessary in PHP < 5.3. - $encoded = str_replace('%7E', '~', $encoded); - return $encoded; - } - - /** - * Returns a Net_URL2 instance representing the canonical URL of the - * currently executing PHP script. - * - * @return string - */ - public static function getCanonical() - { - if (!isset($_SERVER['REQUEST_METHOD'])) { - // ALERT - no current URL - throw new Exception('Script was not called through a webserver'); - } - - // Begin with a relative URL - $url = new self($_SERVER['PHP_SELF']); - $url->_scheme = isset($_SERVER['HTTPS']) ? 'https' : 'http'; - $url->_host = $_SERVER['SERVER_NAME']; - $port = $_SERVER['SERVER_PORT']; - if ($url->_scheme == 'http' && $port != 80 || - $url->_scheme == 'https' && $port != 443) { - - $url->_port = $port; - } - return $url; - } - - /** - * Returns the URL used to retrieve the current request. - * - * @return string - */ - public static function getRequestedURL() - { - return self::getRequested()->getUrl(); - } - - /** - * Returns a Net_URL2 instance representing the URL used to retrieve the - * current request. - * - * @return Net_URL2 - */ - public static function getRequested() - { - if (!isset($_SERVER['REQUEST_METHOD'])) { - // ALERT - no current URL - throw new Exception('Script was not called through a webserver'); - } - - // Begin with a relative URL - $url = new self($_SERVER['REQUEST_URI']); - $url->_scheme = isset($_SERVER['HTTPS']) ? 'https' : 'http'; - // Set host and possibly port - $url->setAuthority($_SERVER['HTTP_HOST']); - return $url; - } - - /** - * Returns the value of the specified option. - * - * @param string $optionName The name of the option to retrieve - * - * @return mixed - */ - public function getOption($optionName) - { - return isset($this->_options[$optionName]) - ? $this->_options[$optionName] : false; - } - - /** - * A simple version of http_build_query in userland. The encoded string is - * percentage encoded according to RFC 3986. - * - * @param array $data An array, which has to be converted into - * QUERY_STRING. Anything is possible. - * @param string $seperator See {@link self::OPTION_SEPARATOR_OUTPUT} - * @param string $key For stacked values (arrays in an array). - * - * @return string - */ - protected function buildQuery(array $data, $separator, $key = null) - { - $query = array(); - foreach ($data as $name => $value) { - if ($this->getOption(self::OPTION_ENCODE_KEYS) === true) { - $name = rawurlencode($name); - } - if ($key !== null) { - if ($this->getOption(self::OPTION_USE_BRACKETS) === true) { - $name = $key . '[' . $name . ']'; - } else { - $name = $key; - } - } - if (is_array($value)) { - $query[] = $this->buildQuery($value, $separator, $name); - } else { - $query[] = $name . '=' . rawurlencode($value); - } - } - return implode($separator, $query); - } - - /** - * This method uses a funky regex to parse the url into the designated parts. - * - * @param string $url - * - * @return void - * @uses self::$_scheme, self::setAuthority(), self::$_path, self::$_query, - * self::$_fragment - * @see self::__construct() - */ - protected function parseUrl($url) - { - // The regular expression is copied verbatim from RFC 3986, appendix B. - // The expression does not validate the URL but matches any string. - preg_match('!^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?!', - $url, - $matches); - - // "path" is always present (possibly as an empty string); the rest - // are optional. - $this->_scheme = !empty($matches[1]) ? $matches[2] : false; - $this->setAuthority(!empty($matches[3]) ? $matches[4] : false); - $this->_path = $matches[5]; - $this->_query = !empty($matches[6]) ? $matches[7] : false; - $this->_fragment = !empty($matches[8]) ? $matches[9] : false; - } -} diff --git a/lib/ext/PEAR.php b/lib/ext/PEAR.php deleted file mode 100644 index f4dfd96..0000000 --- a/lib/ext/PEAR.php +++ /dev/null @@ -1,1137 +0,0 @@ - - * @author Stig Bakken - * @author Tomas V.V.Cox - * @author Greg Beaver - * @copyright 1997-2009 The Authors - * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id$ - * @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); -/**#@-*/ -define('PEAR_ZE2', (function_exists('version_compare') && - version_compare(zend_version(), "2-dev", "ge"))); - -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 - * @author Tomas V.V. Cox - * @author Greg Beaver - * @copyright 1997-2006 The PHP Group - * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.0 - * @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 -{ - // {{{ properties - - /** - * 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(); - - // }}} - - // {{{ constructor - - /** - * 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 PEAR($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); - } - } - } - - // }}} - // {{{ destructor - - /** - * 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))); - } - } - - // }}} - // {{{ getStaticProperty() - - /** - * 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! - * - * @access public - * @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. - */ - 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]; - } - - // }}} - // {{{ registerShutdownFunc() - - /** - * Use this function to register a shutdown method for static - * classes. - * - * @access public - * @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 - */ - 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); - } - - // }}} - // {{{ isError() - - /** - * 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 - * @access public - * @return bool true if parameter is an error - */ - static function isError($data, $code = null) - { - if (!is_object($data) || !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; - } - - // }}} - // {{{ setErrorHandling() - - /** - * 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 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 - */ - - function setErrorHandling($mode = null, $options = null) - { - if (isset($this) && is_a($this, 'PEAR')) { - $setmode = &$this->_default_error_mode; - $setoptions = &$this->_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; - } - } - - // }}} - // {{{ expectError() - - /** - * 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 sizeof($this->_expected_errors); - } - - // }}} - // {{{ popExpect() - - /** - * 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); - } - - // }}} - // {{{ _checkDelExpect() - - /** - * 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; - } - - // }}} - // {{{ delExpect() - - /** - * This method deletes all occurences 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) { - if ($this->_checkDelExpect($error)) { - $deleted = true; - } else { - $deleted = 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; - } else { - 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 - } - - // }}} - // {{{ raiseError() - - /** - * 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. - * - * @access public - * @return object a PEAR error object - * @see PEAR::setErrorHandling - * @since PHP 4.0.5 - */ - function &raiseError($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 (isset($this) && isset($this->_expected_errors) && sizeof($this->_expected_errors) > 0 && sizeof($exp = end($this->_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 (isset($this) && isset($this->_default_error_mode)) { - $mode = $this->_default_error_mode; - $options = $this->_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 (isset($this) && isset($this->_error_class)) { - $ec = $this->_error_class; - } else { - $ec = 'PEAR_Error'; - } - - if (intval(PHP_VERSION) < 5) { - // little non-eval hack to fix bug #12147 - include 'PEAR/FixPHP5PEARWarnings.php'; - return $a; - } - - if ($skipmsg) { - $a = new $ec($code, $mode, $options, $userinfo); - } else { - $a = new $ec($message, $code, $mode, $options, $userinfo); - } - - return $a; - } - - // }}} - // {{{ throwError() - - /** - * Simpler form of raiseError with fewer options. In most cases - * message, code and userinfo are enough. - * - * @param string $message - * - */ - function &throwError($message = null, - $code = null, - $userinfo = null) - { - if (isset($this) && is_a($this, 'PEAR')) { - $a = &$this->raiseError($message, $code, null, null, $userinfo); - return $a; - } - - $a = &PEAR::raiseError($message, $code, null, null, $userinfo); - return $a; - } - - // }}} - 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; - } - - 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; - } - - // {{{ pushErrorHandling() - - /** - * 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 - */ - function pushErrorHandling($mode, $options = null) - { - $stack = &$GLOBALS['_PEAR_error_handler_stack']; - if (isset($this) && is_a($this, 'PEAR')) { - $def_mode = &$this->_default_error_mode; - $def_options = &$this->_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 (isset($this) && is_a($this, 'PEAR')) { - $this->setErrorHandling($mode, $options); - } else { - PEAR::setErrorHandling($mode, $options); - } - $stack[] = array($mode, $options); - return true; - } - - // }}} - // {{{ popErrorHandling() - - /** - * Pop the last error handler used - * - * @return bool Always true - * - * @see PEAR::pushErrorHandling - */ - function popErrorHandling() - { - $stack = &$GLOBALS['_PEAR_error_handler_stack']; - array_pop($stack); - list($mode, $options) = $stack[sizeof($stack) - 1]; - array_pop($stack); - if (isset($this) && is_a($this, 'PEAR')) { - $this->setErrorHandling($mode, $options); - } else { - PEAR::setErrorHandling($mode, $options); - } - return true; - } - - // }}} - // {{{ loadExtension() - - /** - * OS independant 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 - */ - function loadExtension($ext) - { - if (!extension_loaded($ext)) { - // if either returns true dl() will produce a FATAL error, stop that - if ((ini_get('enable_dl') != 1) || (ini_get('safe_mode') == 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); - } - - return true; - } - - // }}} -} - -if (PEAR_ZE2) { - include_once 'PEAR5.php'; -} - -// {{{ _PEAR_call_destructors() - -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); - if (PEAR_ZE2) { - $destructLifoExists = PEAR5::getStaticProperty('PEAR', 'destructlifo'); - } else { - $destructLifoExists = PEAR::getStaticProperty('PEAR', 'destructlifo'); - } - - if ($destructLifoExists) { - $_PEAR_destructor_object_list = array_reverse($_PEAR_destructor_object_list); - } - - while (list($k, $objref) = each($_PEAR_destructor_object_list)) { - $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']) AND is_array($GLOBALS['_PEAR_shutdown_funcs']) AND !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 - * @author Tomas V.V. Cox - * @author Gregory Beaver - * @copyright 1997-2006 The PHP Group - * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.0 - * @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 -{ - // {{{ properties - - var $error_message_prefix = ''; - var $mode = PEAR_ERROR_RETURN; - var $level = E_USER_NOTICE; - var $code = -1; - var $message = ''; - var $userinfo = ''; - var $backtrace = null; - - // }}} - // {{{ constructor - - /** - * 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 PEAR_Error($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; - - if (PEAR_ZE2) { - $skiptrace = PEAR5::getStaticProperty('PEAR_Error', 'skiptrace'); - } else { - $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; - } - die(sprintf($format, $msg)); - } - - if ($this->mode & PEAR_ERROR_CALLBACK) { - if (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);'); - } - } - - // }}} - // {{{ getMode() - - /** - * Get the error mode from an error object. - * - * @return int error mode - * @access public - */ - function getMode() { - return $this->mode; - } - - // }}} - // {{{ getCallback() - - /** - * Get the callback function/method from an error object. - * - * @return mixed callback function or object/method array - * @access public - */ - function getCallback() { - return $this->callback; - } - - // }}} - // {{{ getMessage() - - - /** - * Get the error message from an error object. - * - * @return string full error message - * @access public - */ - function getMessage() - { - return ($this->error_message_prefix . $this->message); - } - - - // }}} - // {{{ getCode() - - /** - * Get error code from an error object - * - * @return int error code - * @access public - */ - function getCode() - { - return $this->code; - } - - // }}} - // {{{ getType() - - /** - * Get the name of this error/exception. - * - * @return string error/exception name (type) - * @access public - */ - function getType() - { - return get_class($this); - } - - // }}} - // {{{ getUserInfo() - - /** - * Get additional user-supplied information. - * - * @return string user-supplied information - * @access public - */ - function getUserInfo() - { - return $this->userinfo; - } - - // }}} - // {{{ getDebugInfo() - - /** - * Get additional debug information supplied by the application. - * - * @return string debug information - * @access public - */ - function getDebugInfo() - { - return $this->getUserInfo(); - } - - // }}} - // {{{ getBacktrace() - - /** - * 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]; - } - - // }}} - // {{{ addUserInfo() - - function addUserInfo($info) - { - if (empty($this->userinfo)) { - $this->userinfo = $info; - } else { - $this->userinfo .= " ** $info"; - } - } - - // }}} - // {{{ toString() - function __toString() - { - return $this->getMessage(); - } - // }}} - // {{{ toString() - - /** - * 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: - */ diff --git a/lib/ext/PEAR5.php b/lib/ext/PEAR5.php deleted file mode 100644 index 4286067..0000000 --- a/lib/ext/PEAR5.php +++ /dev/null @@ -1,33 +0,0 @@ - | - | Author: Aleksander Machniak | - +-----------------------------------------------------------------------+ -*/ - - -/** - * Roundcube Framework Initialization - * - * @package Framework - * @subpackage Core - */ - -$config = array( - 'error_reporting' => E_ALL & ~E_NOTICE & ~E_STRICT, - // Some users are not using Installer, so we'll check some - // critical PHP settings here. Only these, which doesn't provide - // an error/warning in the logs later. See (#1486307). - 'mbstring.func_overload' => 0, - 'magic_quotes_runtime' => false, - 'magic_quotes_sybase' => false, // #1488506 -); - -// check these additional ini settings if not called via CLI -if (php_sapi_name() != 'cli') { - $config += array( - 'suhosin.session.encrypt' => false, - 'file_uploads' => true, - ); -} - -foreach ($config as $optname => $optval) { - $ini_optval = filter_var(ini_get($optname), is_bool($optval) ? FILTER_VALIDATE_BOOLEAN : FILTER_VALIDATE_INT); - if ($optval != $ini_optval && @ini_set($optname, $optval) === false) { - $error = "ERROR: Wrong '$optname' option value and it wasn't possible to set it to required value ($optval).\n" - . "Check your PHP configuration (including php_admin_flag)."; - if (defined('STDERR')) fwrite(STDERR, $error); else echo $error; - exit(1); - } -} - -// framework constants -define('RCUBE_VERSION', '1.0-git'); -define('RCUBE_CHARSET', 'UTF-8'); - -if (!defined('RCUBE_LIB_DIR')) { - define('RCUBE_LIB_DIR', dirname(__FILE__).DIRECTORY_SEPARATOR); -} - -if (!defined('RCUBE_INSTALL_PATH')) { - define('RCUBE_INSTALL_PATH', RCUBE_LIB_DIR); -} - -if (!defined('RCUBE_CONFIG_DIR')) { - define('RCUBE_CONFIG_DIR', RCUBE_INSTALL_PATH . 'config/'); -} - -if (!defined('RCUBE_PLUGINS_DIR')) { - define('RCUBE_PLUGINS_DIR', RCUBE_INSTALL_PATH . 'plugins/'); -} - -if (!defined('RCUBE_LOCALIZATION_DIR')) { - define('RCUBE_LOCALIZATION_DIR', RCUBE_INSTALL_PATH . 'localization/'); -} - -// set internal encoding for mbstring extension -if (extension_loaded('mbstring')) { - mb_internal_encoding(RCUBE_CHARSET); - @mb_regex_encoding(RCUBE_CHARSET); -} - -// make sure the Roundcube lib directory is in the include_path -$rcube_path = realpath(RCUBE_LIB_DIR . '..'); -$sep = PATH_SEPARATOR; -$regexp = "!(^|$sep)" . preg_quote($rcube_path, '!') . "($sep|\$)!"; -$path = ini_get('include_path'); - -if (!preg_match($regexp, $path)) { - set_include_path($path . PATH_SEPARATOR . $rcube_path); -} - -// Register autoloader -spl_autoload_register('rcube_autoload'); - -// set PEAR error handling (will also load the PEAR main class) -PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, 'rcube_pear_error'); - - - -/** - * Similar function as in_array() but case-insensitive - * - * @param string $needle Needle value - * @param array $heystack Array to search in - * - * @return boolean True if found, False if not - */ -function in_array_nocase($needle, $haystack) -{ - $needle = mb_strtolower($needle); - foreach ((array)$haystack as $value) { - if ($needle === mb_strtolower($value)) { - return true; - } - } - - return false; -} - - -/** - * Parse a human readable string for a number of bytes. - * - * @param string $str Input string - * - * @return float Number of bytes - */ -function parse_bytes($str) -{ - if (is_numeric($str)) { - return floatval($str); - } - - if (preg_match('/([0-9\.]+)\s*([a-z]*)/i', $str, $regs)) { - $bytes = floatval($regs[1]); - switch (strtolower($regs[2])) { - case 'g': - case 'gb': - $bytes *= 1073741824; - break; - case 'm': - case 'mb': - $bytes *= 1048576; - break; - case 'k': - case 'kb': - $bytes *= 1024; - break; - } - } - - return floatval($bytes); -} - - -/** - * Make sure the string ends with a slash - */ -function slashify($str) -{ - return unslashify($str).'/'; -} - - -/** - * Remove slashes at the end of the string - */ -function unslashify($str) -{ - return preg_replace('/\/+$/', '', $str); -} - - -/** - * Returns number of seconds for a specified offset string. - * - * @param string $str String representation of the offset (e.g. 20min, 5h, 2days, 1week) - * - * @return int Number of seconds - */ -function get_offset_sec($str) -{ - if (preg_match('/^([0-9]+)\s*([smhdw])/i', $str, $regs)) { - $amount = (int) $regs[1]; - $unit = strtolower($regs[2]); - } - else { - $amount = (int) $str; - $unit = 's'; - } - - switch ($unit) { - case 'w': - $amount *= 7; - case 'd': - $amount *= 24; - case 'h': - $amount *= 60; - case 'm': - $amount *= 60; - } - - return $amount; -} - - -/** - * Create a unix timestamp with a specified offset from now. - * - * @param string $offset_str String representation of the offset (e.g. 20min, 5h, 2days) - * @param int $factor Factor to multiply with the offset - * - * @return int Unix timestamp - */ -function get_offset_time($offset_str, $factor=1) -{ - return time() + get_offset_sec($offset_str) * $factor; -} - - -/** - * Truncate string if it is longer than the allowed length. - * Replace the middle or the ending part of a string with a placeholder. - * - * @param string $str Input string - * @param int $maxlength Max. length - * @param string $placeholder Replace removed chars with this - * @param bool $ending Set to True if string should be truncated from the end - * - * @return string Abbreviated string - */ -function abbreviate_string($str, $maxlength, $placeholder='...', $ending=false) -{ - $length = mb_strlen($str); - - if ($length > $maxlength) { - if ($ending) { - return mb_substr($str, 0, $maxlength) . $placeholder; - } - - $placeholder_length = mb_strlen($placeholder); - $first_part_length = floor(($maxlength - $placeholder_length)/2); - $second_starting_location = $length - $maxlength + $first_part_length + $placeholder_length; - - $str = mb_substr($str, 0, $first_part_length) . $placeholder . mb_substr($str, $second_starting_location); - } - - return $str; -} - - -/** - * Get all keys from array (recursive). - * - * @param array $array Input array - * - * @return array List of array keys - */ -function array_keys_recursive($array) -{ - $keys = array(); - - if (!empty($array) && is_array($array)) { - foreach ($array as $key => $child) { - $keys[] = $key; - foreach (array_keys_recursive($child) as $val) { - $keys[] = $val; - } - } - } - - return $keys; -} - - -/** - * Remove all non-ascii and non-word chars except ., -, _ - */ -function asciiwords($str, $css_id = false, $replace_with = '') -{ - $allowed = 'a-z0-9\_\-' . (!$css_id ? '\.' : ''); - return preg_replace("/[^$allowed]/i", $replace_with, $str); -} - - -/** - * Check if a string contains only ascii characters - * - * @param string $str String to check - * @param bool $control_chars Includes control characters - * - * @return bool - */ -function is_ascii($str, $control_chars = true) -{ - $regexp = $control_chars ? '/[^\x00-\x7F]/' : '/[^\x20-\x7E]/'; - return preg_match($regexp, $str) ? false : true; -} - - -/** - * Compose a valid representation of name and e-mail address - * - * @param string $email E-mail address - * @param string $name Person name - * - * @return string Formatted string - */ -function format_email_recipient($email, $name = '') -{ - $email = trim($email); - - if ($name && $name != $email) { - // Special chars as defined by RFC 822 need to in quoted string (or escaped). - if (preg_match('/[\(\)\<\>\\\.\[\]@,;:"]/', $name)) { - $name = '"'.addcslashes($name, '"').'"'; - } - - return "$name <$email>"; - } - - return $email; -} - - -/** - * Format e-mail address - * - * @param string $email E-mail address - * - * @return string Formatted e-mail address - */ -function format_email($email) -{ - $email = trim($email); - $parts = explode('@', $email); - $count = count($parts); - - if ($count > 1) { - $parts[$count-1] = mb_strtolower($parts[$count-1]); - - $email = implode('@', $parts); - } - - return $email; -} - - -/** - * Fix version number so it can be used correctly in version_compare() - * - * @param string $version Version number string - * - * @param return Version number string - */ -function version_parse($version) -{ - return str_replace( - array('-stable', '-git'), - array('.0', '.99'), - $version); -} - - -/** - * mbstring replacement functions - */ -if (!extension_loaded('mbstring')) -{ - function mb_strlen($str) - { - return strlen($str); - } - - function mb_strtolower($str) - { - return strtolower($str); - } - - function mb_strtoupper($str) - { - return strtoupper($str); - } - - function mb_substr($str, $start, $len=null) - { - return substr($str, $start, $len); - } - - function mb_strpos($haystack, $needle, $offset=0) - { - return strpos($haystack, $needle, $offset); - } - - function mb_strrpos($haystack, $needle, $offset=0) - { - return strrpos($haystack, $needle, $offset); - } -} - -/** - * intl replacement functions - */ - -if (!function_exists('idn_to_utf8')) -{ - function idn_to_utf8($domain, $flags=null) - { - static $idn, $loaded; - - if (!$loaded) { - $idn = new Net_IDNA2(); - $loaded = true; - } - - if ($idn && $domain && preg_match('/(^|\.)xn--/i', $domain)) { - try { - $domain = $idn->decode($domain); - } - catch (Exception $e) { - } - } - return $domain; - } -} - -if (!function_exists('idn_to_ascii')) -{ - function idn_to_ascii($domain, $flags=null) - { - static $idn, $loaded; - - if (!$loaded) { - $idn = new Net_IDNA2(); - $loaded = true; - } - - if ($idn && $domain && preg_match('/[^\x20-\x7E]/', $domain)) { - try { - $domain = $idn->encode($domain); - } - catch (Exception $e) { - } - } - return $domain; - } -} - -/** - * Use PHP5 autoload for dynamic class loading - * - * @todo Make Zend, PEAR etc play with this - * @todo Make our classes conform to a more straight forward CS. - */ -function rcube_autoload($classname) -{ - $filename = preg_replace( - array( - '/Mail_(.+)/', - '/Net_(.+)/', - '/Auth_(.+)/', - '/^html_.+/', - '/^rcube(.*)/', - '/^utf8$/', - ), - array( - 'Mail/\\1', - 'Net/\\1', - 'Auth/\\1', - 'Roundcube/html', - 'Roundcube/rcube\\1', - 'utf8.class', - ), - $classname - ); - - if ($fp = @fopen("$filename.php", 'r', true)) { - fclose($fp); - include_once "$filename.php"; - return true; - } - - return false; -} - -/** - * Local callback function for PEAR errors - */ -function rcube_pear_error($err) -{ - error_log(sprintf("%s (%s): %s", - $err->getMessage(), - $err->getCode(), - $err->getUserinfo()), 0); -} diff --git a/lib/ext/Roundcube/html.php b/lib/ext/Roundcube/html.php deleted file mode 100644 index f6f744c..0000000 --- a/lib/ext/Roundcube/html.php +++ /dev/null @@ -1,895 +0,0 @@ - | - +-----------------------------------------------------------------------+ -*/ - - -/** - * Class for HTML code creation - * - * @package Framework - * @subpackage View - */ -class html -{ - protected $tagname; - protected $attrib = array(); - protected $allowed = array(); - protected $content; - - public static $doctype = 'xhtml'; - public static $lc_tags = true; - public static $common_attrib = array('id','class','style','title','align','unselectable'); - public static $containers = array('iframe','div','span','p','h1','h2','h3','ul','form','textarea','table','thead','tbody','tr','th','td','style','script'); - - - /** - * Constructor - * - * @param array $attrib Hash array with tag attributes - */ - public function __construct($attrib = array()) - { - if (is_array($attrib)) { - $this->attrib = $attrib; - } - } - - /** - * Return the tag code - * - * @return string The finally composed HTML tag - */ - public function show() - { - return self::tag($this->tagname, $this->attrib, $this->content, array_merge(self::$common_attrib, $this->allowed)); - } - - /****** STATIC METHODS *******/ - - /** - * Generic method to create a HTML tag - * - * @param string $tagname Tag name - * @param array $attrib Tag attributes as key/value pairs - * @param string $content Optinal Tag content (creates a container tag) - * @param array $allowed_attrib List with allowed attributes, omit to allow all - * @return string The XHTML tag - */ - public static function tag($tagname, $attrib = array(), $content = null, $allowed_attrib = null) - { - if (is_string($attrib)) - $attrib = array('class' => $attrib); - - $inline_tags = array('a','span','img'); - $suffix = $attrib['nl'] || ($content && $attrib['nl'] !== false && !in_array($tagname, $inline_tags)) ? "\n" : ''; - - $tagname = self::$lc_tags ? strtolower($tagname) : $tagname; - if (isset($content) || in_array($tagname, self::$containers)) { - $suffix = $attrib['noclose'] ? $suffix : '' . $suffix; - unset($attrib['noclose'], $attrib['nl']); - return '<' . $tagname . self::attrib_string($attrib, $allowed_attrib) . '>' . $content . $suffix; - } - else { - return '<' . $tagname . self::attrib_string($attrib, $allowed_attrib) . '>' . $suffix; - } - } - - /** - * - */ - public static function doctype($type) - { - $doctypes = array( - 'html5' => '', - 'xhtml' => '', - 'xhtml-trans' => '', - 'xhtml-strict' => '', - ); - - if ($doctypes[$type]) { - self::$doctype = preg_replace('/-\w+$/', '', $type); - return $doctypes[$type]; - } - - return ''; - } - - /** - * Derrived method for
    containers - * - * @param mixed $attr Hash array with tag attributes or string with class name - * @param string $cont Div content - * @return string HTML code - * @see html::tag() - */ - public static function div($attr = null, $cont = null) - { - if (is_string($attr)) { - $attr = array('class' => $attr); - } - return self::tag('div', $attr, $cont, array_merge(self::$common_attrib, array('onclick'))); - } - - /** - * Derrived method for

    blocks - * - * @param mixed $attr Hash array with tag attributes or string with class name - * @param string $cont Paragraph content - * @return string HTML code - * @see html::tag() - */ - public static function p($attr = null, $cont = null) - { - if (is_string($attr)) { - $attr = array('class' => $attr); - } - return self::tag('p', $attr, $cont, self::$common_attrib); - } - - /** - * Derrived method to create - * - * @param mixed $attr Hash array with tag attributes or string with image source (src) - * @return string HTML code - * @see html::tag() - */ - public static function img($attr = null) - { - if (is_string($attr)) { - $attr = array('src' => $attr); - } - return self::tag('img', $attr + array('alt' => ''), null, array_merge(self::$common_attrib, - array('src','alt','width','height','border','usemap','onclick'))); - } - - /** - * Derrived method for link tags - * - * @param mixed $attr Hash array with tag attributes or string with link location (href) - * @param string $cont Link content - * @return string HTML code - * @see html::tag() - */ - public static function a($attr, $cont) - { - if (is_string($attr)) { - $attr = array('href' => $attr); - } - return self::tag('a', $attr, $cont, array_merge(self::$common_attrib, - array('href','target','name','rel','onclick','onmouseover','onmouseout','onmousedown','onmouseup'))); - } - - /** - * Derrived method for inline span tags - * - * @param mixed $attr Hash array with tag attributes or string with class name - * @param string $cont Tag content - * @return string HTML code - * @see html::tag() - */ - public static function span($attr, $cont) - { - if (is_string($attr)) { - $attr = array('class' => $attr); - } - return self::tag('span', $attr, $cont, self::$common_attrib); - } - - /** - * Derrived method for form element labels - * - * @param mixed $attr Hash array with tag attributes or string with 'for' attrib - * @param string $cont Tag content - * @return string HTML code - * @see html::tag() - */ - public static function label($attr, $cont) - { - if (is_string($attr)) { - $attr = array('for' => $attr); - } - return self::tag('label', $attr, $cont, array_merge(self::$common_attrib, array('for'))); - } - - /** - * Derrived method to create - * - * @param mixed $attr Hash array with tag attributes or string with frame source (src) - * @return string HTML code - * @see html::tag() - */ - public static function iframe($attr = null, $cont = null) - { - if (is_string($attr)) { - $attr = array('src' => $attr); - } - return self::tag('iframe', $attr, $cont, array_merge(self::$common_attrib, - array('src','name','width','height','border','frameborder','onload'))); - } - - /** - * Derrived method to create