diff --git a/CONTRIBUTORS b/CONTRIBUTORS new file mode 100644 index 0000000..21a531f --- /dev/null +++ b/CONTRIBUTORS @@ -0,0 +1,79 @@ + +# NET-HTTP CONTRIBUTORS # + +This is the (likely incomplete) list of people who have helped +make this distribution what it is, either via code contributions, +patches, bug reports, help with troubleshooting, etc. A huge +'thank you' to all of them. + + * Adam Kennedy + * Adam Sjogren + * Alexey Tourbin + * Alex Kapranoff + * amire80 + * Andreas J. Koenig + * Andy Grundman + * Bill Mann + * Bron Gondwana + * Chase Whitener + * Dagfinn Ilmari Mannsåker + * Daniel Hedlund + * Dave Rolsky + * David E. Wheeler + * DAVIDRW + * David Steinbrunner + * Eric Wong + * Father Chrysostomos + * FWILES + * Gavin Peters + * Gisle Aas + * Gisle Aas + * Gisle Aas + * Gisle Aas + * Graeme Thompson + * Hans-H. Froehlich + * Ian Kilgore + * Jacob J + * Jason A Fesler + * Jay Hannah + * Jean-Louis Martineau + * jefflee + * Jesse Luehrs + * john9art + * Karen Etheridge + * Kent Fredric + * Lasse Makholm + * Marinos Yannikos + * Mark Overmeer + * Mark Stosberg + * Mark Stosberg + * Mark Stosberg + * Mike Schilli + * Mike Schilli + * Mohammad S Anwar + * murphy + * Olaf Alders + * Ondrej Hanak + * Peter Rabbitson + * phrstbrn + * Robert Stone + * Rolf Grossmann + * ruff + * sasao + * Sean M. Burke + * Shoichi Kaji + * Slaven Rezic + * Slaven Rezic + * Spiros Denaxas + * Steffen Ullrich + * Steve Hay + * Todd Lipcon + * Tom Hukins + * Tony Finch + * Toru Yamaguchi + * uid39246 + * Ville Skytta + * Yuri Karaban + * Zefram + + diff --git a/Changes b/Changes new file mode 100644 index 0000000..07db9b0 --- /dev/null +++ b/Changes @@ -0,0 +1,82 @@ +Release history for Net-HTTP + +6.17 2017-09-01 15:30:20Z + - Fix test which relied on cpan.org speaking plain HTTP GH#54 (Chase + Whitener) + +6.16 2017-05-29 10:46:24-04:00 America/Toronto + - Bump IO::Socket::SSL version from 1.38 to 2.012 + +6.15 2017-05-12 14:57:02+02:00 Europe/Paris + - Fix t/rt-112313.t (Shoichi Kaji) + +6.14 2017-04-24 11:27:26-04:00 America/Toronto + - Improvements to live tests (Shoichi Kaji and Kent Fredric) + - Fix a bug where downloading files is sometimes very slow GH#44 (Shoichi + Kaji) + +6.13 2017-02-19 21:40:54-05:00 America/Toronto + - use EWOULDBLOCK as well on all places where EAGAIN is used (GH PR#24) + +6.12 2017-01-04 23:32:54-05:00 America/Toronto + - Fix prereqs + +6.11 2017-01-04 15:05:57-05:00 America/Toronto + - Updated the Changes file + - When using Net::SSL, pending data was potentially ignored GH PR#7 (Jean-Louis Martineau) + +6.10-DEV 2016-12-30 + - Added LICENSE + - Added 'use warnings' to everywhere that lacked it + - Drop all use of Test.pm + - Removed unneeded uses of 'use vars' + - Switch live tests to use Google. + - Fix RT#112313 - Hang in my_readline() when keep-alive => 1 and $response_size % 1024 == 0 + +6.09 2015-05-20 + - No changes since 6.08_002 + +6.08_002 2015-05-02 + - Fix foolish $VERSION error in 6.08_001 (Karen Etheridge) + +6.08_001 2015-05-01 + - resolve issues with SSL by reading bytes still waiting to be read after + the initial 1024 bytes [RT#104122] (Mark Overmeer) + +6.07 2014-07-23 + - Opportunistically use IO::Socket::IP or IO::Socket::INET6. (Jason Fesler) + - Properly parse IPv6 literal addresses with optional port numbers. [RT#75618] + +6.06 2013-03-10 + - IO::Socket::SSL doesn't play well with select() [RT#81237] (Jesse Luehrs) + +6.05 2012-11-10 + - Convert to Test::More style and disable test on Windows [RT#81090] (Gisle Aas) + - SSL broken for some servers [RT#81073] (Marinos Yannikos) + +6.04 2012-11-08 + - Simpler handling of double chunked [RT#77240] (Gisle Aas) + - Check for timeouts before reading [RT#72676] (Gisle Aas) + - Fake can_read (Gisle Aas) + - Fix chunked decoding on temporary read error [RT#74431] (Dagfinn Ilmari Mannsåker) + - NB: set http_bytes if read_entity_body hits EAGAIN on first read (Eric Wong) + - chunked,chunked is invalid, but happens. Ignore all but the first. [RT#77240] (Jay Hannah) + +6.03 2012-02-16 + - Restore blocking override for Net::SSL [RT#72790] + - Restore perl-5.6 compatibility. + +6.02 2011-11-21 + - Don't disable blocking method [RT#72580] + - Don't stop on unrecognized Makefile.PL arguments [RT#68337] + - Document Net:HTTPS [RT#71599] + +6.01 2011-03-17 + - Don't run live test by default; 'perl Makefile.PL --live-tests' to enable. + - More relaxed apache test; should pass even if proxies has added headers. + +6.00 2011-02-27 + - Initial release of Net-HTTP as a separate distribution. + - No code changes. + - Version bump to be ahead of old release + - The Net::HTTP module used to be bundled with the libwww-perl distribution. diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..c1fb5f4 --- /dev/null +++ b/INSTALL @@ -0,0 +1,43 @@ +This is the Perl distribution Net-HTTP. + +Installing Net-HTTP is straightforward. + +## Installation with cpanm + +If you have cpanm, you only need one line: + + % cpanm Net::HTTP + +If it does not have permission to install modules to the current perl, cpanm +will automatically set up and install to a local::lib in your home directory. +See the local::lib documentation (https://metacpan.org/pod/local::lib) for +details on enabling it in your environment. + +## Installing with the CPAN shell + +Alternatively, if your CPAN shell is set up, you should just be able to do: + + % cpan Net::HTTP + +## Manual installation + +As a last resort, you can manually install it. Download the tarball, untar it, +then build it: + + % perl Makefile.PL + % make && make test + +Then install it: + + % make install + +If your perl is system-managed, you can create a local::lib in your home +directory to install modules to. For details, see the local::lib documentation: +https://metacpan.org/pod/local::lib + +## Documentation + +Net-HTTP documentation is available as POD. +You can run perldoc from a shell to read the documentation: + + % perldoc Net::HTTP diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0df0e2e --- /dev/null +++ b/LICENSE @@ -0,0 +1,379 @@ +This software is copyright (c) 2001-2017 by Gisle Aas. + +This is free software; you can redistribute it and/or modify it under +the same terms as the Perl 5 programming language system itself. + +Terms of the Perl programming language system itself + +a) the GNU General Public License as published by the Free + Software Foundation; either version 1, or (at your option) any + later version, or +b) the "Artistic License" + +--- The GNU General Public License, Version 1, February 1989 --- + +This software is Copyright (c) 2001-2017 by Gisle Aas. + +This is free software, licensed under: + + The GNU General Public License, Version 1, February 1989 + + GNU GENERAL PUBLIC LICENSE + Version 1, February 1989 + + Copyright (C) 1989 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The license agreements of most software companies try to keep users +at the mercy of those companies. By contrast, our General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. The +General Public License applies to the Free Software Foundation's +software and to any other program whose authors commit to using it. +You can use it for your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Specifically, the General Public License is designed to make +sure that you have the freedom to give away or sell copies of free +software, that you receive source code or can get it if you want it, +that you can change the software or use pieces of it in new free +programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of a such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must tell them their rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any program or other work which +contains a notice placed by the copyright holder saying it may be +distributed under the terms of this General Public License. The +"Program", below, refers to any such program or work, and a "work based +on the Program" means either the Program or any work containing the +Program or a portion of it, either verbatim or with modifications. Each +licensee is addressed as "you". + + 1. You may copy and distribute verbatim copies of the Program's source +code as you receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice and +disclaimer of warranty; keep intact all the notices that refer to this +General Public License and to the absence of any warranty; and give any +other recipients of the Program a copy of this General Public License +along with the Program. You may charge a fee for the physical act of +transferring a copy. + + 2. You may modify your copy or copies of the Program or any portion of +it, and copy and distribute such modifications under the terms of Paragraph +1 above, provided that you also do the following: + + a) cause the modified files to carry prominent notices stating that + you changed the files and the date of any change; and + + b) cause the whole of any work that you distribute or publish, that + in whole or in part contains the Program or any part thereof, either + with or without modifications, to be licensed at no charge to all + third parties under the terms of this General Public License (except + that you may choose to grant warranty protection to some or all + third parties, at your option). + + c) If the modified program normally reads commands interactively when + run, you must cause it, when started running for such interactive use + in the simplest and most usual way, to print or display an + announcement including an appropriate copyright notice and a notice + that there is no warranty (or else, saying that you provide a + warranty) and that users may redistribute the program under these + conditions, and telling the user how to view a copy of this General + Public License. + + d) You may charge a fee for the physical act of transferring a + copy, and you may at your option offer warranty protection in + exchange for a fee. + +Mere aggregation of another independent work with the Program (or its +derivative) on a volume of a storage or distribution medium does not bring +the other work under the scope of these terms. + + 3. You may copy and distribute the Program (or a portion or derivative of +it, under Paragraph 2) in object code or executable form under the terms of +Paragraphs 1 and 2 above provided that you also do one of the following: + + a) accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of + Paragraphs 1 and 2 above; or, + + b) accompany it with a written offer, valid for at least three + years, to give any third party free (except for a nominal charge + for the cost of distribution) a complete machine-readable copy of the + corresponding source code, to be distributed under the terms of + Paragraphs 1 and 2 above; or, + + c) accompany it with the information you received as to where the + corresponding source code may be obtained. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form alone.) + +Source code for a work means the preferred form of the work for making +modifications to it. For an executable file, complete source code means +all the source code for all modules it contains; but, as a special +exception, it need not include source code for modules which are standard +libraries that accompany the operating system on which the executable +file runs, or for standard header files or definitions files that +accompany that operating system. + + 4. You may not copy, modify, sublicense, distribute or transfer the +Program except as expressly provided under this General Public License. +Any attempt otherwise to copy, modify, sublicense, distribute or transfer +the Program is void, and will automatically terminate your rights to use +the Program under this License. However, parties who have received +copies, or rights to use copies, from you under this General Public +License will not have their licenses terminated so long as such parties +remain in full compliance. + + 5. By copying, distributing or modifying the Program (or any work based +on the Program) you indicate your acceptance of this license to do so, +and all its terms and conditions. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the original +licensor to copy, distribute or modify the Program subject to these +terms and conditions. You may not impose any further restrictions on the +recipients' exercise of the rights granted herein. + + 7. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of the license which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +the license, you may choose any version ever published by the Free Software +Foundation. + + 8. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 9. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 10. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + Appendix: 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 humanity, the best way to achieve this is to make it +free software which everyone can redistribute and change under these +terms. + + To do so, attach the following notices to the program. It is safest to +attach them to the start of each source file to most effectively convey +the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 1, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19xx name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the +appropriate parts of the General Public License. Of course, the +commands you use may be called something other than `show w' and `show +c'; they could even be mouse-clicks or menu items--whatever suits your +program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + program `Gnomovision' (a program to direct compilers to make passes + at assemblers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +That's all there is to it! + + +--- The Artistic License 1.0 --- + +This software is Copyright (c) 2001-2017 by Gisle Aas. + +This is free software, licensed under: + + The Artistic License 1.0 + +The Artistic License + +Preamble + +The intent of this document is to state the conditions under which a Package +may be copied, such that the Copyright Holder maintains some semblance of +artistic control over the development of the package, while giving the users of +the package the right to use and distribute the Package in a more-or-less +customary fashion, plus the right to make reasonable modifications. + +Definitions: + + - "Package" refers to the collection of files distributed by the Copyright + Holder, and derivatives of that collection of files created through + textual modification. + - "Standard Version" refers to such a Package if it has not been modified, + or has been modified in accordance with the wishes of the Copyright + Holder. + - "Copyright Holder" is whoever is named in the copyright or copyrights for + the package. + - "You" is you, if you're thinking about copying or distributing this Package. + - "Reasonable copying fee" is whatever you can justify on the basis of media + cost, duplication charges, time of people involved, and so on. (You will + not be required to justify it to the Copyright Holder, but only to the + computing community at large as a market that must bear the fee.) + - "Freely Available" means that no fee is charged for the item itself, though + there may be fees involved in handling the item. It also means that + recipients of the item may redistribute it under the same conditions they + received it. + +1. You may make and give away verbatim copies of the source form of the +Standard Version of this Package without restriction, provided that you +duplicate all of the original copyright notices and associated disclaimers. + +2. You may apply bug fixes, portability fixes and other modifications derived +from the Public Domain or from the Copyright Holder. A Package modified in such +a way shall still be considered the Standard Version. + +3. You may otherwise modify your copy of this Package in any way, provided that +you insert a prominent notice in each changed file stating how and when you +changed that file, and provided that you do at least ONE of the following: + + a) place your modifications in the Public Domain or otherwise make them + Freely Available, such as by posting said modifications to Usenet or an + equivalent medium, or placing the modifications on a major archive site + such as ftp.uu.net, or by allowing the Copyright Holder to include your + modifications in the Standard Version of the Package. + + b) use the modified Package only within your corporation or organization. + + c) rename any non-standard executables so the names do not conflict with + standard executables, which must also be provided, and provide a separate + manual page for each non-standard executable that clearly documents how it + differs from the Standard Version. + + d) make other distribution arrangements with the Copyright Holder. + +4. You may distribute the programs of this Package in object code or executable +form, provided that you do at least ONE of the following: + + a) distribute a Standard Version of the executables and library files, + together with instructions (in the manual page or equivalent) on where to + get the Standard Version. + + b) accompany the distribution with the machine-readable source of the Package + with your modifications. + + c) accompany any non-standard executables with their corresponding Standard + Version executables, giving the non-standard executables non-standard + names, and clearly documenting the differences in manual pages (or + equivalent), together with instructions on where to get the Standard + Version. + + d) make other distribution arrangements with the Copyright Holder. + +5. You may charge a reasonable copying fee for any distribution of this +Package. You may charge any fee you choose for support of this Package. You +may not charge a fee for this Package itself. However, you may distribute this +Package in aggregate with other (possibly commercial) programs as part of a +larger (possibly commercial) software distribution provided that you do not +advertise this Package as a product of your own. + +6. The scripts and library files supplied as input to or produced as output +from the programs of this Package do not automatically fall under the copyright +of this Package, but belong to whomever generated them, and may be sold +commercially, and may be aggregated with this Package. + +7. C or perl subroutines supplied by you and linked into this Package shall not +be considered part of this Package. + +8. The name of the Copyright Holder may not be used to endorse or promote +products derived from this software without specific prior written permission. + +9. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF +MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + +The End + diff --git a/MANIFEST b/MANIFEST new file mode 100644 index 0000000..c654824 --- /dev/null +++ b/MANIFEST @@ -0,0 +1,26 @@ +# This file was automatically generated by Dist::Zilla::Plugin::Manifest v6.009. +CONTRIBUTORS +Changes +INSTALL +LICENSE +MANIFEST +META.json +META.yml +Makefile.PL +README.md +cpanfile +dist.ini +lib/Net/HTTP.pm +lib/Net/HTTP/Methods.pm +lib/Net/HTTP/NB.pm +lib/Net/HTTPS.pm +perlcriticrc +perltidyrc +t/00-report-prereqs.dd +t/00-report-prereqs.t +t/http-nb.t +t/http.t +t/live-https.t +t/live.t +t/rt-112313.t +tidyall.ini diff --git a/META.json b/META.json new file mode 100644 index 0000000..6c543a6 --- /dev/null +++ b/META.json @@ -0,0 +1,699 @@ +{ + "abstract" : "Low-level HTTP connection (client)", + "author" : [ + "Gisle Aas " + ], + "dynamic_config" : 0, + "generated_by" : "Dist::Zilla version 6.009, CPAN::Meta::Converter version 2.150010", + "license" : [ + "perl_5" + ], + "meta-spec" : { + "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", + "version" : 2 + }, + "name" : "Net-HTTP", + "no_index" : { + "directory" : [ + "examples", + "t", + "xt" + ] + }, + "prereqs" : { + "configure" : { + "requires" : { + "ExtUtils::MakeMaker" : "0" + }, + "suggests" : { + "JSON::PP" : "2.27300" + } + }, + "runtime" : { + "requires" : { + "Carp" : "0", + "Compress::Raw::Zlib" : "0", + "IO::Socket::INET" : "0", + "IO::Uncompress::Gunzip" : "0", + "URI" : "0", + "base" : "0", + "perl" : "5.006002", + "strict" : "0", + "vars" : "0", + "warnings" : "0" + }, + "suggests" : { + "IO::Socket" : "0", + "IO::Socket::INET6" : "0", + "IO::Socket::IP" : "0", + "IO::Socket::SSL" : "2.012", + "Symbol" : "0" + } + }, + "test" : { + "recommends" : { + "CPAN::Meta" : "2.120900" + }, + "requires" : { + "Data::Dumper" : "0", + "ExtUtils::MakeMaker" : "0", + "File::Spec" : "0", + "IO::Select" : "0", + "Socket" : "0", + "Test::More" : "0" + } + } + }, + "release_status" : "stable", + "resources" : { + "bugtracker" : { + "web" : "https://github.com/libwww-perl/Net-HTTP/issues" + }, + "homepage" : "https://github.com/libwww-perl/Net-HTTP", + "repository" : { + "type" : "git", + "url" : "https://github.com/libwww-perl/Net-HTTP.git", + "web" : "https://github.com/libwww-perl/Net-HTTP" + }, + "x_IRC" : "irc://irc.perl.org/#lwp", + "x_MailingList" : "mailto:libwww@perl.org" + }, + "version" : "6.17", + "x_Dist_Zilla" : { + "perl" : { + "version" : "5.026000" + }, + "plugins" : [ + { + "class" : "Dist::Zilla::Plugin::MetaResources", + "name" : "MetaResources", + "version" : "6.009" + }, + { + "class" : "Dist::Zilla::Plugin::Prereqs", + "config" : { + "Dist::Zilla::Plugin::Prereqs" : { + "phase" : "runtime", + "type" : "requires" + } + }, + "name" : "Prereqs", + "version" : "6.009" + }, + { + "class" : "Dist::Zilla::Plugin::PromptIfStale", + "config" : { + "Dist::Zilla::Plugin::PromptIfStale" : { + "check_all_plugins" : 0, + "check_all_prereqs" : 0, + "modules" : [ + "Dist::Zilla::PluginBundle::Author::OALDERS" + ], + "phase" : "build", + "run_under_travis" : 0, + "skip" : [] + } + }, + "name" : "@Author::OALDERS/stale modules, build", + "version" : "0.053" + }, + { + "class" : "Dist::Zilla::Plugin::PromptIfStale", + "config" : { + "Dist::Zilla::Plugin::PromptIfStale" : { + "check_all_plugins" : 1, + "check_all_prereqs" : 1, + "modules" : [], + "phase" : "release", + "run_under_travis" : 0, + "skip" : [] + } + }, + "name" : "@Author::OALDERS/stale modules, release", + "version" : "0.053" + }, + { + "class" : "Dist::Zilla::Plugin::MAXMIND::TidyAll", + "name" : "@Author::OALDERS/MAXMIND::TidyAll", + "version" : "0.13" + }, + { + "class" : "Dist::Zilla::Plugin::MakeMaker", + "config" : { + "Dist::Zilla::Role::TestRunner" : { + "default_jobs" : 1 + } + }, + "name" : "@Author::OALDERS/MakeMaker", + "version" : "6.009" + }, + { + "class" : "Dist::Zilla::Plugin::CPANFile", + "name" : "@Author::OALDERS/CPANFile", + "version" : "6.009" + }, + { + "class" : "Dist::Zilla::Plugin::ContributorsFile", + "name" : "@Author::OALDERS/ContributorsFile", + "version" : "0.3.0" + }, + { + "class" : "Dist::Zilla::Plugin::MetaJSON", + "name" : "@Author::OALDERS/MetaJSON", + "version" : "6.009" + }, + { + "class" : "Dist::Zilla::Plugin::MetaYAML", + "name" : "@Author::OALDERS/MetaYAML", + "version" : "6.009" + }, + { + "class" : "Dist::Zilla::Plugin::Manifest", + "name" : "@Author::OALDERS/Manifest", + "version" : "6.009" + }, + { + "class" : "Dist::Zilla::Plugin::ManifestSkip", + "name" : "@Author::OALDERS/ManifestSkip", + "version" : "6.009" + }, + { + "class" : "Dist::Zilla::Plugin::MetaNoIndex", + "name" : "@Author::OALDERS/MetaNoIndex", + "version" : "6.009" + }, + { + "class" : "Dist::Zilla::Plugin::MetaConfig", + "name" : "@Author::OALDERS/MetaConfig", + "version" : "6.009" + }, + { + "class" : "Dist::Zilla::Plugin::MetaResources", + "name" : "@Author::OALDERS/MetaResources", + "version" : "6.009" + }, + { + "class" : "Dist::Zilla::Plugin::License", + "name" : "@Author::OALDERS/License", + "version" : "6.009" + }, + { + "class" : "Dist::Zilla::Plugin::InstallGuide", + "name" : "@Author::OALDERS/InstallGuide", + "version" : "1.200007" + }, + { + "class" : "Dist::Zilla::Plugin::ExecDir", + "name" : "@Author::OALDERS/ExecDir", + "version" : "6.009" + }, + { + "class" : "Dist::Zilla::Plugin::TestRelease", + "name" : "@Author::OALDERS/TestRelease", + "version" : "6.009" + }, + { + "class" : "Dist::Zilla::Plugin::Test::ReportPrereqs", + "name" : "@Author::OALDERS/Test::ReportPrereqs", + "version" : "0.027" + }, + { + "class" : "Dist::Zilla::Plugin::RunExtraTests", + "config" : { + "Dist::Zilla::Role::TestRunner" : { + "default_jobs" : 1 + } + }, + "name" : "@Author::OALDERS/RunExtraTests", + "version" : "0.029" + }, + { + "class" : "Dist::Zilla::Plugin::PkgVersion", + "name" : "@Author::OALDERS/PkgVersion", + "version" : "6.009" + }, + { + "class" : "Dist::Zilla::Plugin::PodWeaver", + "config" : { + "Dist::Zilla::Plugin::PodWeaver" : { + "finder" : [ + ":InstallModules", + ":ExecFiles" + ], + "plugins" : [ + { + "class" : "Pod::Weaver::Plugin::EnsurePod5", + "name" : "@CorePrep/EnsurePod5", + "version" : "4.015" + }, + { + "class" : "Pod::Weaver::Plugin::H1Nester", + "name" : "@CorePrep/H1Nester", + "version" : "4.015" + }, + { + "class" : "Pod::Weaver::Plugin::SingleEncoding", + "name" : "@Default/SingleEncoding", + "version" : "4.015" + }, + { + "class" : "Pod::Weaver::Section::Name", + "name" : "@Default/Name", + "version" : "4.015" + }, + { + "class" : "Pod::Weaver::Section::Version", + "name" : "@Default/Version", + "version" : "4.015" + }, + { + "class" : "Pod::Weaver::Section::Region", + "name" : "@Default/prelude", + "version" : "4.015" + }, + { + "class" : "Pod::Weaver::Section::Generic", + "name" : "SYNOPSIS", + "version" : "4.015" + }, + { + "class" : "Pod::Weaver::Section::Generic", + "name" : "DESCRIPTION", + "version" : "4.015" + }, + { + "class" : "Pod::Weaver::Section::Generic", + "name" : "OVERVIEW", + "version" : "4.015" + }, + { + "class" : "Pod::Weaver::Section::Collect", + "name" : "ATTRIBUTES", + "version" : "4.015" + }, + { + "class" : "Pod::Weaver::Section::Collect", + "name" : "METHODS", + "version" : "4.015" + }, + { + "class" : "Pod::Weaver::Section::Collect", + "name" : "FUNCTIONS", + "version" : "4.015" + }, + { + "class" : "Pod::Weaver::Section::Leftovers", + "name" : "@Default/Leftovers", + "version" : "4.015" + }, + { + "class" : "Pod::Weaver::Section::Region", + "name" : "@Default/postlude", + "version" : "4.015" + }, + { + "class" : "Pod::Weaver::Section::Authors", + "name" : "@Default/Authors", + "version" : "4.015" + }, + { + "class" : "Pod::Weaver::Section::Legal", + "name" : "@Default/Legal", + "version" : "4.015" + } + ] + } + }, + "name" : "@Author::OALDERS/PodWeaver", + "version" : "4.008" + }, + { + "class" : "Dist::Zilla::Plugin::PruneCruft", + "name" : "@Author::OALDERS/PruneCruft", + "version" : "6.009" + }, + { + "class" : "Dist::Zilla::Plugin::CopyFilesFromBuild", + "name" : "@Author::OALDERS/CopyFilesFromBuild", + "version" : "0.170880" + }, + { + "class" : "Dist::Zilla::Plugin::NextRelease", + "name" : "@Author::OALDERS/NextRelease", + "version" : "6.009" + }, + { + "class" : "Dist::Zilla::Plugin::GithubMeta", + "name" : "@Author::OALDERS/GithubMeta", + "version" : "0.54" + }, + { + "class" : "Dist::Zilla::Plugin::Git::GatherDir", + "config" : { + "Dist::Zilla::Plugin::GatherDir" : { + "exclude_filename" : [ + "Install", + "LICENSE", + "META.json", + "Makefile.PL", + "README.md", + "cpanfile" + ], + "exclude_match" : [], + "follow_symlinks" : 0, + "include_dotfiles" : 0, + "prefix" : "", + "prune_directory" : [], + "root" : "." + }, + "Dist::Zilla::Plugin::Git::GatherDir" : { + "include_untracked" : 0 + } + }, + "name" : "@Author::OALDERS/Git::GatherDir", + "version" : "2.042" + }, + { + "class" : "Dist::Zilla::Plugin::CopyFilesFromRelease", + "config" : { + "Dist::Zilla::Plugin::CopyFilesFromRelease" : { + "filename" : [ + "Install" + ], + "match" : [] + } + }, + "name" : "@Author::OALDERS/CopyFilesFromRelease", + "version" : "0.006" + }, + { + "class" : "Dist::Zilla::Plugin::Git::Check", + "config" : { + "Dist::Zilla::Plugin::Git::Check" : { + "untracked_files" : "die" + }, + "Dist::Zilla::Role::Git::DirtyFiles" : { + "allow_dirty" : [ + "Changes", + "Install", + "LICENSE", + "META.json", + "Makefile.PL", + "README.md", + "cpanfile", + "dist.ini" + ], + "allow_dirty_match" : [], + "changelog" : "Changes" + }, + "Dist::Zilla::Role::Git::Repo" : { + "git_version" : "2.12.2", + "repo_root" : "." + } + }, + "name" : "@Author::OALDERS/Git::Check", + "version" : "2.042" + }, + { + "class" : "Dist::Zilla::Plugin::Git::Commit", + "config" : { + "Dist::Zilla::Plugin::Git::Commit" : { + "add_files_in" : [], + "commit_msg" : "v%v%n%n%c" + }, + "Dist::Zilla::Role::Git::DirtyFiles" : { + "allow_dirty" : [ + "Changes", + "Install", + "LICENSE", + "META.json", + "Makefile.PL", + "README.md", + "cpanfile", + "dist.ini" + ], + "allow_dirty_match" : [], + "changelog" : "Changes" + }, + "Dist::Zilla::Role::Git::Repo" : { + "git_version" : "2.12.2", + "repo_root" : "." + }, + "Dist::Zilla::Role::Git::StringFormatter" : { + "time_zone" : "local" + } + }, + "name" : "@Author::OALDERS/commit generated files", + "version" : "2.042" + }, + { + "class" : "Dist::Zilla::Plugin::Git::Contributors", + "config" : { + "Dist::Zilla::Plugin::Git::Contributors" : { + "git_version" : "2.12.2", + "include_authors" : 0, + "include_releaser" : 1, + "order_by" : "name", + "paths" : [] + } + }, + "name" : "@Author::OALDERS/Git::Contributors", + "version" : "0.030" + }, + { + "class" : "Dist::Zilla::Plugin::Git::Tag", + "config" : { + "Dist::Zilla::Plugin::Git::Tag" : { + "branch" : null, + "changelog" : "Changes", + "signed" : 0, + "tag" : "v6.17", + "tag_format" : "v%v", + "tag_message" : "v%v" + }, + "Dist::Zilla::Role::Git::Repo" : { + "git_version" : "2.12.2", + "repo_root" : "." + }, + "Dist::Zilla::Role::Git::StringFormatter" : { + "time_zone" : "local" + } + }, + "name" : "@Author::OALDERS/Git::Tag", + "version" : "2.042" + }, + { + "class" : "Dist::Zilla::Plugin::Git::Push", + "config" : { + "Dist::Zilla::Plugin::Git::Push" : { + "push_to" : [ + "origin" + ], + "remotes_must_exist" : 1 + }, + "Dist::Zilla::Role::Git::Repo" : { + "git_version" : "2.12.2", + "repo_root" : "." + } + }, + "name" : "@Author::OALDERS/Git::Push", + "version" : "2.042" + }, + { + "class" : "Dist::Zilla::Plugin::ReadmeAnyFromPod", + "config" : { + "Dist::Zilla::Role::FileWatcher" : { + "version" : "0.006" + } + }, + "name" : "@Author::OALDERS/ReadmeMdInBuild", + "version" : "0.163250" + }, + { + "class" : "Dist::Zilla::Plugin::ShareDir", + "name" : "@Author::OALDERS/ShareDir", + "version" : "6.009" + }, + { + "class" : "Dist::Zilla::Plugin::TravisCI::StatusBadge", + "name" : "@Author::OALDERS/TravisCI::StatusBadge", + "version" : "0.007" + }, + { + "class" : "Dist::Zilla::Plugin::ConfirmRelease", + "name" : "@Author::OALDERS/ConfirmRelease", + "version" : "6.009" + }, + { + "class" : "Dist::Zilla::Plugin::UploadToCPAN", + "name" : "@Author::OALDERS/UploadToCPAN", + "version" : "6.009" + }, + { + "class" : "Dist::Zilla::Plugin::AutoPrereqs", + "name" : "AutoPrereqs", + "version" : "6.009" + }, + { + "class" : "Dist::Zilla::Plugin::Prereqs", + "config" : { + "Dist::Zilla::Plugin::Prereqs" : { + "phase" : "runtime", + "type" : "suggests" + } + }, + "name" : "RuntimeSuggests", + "version" : "6.009" + }, + { + "class" : "Dist::Zilla::Plugin::Prereqs::Soften", + "config" : { + "Dist::Zilla::Plugin::Prereqs::Soften" : { + "copy_to" : [], + "modules" : [ + "IO::Socket", + "IO::Socket::INET6", + "IO::Socket::IP", + "IO::Socket::SSL", + "Symbol" + ], + "modules_from_features" : null, + "to_relationship" : "suggests" + } + }, + "name" : "Prereqs::Soften", + "version" : "0.006003" + }, + { + "class" : "Dist::Zilla::Plugin::FinderCode", + "name" : ":InstallModules", + "version" : "6.009" + }, + { + "class" : "Dist::Zilla::Plugin::FinderCode", + "name" : ":IncModules", + "version" : "6.009" + }, + { + "class" : "Dist::Zilla::Plugin::FinderCode", + "name" : ":TestFiles", + "version" : "6.009" + }, + { + "class" : "Dist::Zilla::Plugin::FinderCode", + "name" : ":ExtraTestFiles", + "version" : "6.009" + }, + { + "class" : "Dist::Zilla::Plugin::FinderCode", + "name" : ":ExecFiles", + "version" : "6.009" + }, + { + "class" : "Dist::Zilla::Plugin::FinderCode", + "name" : ":PerlExecFiles", + "version" : "6.009" + }, + { + "class" : "Dist::Zilla::Plugin::FinderCode", + "name" : ":ShareFiles", + "version" : "6.009" + }, + { + "class" : "Dist::Zilla::Plugin::FinderCode", + "name" : ":MainModule", + "version" : "6.009" + }, + { + "class" : "Dist::Zilla::Plugin::FinderCode", + "name" : ":AllFiles", + "version" : "6.009" + }, + { + "class" : "Dist::Zilla::Plugin::FinderCode", + "name" : ":NoFiles", + "version" : "6.009" + } + ], + "zilla" : { + "class" : "Dist::Zilla::Dist::Builder", + "config" : { + "is_trial" : 0 + }, + "version" : "6.009" + } + }, + "x_contributors" : [ + "Adam Kennedy ", + "Adam Sjogren ", + "Alexey Tourbin ", + "Alex Kapranoff ", + "amire80 ", + "Andreas J. Koenig ", + "Andy Grundman ", + "Bill Mann ", + "Bron Gondwana ", + "Chase Whitener ", + "Dagfinn Ilmari Manns\u00e5ker ", + "Daniel Hedlund ", + "Dave Rolsky ", + "David E. Wheeler ", + "DAVIDRW ", + "David Steinbrunner ", + "Eric Wong ", + "Father Chrysostomos ", + "FWILES ", + "Gavin Peters ", + "Gisle Aas ", + "Gisle Aas ", + "Gisle Aas ", + "Gisle Aas ", + "Graeme Thompson ", + "Hans-H. Froehlich ", + "Ian Kilgore ", + "Jacob J ", + "Jason A Fesler ", + "Jay Hannah ", + "Jean-Louis Martineau ", + "jefflee ", + "Jesse Luehrs ", + "john9art ", + "Karen Etheridge ", + "Kent Fredric ", + "Lasse Makholm ", + "Marinos Yannikos ", + "Mark Overmeer ", + "Mark Stosberg ", + "Mark Stosberg ", + "Mark Stosberg ", + "Mike Schilli ", + "Mike Schilli ", + "Mohammad S Anwar ", + "murphy ", + "Olaf Alders ", + "Ondrej Hanak ", + "Peter Rabbitson ", + "phrstbrn ", + "Robert Stone ", + "Rolf Grossmann ", + "ruff ", + "sasao ", + "Sean M. Burke ", + "Shoichi Kaji ", + "Slaven Rezic ", + "Slaven Rezic ", + "Spiros Denaxas ", + "Steffen Ullrich ", + "Steve Hay ", + "Todd Lipcon ", + "Tom Hukins ", + "Tony Finch ", + "Toru Yamaguchi ", + "uid39246 ", + "Ville Skytta ", + "Yuri Karaban ", + "Zefram " + ], + "x_serialization_backend" : "Cpanel::JSON::XS version 3.0233" +} + diff --git a/META.yml b/META.yml new file mode 100644 index 0000000..342bc83 --- /dev/null +++ b/META.yml @@ -0,0 +1,535 @@ +--- +abstract: 'Low-level HTTP connection (client)' +author: + - 'Gisle Aas ' +build_requires: + Data::Dumper: '0' + ExtUtils::MakeMaker: '0' + File::Spec: '0' + IO::Select: '0' + Socket: '0' + Test::More: '0' +configure_requires: + ExtUtils::MakeMaker: '0' +dynamic_config: 0 +generated_by: 'Dist::Zilla version 6.009, CPAN::Meta::Converter version 2.150010' +license: perl +meta-spec: + url: http://module-build.sourceforge.net/META-spec-v1.4.html + version: '1.4' +name: Net-HTTP +no_index: + directory: + - examples + - t + - xt +requires: + Carp: '0' + Compress::Raw::Zlib: '0' + IO::Socket::INET: '0' + IO::Uncompress::Gunzip: '0' + URI: '0' + base: '0' + perl: '5.006002' + strict: '0' + vars: '0' + warnings: '0' +resources: + IRC: irc://irc.perl.org/#lwp + MailingList: mailto:libwww@perl.org + bugtracker: https://github.com/libwww-perl/Net-HTTP/issues + homepage: https://github.com/libwww-perl/Net-HTTP + repository: https://github.com/libwww-perl/Net-HTTP.git +version: '6.17' +x_Dist_Zilla: + perl: + version: '5.026000' + plugins: + - + class: Dist::Zilla::Plugin::MetaResources + name: MetaResources + version: '6.009' + - + class: Dist::Zilla::Plugin::Prereqs + config: + Dist::Zilla::Plugin::Prereqs: + phase: runtime + type: requires + name: Prereqs + version: '6.009' + - + class: Dist::Zilla::Plugin::PromptIfStale + config: + Dist::Zilla::Plugin::PromptIfStale: + check_all_plugins: 0 + check_all_prereqs: 0 + modules: + - Dist::Zilla::PluginBundle::Author::OALDERS + phase: build + run_under_travis: 0 + skip: [] + name: '@Author::OALDERS/stale modules, build' + version: '0.053' + - + class: Dist::Zilla::Plugin::PromptIfStale + config: + Dist::Zilla::Plugin::PromptIfStale: + check_all_plugins: 1 + check_all_prereqs: 1 + modules: [] + phase: release + run_under_travis: 0 + skip: [] + name: '@Author::OALDERS/stale modules, release' + version: '0.053' + - + class: Dist::Zilla::Plugin::MAXMIND::TidyAll + name: '@Author::OALDERS/MAXMIND::TidyAll' + version: '0.13' + - + class: Dist::Zilla::Plugin::MakeMaker + config: + Dist::Zilla::Role::TestRunner: + default_jobs: 1 + name: '@Author::OALDERS/MakeMaker' + version: '6.009' + - + class: Dist::Zilla::Plugin::CPANFile + name: '@Author::OALDERS/CPANFile' + version: '6.009' + - + class: Dist::Zilla::Plugin::ContributorsFile + name: '@Author::OALDERS/ContributorsFile' + version: 0.3.0 + - + class: Dist::Zilla::Plugin::MetaJSON + name: '@Author::OALDERS/MetaJSON' + version: '6.009' + - + class: Dist::Zilla::Plugin::MetaYAML + name: '@Author::OALDERS/MetaYAML' + version: '6.009' + - + class: Dist::Zilla::Plugin::Manifest + name: '@Author::OALDERS/Manifest' + version: '6.009' + - + class: Dist::Zilla::Plugin::ManifestSkip + name: '@Author::OALDERS/ManifestSkip' + version: '6.009' + - + class: Dist::Zilla::Plugin::MetaNoIndex + name: '@Author::OALDERS/MetaNoIndex' + version: '6.009' + - + class: Dist::Zilla::Plugin::MetaConfig + name: '@Author::OALDERS/MetaConfig' + version: '6.009' + - + class: Dist::Zilla::Plugin::MetaResources + name: '@Author::OALDERS/MetaResources' + version: '6.009' + - + class: Dist::Zilla::Plugin::License + name: '@Author::OALDERS/License' + version: '6.009' + - + class: Dist::Zilla::Plugin::InstallGuide + name: '@Author::OALDERS/InstallGuide' + version: '1.200007' + - + class: Dist::Zilla::Plugin::ExecDir + name: '@Author::OALDERS/ExecDir' + version: '6.009' + - + class: Dist::Zilla::Plugin::TestRelease + name: '@Author::OALDERS/TestRelease' + version: '6.009' + - + class: Dist::Zilla::Plugin::Test::ReportPrereqs + name: '@Author::OALDERS/Test::ReportPrereqs' + version: '0.027' + - + class: Dist::Zilla::Plugin::RunExtraTests + config: + Dist::Zilla::Role::TestRunner: + default_jobs: 1 + name: '@Author::OALDERS/RunExtraTests' + version: '0.029' + - + class: Dist::Zilla::Plugin::PkgVersion + name: '@Author::OALDERS/PkgVersion' + version: '6.009' + - + class: Dist::Zilla::Plugin::PodWeaver + config: + Dist::Zilla::Plugin::PodWeaver: + finder: + - ':InstallModules' + - ':ExecFiles' + plugins: + - + class: Pod::Weaver::Plugin::EnsurePod5 + name: '@CorePrep/EnsurePod5' + version: '4.015' + - + class: Pod::Weaver::Plugin::H1Nester + name: '@CorePrep/H1Nester' + version: '4.015' + - + class: Pod::Weaver::Plugin::SingleEncoding + name: '@Default/SingleEncoding' + version: '4.015' + - + class: Pod::Weaver::Section::Name + name: '@Default/Name' + version: '4.015' + - + class: Pod::Weaver::Section::Version + name: '@Default/Version' + version: '4.015' + - + class: Pod::Weaver::Section::Region + name: '@Default/prelude' + version: '4.015' + - + class: Pod::Weaver::Section::Generic + name: SYNOPSIS + version: '4.015' + - + class: Pod::Weaver::Section::Generic + name: DESCRIPTION + version: '4.015' + - + class: Pod::Weaver::Section::Generic + name: OVERVIEW + version: '4.015' + - + class: Pod::Weaver::Section::Collect + name: ATTRIBUTES + version: '4.015' + - + class: Pod::Weaver::Section::Collect + name: METHODS + version: '4.015' + - + class: Pod::Weaver::Section::Collect + name: FUNCTIONS + version: '4.015' + - + class: Pod::Weaver::Section::Leftovers + name: '@Default/Leftovers' + version: '4.015' + - + class: Pod::Weaver::Section::Region + name: '@Default/postlude' + version: '4.015' + - + class: Pod::Weaver::Section::Authors + name: '@Default/Authors' + version: '4.015' + - + class: Pod::Weaver::Section::Legal + name: '@Default/Legal' + version: '4.015' + name: '@Author::OALDERS/PodWeaver' + version: '4.008' + - + class: Dist::Zilla::Plugin::PruneCruft + name: '@Author::OALDERS/PruneCruft' + version: '6.009' + - + class: Dist::Zilla::Plugin::CopyFilesFromBuild + name: '@Author::OALDERS/CopyFilesFromBuild' + version: '0.170880' + - + class: Dist::Zilla::Plugin::NextRelease + name: '@Author::OALDERS/NextRelease' + version: '6.009' + - + class: Dist::Zilla::Plugin::GithubMeta + name: '@Author::OALDERS/GithubMeta' + version: '0.54' + - + class: Dist::Zilla::Plugin::Git::GatherDir + config: + Dist::Zilla::Plugin::GatherDir: + exclude_filename: + - Install + - LICENSE + - META.json + - Makefile.PL + - README.md + - cpanfile + exclude_match: [] + follow_symlinks: 0 + include_dotfiles: 0 + prefix: '' + prune_directory: [] + root: . + Dist::Zilla::Plugin::Git::GatherDir: + include_untracked: 0 + name: '@Author::OALDERS/Git::GatherDir' + version: '2.042' + - + class: Dist::Zilla::Plugin::CopyFilesFromRelease + config: + Dist::Zilla::Plugin::CopyFilesFromRelease: + filename: + - Install + match: [] + name: '@Author::OALDERS/CopyFilesFromRelease' + version: '0.006' + - + class: Dist::Zilla::Plugin::Git::Check + config: + Dist::Zilla::Plugin::Git::Check: + untracked_files: die + Dist::Zilla::Role::Git::DirtyFiles: + allow_dirty: + - Changes + - Install + - LICENSE + - META.json + - Makefile.PL + - README.md + - cpanfile + - dist.ini + allow_dirty_match: [] + changelog: Changes + Dist::Zilla::Role::Git::Repo: + git_version: 2.12.2 + repo_root: . + name: '@Author::OALDERS/Git::Check' + version: '2.042' + - + class: Dist::Zilla::Plugin::Git::Commit + config: + Dist::Zilla::Plugin::Git::Commit: + add_files_in: [] + commit_msg: v%v%n%n%c + Dist::Zilla::Role::Git::DirtyFiles: + allow_dirty: + - Changes + - Install + - LICENSE + - META.json + - Makefile.PL + - README.md + - cpanfile + - dist.ini + allow_dirty_match: [] + changelog: Changes + Dist::Zilla::Role::Git::Repo: + git_version: 2.12.2 + repo_root: . + Dist::Zilla::Role::Git::StringFormatter: + time_zone: local + name: '@Author::OALDERS/commit generated files' + version: '2.042' + - + class: Dist::Zilla::Plugin::Git::Contributors + config: + Dist::Zilla::Plugin::Git::Contributors: + git_version: 2.12.2 + include_authors: 0 + include_releaser: 1 + order_by: name + paths: [] + name: '@Author::OALDERS/Git::Contributors' + version: '0.030' + - + class: Dist::Zilla::Plugin::Git::Tag + config: + Dist::Zilla::Plugin::Git::Tag: + branch: ~ + changelog: Changes + signed: 0 + tag: v6.17 + tag_format: v%v + tag_message: v%v + Dist::Zilla::Role::Git::Repo: + git_version: 2.12.2 + repo_root: . + Dist::Zilla::Role::Git::StringFormatter: + time_zone: local + name: '@Author::OALDERS/Git::Tag' + version: '2.042' + - + class: Dist::Zilla::Plugin::Git::Push + config: + Dist::Zilla::Plugin::Git::Push: + push_to: + - origin + remotes_must_exist: 1 + Dist::Zilla::Role::Git::Repo: + git_version: 2.12.2 + repo_root: . + name: '@Author::OALDERS/Git::Push' + version: '2.042' + - + class: Dist::Zilla::Plugin::ReadmeAnyFromPod + config: + Dist::Zilla::Role::FileWatcher: + version: '0.006' + name: '@Author::OALDERS/ReadmeMdInBuild' + version: '0.163250' + - + class: Dist::Zilla::Plugin::ShareDir + name: '@Author::OALDERS/ShareDir' + version: '6.009' + - + class: Dist::Zilla::Plugin::TravisCI::StatusBadge + name: '@Author::OALDERS/TravisCI::StatusBadge' + version: '0.007' + - + class: Dist::Zilla::Plugin::ConfirmRelease + name: '@Author::OALDERS/ConfirmRelease' + version: '6.009' + - + class: Dist::Zilla::Plugin::UploadToCPAN + name: '@Author::OALDERS/UploadToCPAN' + version: '6.009' + - + class: Dist::Zilla::Plugin::AutoPrereqs + name: AutoPrereqs + version: '6.009' + - + class: Dist::Zilla::Plugin::Prereqs + config: + Dist::Zilla::Plugin::Prereqs: + phase: runtime + type: suggests + name: RuntimeSuggests + version: '6.009' + - + class: Dist::Zilla::Plugin::Prereqs::Soften + config: + Dist::Zilla::Plugin::Prereqs::Soften: + copy_to: [] + modules: + - IO::Socket + - IO::Socket::INET6 + - IO::Socket::IP + - IO::Socket::SSL + - Symbol + modules_from_features: ~ + to_relationship: suggests + name: Prereqs::Soften + version: '0.006003' + - + class: Dist::Zilla::Plugin::FinderCode + name: ':InstallModules' + version: '6.009' + - + class: Dist::Zilla::Plugin::FinderCode + name: ':IncModules' + version: '6.009' + - + class: Dist::Zilla::Plugin::FinderCode + name: ':TestFiles' + version: '6.009' + - + class: Dist::Zilla::Plugin::FinderCode + name: ':ExtraTestFiles' + version: '6.009' + - + class: Dist::Zilla::Plugin::FinderCode + name: ':ExecFiles' + version: '6.009' + - + class: Dist::Zilla::Plugin::FinderCode + name: ':PerlExecFiles' + version: '6.009' + - + class: Dist::Zilla::Plugin::FinderCode + name: ':ShareFiles' + version: '6.009' + - + class: Dist::Zilla::Plugin::FinderCode + name: ':MainModule' + version: '6.009' + - + class: Dist::Zilla::Plugin::FinderCode + name: ':AllFiles' + version: '6.009' + - + class: Dist::Zilla::Plugin::FinderCode + name: ':NoFiles' + version: '6.009' + zilla: + class: Dist::Zilla::Dist::Builder + config: + is_trial: '0' + version: '6.009' +x_contributors: + - 'Adam Kennedy ' + - 'Adam Sjogren ' + - 'Alexey Tourbin ' + - 'Alex Kapranoff ' + - 'amire80 ' + - 'Andreas J. Koenig ' + - 'Andy Grundman ' + - 'Bill Mann ' + - 'Bron Gondwana ' + - 'Chase Whitener ' + - 'Dagfinn Ilmari Mannsåker ' + - 'Daniel Hedlund ' + - 'Dave Rolsky ' + - 'David E. Wheeler ' + - 'DAVIDRW ' + - 'David Steinbrunner ' + - 'Eric Wong ' + - 'Father Chrysostomos ' + - 'FWILES ' + - 'Gavin Peters ' + - 'Gisle Aas ' + - 'Gisle Aas ' + - 'Gisle Aas ' + - 'Gisle Aas ' + - 'Graeme Thompson ' + - 'Hans-H. Froehlich ' + - 'Ian Kilgore ' + - 'Jacob J ' + - 'Jason A Fesler ' + - 'Jay Hannah ' + - 'Jean-Louis Martineau ' + - 'jefflee ' + - 'Jesse Luehrs ' + - 'john9art ' + - 'Karen Etheridge ' + - 'Kent Fredric ' + - 'Lasse Makholm ' + - 'Marinos Yannikos ' + - 'Mark Overmeer ' + - 'Mark Stosberg ' + - 'Mark Stosberg ' + - 'Mark Stosberg ' + - 'Mike Schilli ' + - 'Mike Schilli ' + - 'Mohammad S Anwar ' + - 'murphy ' + - 'Olaf Alders ' + - 'Ondrej Hanak ' + - 'Peter Rabbitson ' + - 'phrstbrn ' + - 'Robert Stone ' + - 'Rolf Grossmann ' + - 'ruff ' + - 'sasao ' + - 'Sean M. Burke ' + - 'Shoichi Kaji ' + - 'Slaven Rezic ' + - 'Slaven Rezic ' + - 'Spiros Denaxas ' + - 'Steffen Ullrich ' + - 'Steve Hay ' + - 'Todd Lipcon ' + - 'Tom Hukins ' + - 'Tony Finch ' + - 'Toru Yamaguchi ' + - 'uid39246 ' + - 'Ville Skytta ' + - 'Yuri Karaban ' + - 'Zefram ' +x_serialization_backend: 'YAML::Tiny version 1.70' diff --git a/Makefile.PL b/Makefile.PL new file mode 100644 index 0000000..e11f498 --- /dev/null +++ b/Makefile.PL @@ -0,0 +1,73 @@ +# This file was automatically generated by Dist::Zilla::Plugin::MakeMaker v6.009. +use strict; +use warnings; + +use 5.006002; + +use ExtUtils::MakeMaker; + +my %WriteMakefileArgs = ( + "ABSTRACT" => "Low-level HTTP connection (client)", + "AUTHOR" => "Gisle Aas ", + "CONFIGURE_REQUIRES" => { + "ExtUtils::MakeMaker" => 0 + }, + "DISTNAME" => "Net-HTTP", + "LICENSE" => "perl", + "MIN_PERL_VERSION" => "5.006002", + "NAME" => "Net::HTTP", + "PREREQ_PM" => { + "Carp" => 0, + "Compress::Raw::Zlib" => 0, + "IO::Socket::INET" => 0, + "IO::Uncompress::Gunzip" => 0, + "URI" => 0, + "base" => 0, + "strict" => 0, + "vars" => 0, + "warnings" => 0 + }, + "TEST_REQUIRES" => { + "Data::Dumper" => 0, + "ExtUtils::MakeMaker" => 0, + "File::Spec" => 0, + "IO::Select" => 0, + "Socket" => 0, + "Test::More" => 0 + }, + "VERSION" => "6.17", + "test" => { + "TESTS" => "t/*.t" + } +); + + +my %FallbackPrereqs = ( + "Carp" => 0, + "Compress::Raw::Zlib" => 0, + "Data::Dumper" => 0, + "ExtUtils::MakeMaker" => 0, + "File::Spec" => 0, + "IO::Select" => 0, + "IO::Socket::INET" => 0, + "IO::Uncompress::Gunzip" => 0, + "Socket" => 0, + "Test::More" => 0, + "URI" => 0, + "base" => 0, + "strict" => 0, + "vars" => 0, + "warnings" => 0 +); + + +unless ( eval { ExtUtils::MakeMaker->VERSION(6.63_03) } ) { + delete $WriteMakefileArgs{TEST_REQUIRES}; + delete $WriteMakefileArgs{BUILD_REQUIRES}; + $WriteMakefileArgs{PREREQ_PM} = \%FallbackPrereqs; +} + +delete $WriteMakefileArgs{CONFIGURE_REQUIRES} + unless eval { ExtUtils::MakeMaker->VERSION(6.52) }; + +WriteMakefile(%WriteMakefileArgs); diff --git a/README.md b/README.md new file mode 100644 index 0000000..9826b87 --- /dev/null +++ b/README.md @@ -0,0 +1,250 @@ +# NAME + +Net::HTTP - Low-level HTTP connection (client) + +# VERSION + +version 6.17 + +# SYNOPSIS + + use Net::HTTP; + my $s = Net::HTTP->new(Host => "www.perl.com") || die $@; + $s->write_request(GET => "/", 'User-Agent' => "Mozilla/5.0"); + my($code, $mess, %h) = $s->read_response_headers; + + while (1) { + my $buf; + my $n = $s->read_entity_body($buf, 1024); + die "read failed: $!" unless defined $n; + last unless $n; + print $buf; + } + +# DESCRIPTION + +The `Net::HTTP` class is a low-level HTTP client. An instance of the +`Net::HTTP` class represents a connection to an HTTP server. The +HTTP protocol is described in RFC 2616. The `Net::HTTP` class +supports `HTTP/1.0` and `HTTP/1.1`. + +`Net::HTTP` is a sub-class of one of `IO::Socket::IP` (IPv6+IPv4), +`IO::Socket::INET6` (IPv6+IPv4), or `IO::Socket::INET` (IPv4 only). +You can mix the methods described below with reading and writing from the +socket directly. This is not necessary a good idea, unless you know what +you are doing. + +The following methods are provided (in addition to those of +`IO::Socket::INET`): + +- $s = Net::HTTP->new( %options ) + + The `Net::HTTP` constructor method takes the same options as + `IO::Socket::INET`'s as well as these: + + Host: Initial host attribute value + KeepAlive: Initial keep_alive attribute value + SendTE: Initial send_te attribute_value + HTTPVersion: Initial http_version attribute value + PeerHTTPVersion: Initial peer_http_version attribute value + MaxLineLength: Initial max_line_length attribute value + MaxHeaderLines: Initial max_header_lines attribute value + + The `Host` option is also the default for `IO::Socket::INET`'s + `PeerAddr`. The `PeerPort` defaults to 80 if not provided. + The `PeerPort` specification can also be embedded in the `PeerAddr` + by preceding it with a ":", and closing the IPv6 address on brackets "\[\]" if + necessary: "192.0.2.1:80","\[2001:db8::1\]:80","any.example.com:80". + + The `Listen` option provided by `IO::Socket::INET`'s constructor + method is not allowed. + + If unable to connect to the given HTTP server then the constructor + returns `undef` and $@ contains the reason. After a successful + connect, a `Net:HTTP` object is returned. + +- $s->host + + Get/set the default value of the `Host` header to send. The $host + must not be set to an empty string (or `undef`) for HTTP/1.1. + +- $s->keep\_alive + + Get/set the _keep-alive_ value. If this value is TRUE then the + request will be sent with headers indicating that the server should try + to keep the connection open so that multiple requests can be sent. + + The actual headers set will depend on the value of the `http_version` + and `peer_http_version` attributes. + +- $s->send\_te + + Get/set the a value indicating if the request will be sent with a "TE" + header to indicate the transfer encodings that the server can choose to + use. The list of encodings announced as accepted by this client depends + on availability of the following modules: `Compress::Raw::Zlib` for + _deflate_, and `IO::Compress::Gunzip` for _gzip_. + +- $s->http\_version + + Get/set the HTTP version number that this client should announce. + This value can only be set to "1.0" or "1.1". The default is "1.1". + +- $s->peer\_http\_version + + Get/set the protocol version number of our peer. This value will + initially be "1.0", but will be updated by a successful + read\_response\_headers() method call. + +- $s->max\_line\_length + + Get/set a limit on the length of response line and response header + lines. The default is 8192. A value of 0 means no limit. + +- $s->max\_header\_length + + Get/set a limit on the number of header lines that a response can + have. The default is 128. A value of 0 means no limit. + +- $s->format\_request($method, $uri, %headers, \[$content\]) + + Format a request message and return it as a string. If the headers do + not include a `Host` header, then a header is inserted with the value + of the `host` attribute. Headers like `Connection` and + `Keep-Alive` might also be added depending on the status of the + `keep_alive` attribute. + + If $content is given (and it is non-empty), then a `Content-Length` + header is automatically added unless it was already present. + +- $s->write\_request($method, $uri, %headers, \[$content\]) + + Format and send a request message. Arguments are the same as for + format\_request(). Returns true if successful. + +- $s->format\_chunk( $data ) + + Returns the string to be written for the given chunk of data. + +- $s->write\_chunk($data) + + Will write a new chunk of request entity body data. This method + should only be used if the `Transfer-Encoding` header with a value of + `chunked` was sent in the request. Note, writing zero-length data is + a no-op. Use the write\_chunk\_eof() method to signal end of entity + body data. + + Returns true if successful. + +- $s->format\_chunk\_eof( %trailers ) + + Returns the string to be written for signaling EOF when a + `Transfer-Encoding` of `chunked` is used. + +- $s->write\_chunk\_eof( %trailers ) + + Will write eof marker for chunked data and optional trailers. Note + that trailers should not really be used unless is was signaled + with a `Trailer` header. + + Returns true if successful. + +- ($code, $mess, %headers) = $s->read\_response\_headers( %opts ) + + Read response headers from server and return it. The $code is the 3 + digit HTTP status code (see [HTTP::Status](https://metacpan.org/pod/HTTP::Status)) and $mess is the textual + message that came with it. Headers are then returned as key/value + pairs. Since key letter casing is not normalized and the same key can + even occur multiple times, assigning these values directly to a hash + is not wise. Only the $code is returned if this method is called in + scalar context. + + As a side effect this method updates the 'peer\_http\_version' + attribute. + + Options might be passed in as key/value pairs. There are currently + only two options supported; `laxed` and `junk_out`. + + The `laxed` option will make read\_response\_headers() more forgiving + towards servers that have not learned how to speak HTTP properly. The + `laxed` option is a boolean flag, and is enabled by passing in a TRUE + value. The `junk_out` option can be used to capture bad header lines + when `laxed` is enabled. The value should be an array reference. + Bad header lines will be pushed onto the array. + + The `laxed` option must be specified in order to communicate with + pre-HTTP/1.0 servers that don't describe the response outcome or the + data they send back with a header block. For these servers + peer\_http\_version is set to "0.9" and this method returns (200, + "Assumed OK"). + + The method will raise an exception (die) if the server does not speak + proper HTTP or if the `max_line_length` or `max_header_length` + limits are reached. If the `laxed` option is turned on and + `max_line_length` and `max_header_length` checks are turned off, + then no exception will be raised and this method will always + return a response code. + +- $n = $s->read\_entity\_body($buf, $size); + + Reads chunks of the entity body content. Basically the same interface + as for read() and sysread(), but the buffer offset argument is not + supported yet. This method should only be called after a successful + read\_response\_headers() call. + + The return value will be `undef` on read errors, 0 on EOF, -1 if no data + could be returned this time, otherwise the number of bytes assigned + to $buf. The $buf is set to "" when the return value is -1. + + You normally want to retry this call if this function returns either + \-1 or `undef` with `$!` as EINTR or EAGAIN (see [Errno](https://metacpan.org/pod/Errno)). EINTR + can happen if the application catches signals and EAGAIN can happen if + you made the socket non-blocking. + + This method will raise exceptions (die) if the server does not speak + proper HTTP. This can only happen when reading chunked data. + +- %headers = $s->get\_trailers + + After read\_entity\_body() has returned 0 to indicate end of the entity + body, you might call this method to pick up any trailers. + +- $s->\_rbuf + + Get/set the read buffer content. The read\_response\_headers() and + read\_entity\_body() methods use an internal buffer which they will look + for data before they actually sysread more from the socket itself. If + they read too much, the remaining data will be left in this buffer. + +- $s->\_rbuf\_length + + Returns the number of bytes in the read buffer. This should always be + the same as: + + length($s->_rbuf) + + but might be more efficient. + +# SUBCLASSING + +The read\_response\_headers() and read\_entity\_body() will invoke the +sysread() method when they need more data. Subclasses might want to +override this method to control how reading takes place. + +The object itself is a glob. Subclasses should avoid using hash key +names prefixed with `http_` and `io_`. + +# SEE ALSO + +[LWP](https://metacpan.org/pod/LWP), [IO::Socket::INET](https://metacpan.org/pod/IO::Socket::INET), [Net::HTTP::NB](https://metacpan.org/pod/Net::HTTP::NB) + +# AUTHOR + +Gisle Aas + +# COPYRIGHT AND LICENSE + +This software is copyright (c) 2001-2017 by Gisle Aas. + +This is free software; you can redistribute it and/or modify it under +the same terms as the Perl 5 programming language system itself. diff --git a/cpanfile b/cpanfile new file mode 100644 index 0000000..453ff9b --- /dev/null +++ b/cpanfile @@ -0,0 +1,36 @@ +requires "Carp" => "0"; +requires "Compress::Raw::Zlib" => "0"; +requires "IO::Socket::INET" => "0"; +requires "IO::Uncompress::Gunzip" => "0"; +requires "URI" => "0"; +requires "base" => "0"; +requires "perl" => "5.006002"; +requires "strict" => "0"; +requires "vars" => "0"; +requires "warnings" => "0"; +suggests "IO::Socket" => "0"; +suggests "IO::Socket::INET6" => "0"; +suggests "IO::Socket::IP" => "0"; +suggests "IO::Socket::SSL" => "2.012"; +suggests "Symbol" => "0"; + +on 'test' => sub { + requires "Data::Dumper" => "0"; + requires "ExtUtils::MakeMaker" => "0"; + requires "File::Spec" => "0"; + requires "IO::Select" => "0"; + requires "Socket" => "0"; + requires "Test::More" => "0"; +}; + +on 'test' => sub { + recommends "CPAN::Meta" => "2.120900"; +}; + +on 'configure' => sub { + requires "ExtUtils::MakeMaker" => "0"; +}; + +on 'configure' => sub { + suggests "JSON::PP" => "2.27300"; +}; diff --git a/dist.ini b/dist.ini new file mode 100644 index 0000000..8ca5c6e --- /dev/null +++ b/dist.ini @@ -0,0 +1,40 @@ +name = Net-HTTP +author = Gisle Aas +license = Perl_5 +main_module = lib/Net/HTTP.pm +copyright_holder = Gisle Aas +copyright_year = 2001-2017 +version = 6.17 + +[MetaResources] +x_IRC = irc://irc.perl.org/#lwp +x_MailingList = mailto:libwww@perl.org + +[Prereqs] +perl = 5.006002 + +[@Author::OALDERS] +-remove = AutoPrereqs +-remove = CheckChangesHasContent +-remove = MinimumPerl +-remove = PodCoverageTests +-remove = Prereqs +-remove = Test::CPAN::Changes +-remove = Test::Perl::Critic +-remove = Test::PodSpelling +-remove = Test::Synopsis +-remove = Test::TidyAll + +[AutoPrereqs] +skip = Net::SSL + +[Prereqs / RuntimeSuggests] +IO::Socket::SSL = 2.012 + +[Prereqs::Soften] +to_relationship = suggests +module = IO::Socket +module = IO::Socket::INET6 +module = IO::Socket::IP +module = IO::Socket::SSL +module = Symbol diff --git a/lib/Net/HTTP.pm b/lib/Net/HTTP.pm new file mode 100644 index 0000000..c8ff788 --- /dev/null +++ b/lib/Net/HTTP.pm @@ -0,0 +1,307 @@ +package Net::HTTP; +$Net::HTTP::VERSION = '6.17'; +use strict; +use warnings; + +use vars qw($SOCKET_CLASS); +unless ($SOCKET_CLASS) { + # Try several, in order of capability and preference + if (eval { require IO::Socket::IP }) { + $SOCKET_CLASS = "IO::Socket::IP"; # IPv4+IPv6 + } elsif (eval { require IO::Socket::INET6 }) { + $SOCKET_CLASS = "IO::Socket::INET6"; # IPv4+IPv6 + } elsif (eval { require IO::Socket::INET }) { + $SOCKET_CLASS = "IO::Socket::INET"; # IPv4 only + } else { + require IO::Socket; + $SOCKET_CLASS = "IO::Socket::INET"; + } +} +require Net::HTTP::Methods; +require Carp; + +our @ISA = ($SOCKET_CLASS, 'Net::HTTP::Methods'); + +sub new { + my $class = shift; + Carp::croak("No Host option provided") unless @_; + $class->SUPER::new(@_); +} + +sub configure { + my($self, $cnf) = @_; + $self->http_configure($cnf); +} + +sub http_connect { + my($self, $cnf) = @_; + $self->SUPER::configure($cnf); +} + +1; + +=pod + +=encoding UTF-8 + +=head1 NAME + +Net::HTTP - Low-level HTTP connection (client) + +=head1 VERSION + +version 6.17 + +=head1 SYNOPSIS + + use Net::HTTP; + my $s = Net::HTTP->new(Host => "www.perl.com") || die $@; + $s->write_request(GET => "/", 'User-Agent' => "Mozilla/5.0"); + my($code, $mess, %h) = $s->read_response_headers; + + while (1) { + my $buf; + my $n = $s->read_entity_body($buf, 1024); + die "read failed: $!" unless defined $n; + last unless $n; + print $buf; + } + +=head1 DESCRIPTION + +The C class is a low-level HTTP client. An instance of the +C class represents a connection to an HTTP server. The +HTTP protocol is described in RFC 2616. The C class +supports C and C. + +C is a sub-class of one of C (IPv6+IPv4), +C (IPv6+IPv4), or C (IPv4 only). +You can mix the methods described below with reading and writing from the +socket directly. This is not necessary a good idea, unless you know what +you are doing. + +The following methods are provided (in addition to those of +C): + +=over + +=item $s = Net::HTTP->new( %options ) + +The C constructor method takes the same options as +C's as well as these: + + Host: Initial host attribute value + KeepAlive: Initial keep_alive attribute value + SendTE: Initial send_te attribute_value + HTTPVersion: Initial http_version attribute value + PeerHTTPVersion: Initial peer_http_version attribute value + MaxLineLength: Initial max_line_length attribute value + MaxHeaderLines: Initial max_header_lines attribute value + +The C option is also the default for C's +C. The C defaults to 80 if not provided. +The C specification can also be embedded in the C +by preceding it with a ":", and closing the IPv6 address on brackets "[]" if +necessary: "192.0.2.1:80","[2001:db8::1]:80","any.example.com:80". + +The C option provided by C's constructor +method is not allowed. + +If unable to connect to the given HTTP server then the constructor +returns C and $@ contains the reason. After a successful +connect, a C object is returned. + +=item $s->host + +Get/set the default value of the C header to send. The $host +must not be set to an empty string (or C) for HTTP/1.1. + +=item $s->keep_alive + +Get/set the I value. If this value is TRUE then the +request will be sent with headers indicating that the server should try +to keep the connection open so that multiple requests can be sent. + +The actual headers set will depend on the value of the C +and C attributes. + +=item $s->send_te + +Get/set the a value indicating if the request will be sent with a "TE" +header to indicate the transfer encodings that the server can choose to +use. The list of encodings announced as accepted by this client depends +on availability of the following modules: C for +I, and C for I. + +=item $s->http_version + +Get/set the HTTP version number that this client should announce. +This value can only be set to "1.0" or "1.1". The default is "1.1". + +=item $s->peer_http_version + +Get/set the protocol version number of our peer. This value will +initially be "1.0", but will be updated by a successful +read_response_headers() method call. + +=item $s->max_line_length + +Get/set a limit on the length of response line and response header +lines. The default is 8192. A value of 0 means no limit. + +=item $s->max_header_length + +Get/set a limit on the number of header lines that a response can +have. The default is 128. A value of 0 means no limit. + +=item $s->format_request($method, $uri, %headers, [$content]) + +Format a request message and return it as a string. If the headers do +not include a C header, then a header is inserted with the value +of the C attribute. Headers like C and +C might also be added depending on the status of the +C attribute. + +If $content is given (and it is non-empty), then a C +header is automatically added unless it was already present. + +=item $s->write_request($method, $uri, %headers, [$content]) + +Format and send a request message. Arguments are the same as for +format_request(). Returns true if successful. + +=item $s->format_chunk( $data ) + +Returns the string to be written for the given chunk of data. + +=item $s->write_chunk($data) + +Will write a new chunk of request entity body data. This method +should only be used if the C header with a value of +C was sent in the request. Note, writing zero-length data is +a no-op. Use the write_chunk_eof() method to signal end of entity +body data. + +Returns true if successful. + +=item $s->format_chunk_eof( %trailers ) + +Returns the string to be written for signaling EOF when a +C of C is used. + +=item $s->write_chunk_eof( %trailers ) + +Will write eof marker for chunked data and optional trailers. Note +that trailers should not really be used unless is was signaled +with a C header. + +Returns true if successful. + +=item ($code, $mess, %headers) = $s->read_response_headers( %opts ) + +Read response headers from server and return it. The $code is the 3 +digit HTTP status code (see L) and $mess is the textual +message that came with it. Headers are then returned as key/value +pairs. Since key letter casing is not normalized and the same key can +even occur multiple times, assigning these values directly to a hash +is not wise. Only the $code is returned if this method is called in +scalar context. + +As a side effect this method updates the 'peer_http_version' +attribute. + +Options might be passed in as key/value pairs. There are currently +only two options supported; C and C. + +The C option will make read_response_headers() more forgiving +towards servers that have not learned how to speak HTTP properly. The +C option is a boolean flag, and is enabled by passing in a TRUE +value. The C option can be used to capture bad header lines +when C is enabled. The value should be an array reference. +Bad header lines will be pushed onto the array. + +The C option must be specified in order to communicate with +pre-HTTP/1.0 servers that don't describe the response outcome or the +data they send back with a header block. For these servers +peer_http_version is set to "0.9" and this method returns (200, +"Assumed OK"). + +The method will raise an exception (die) if the server does not speak +proper HTTP or if the C or C +limits are reached. If the C option is turned on and +C and C checks are turned off, +then no exception will be raised and this method will always +return a response code. + +=item $n = $s->read_entity_body($buf, $size); + +Reads chunks of the entity body content. Basically the same interface +as for read() and sysread(), but the buffer offset argument is not +supported yet. This method should only be called after a successful +read_response_headers() call. + +The return value will be C on read errors, 0 on EOF, -1 if no data +could be returned this time, otherwise the number of bytes assigned +to $buf. The $buf is set to "" when the return value is -1. + +You normally want to retry this call if this function returns either +-1 or C with C<$!> as EINTR or EAGAIN (see L). EINTR +can happen if the application catches signals and EAGAIN can happen if +you made the socket non-blocking. + +This method will raise exceptions (die) if the server does not speak +proper HTTP. This can only happen when reading chunked data. + +=item %headers = $s->get_trailers + +After read_entity_body() has returned 0 to indicate end of the entity +body, you might call this method to pick up any trailers. + +=item $s->_rbuf + +Get/set the read buffer content. The read_response_headers() and +read_entity_body() methods use an internal buffer which they will look +for data before they actually sysread more from the socket itself. If +they read too much, the remaining data will be left in this buffer. + +=item $s->_rbuf_length + +Returns the number of bytes in the read buffer. This should always be +the same as: + + length($s->_rbuf) + +but might be more efficient. + +=back + +=head1 SUBCLASSING + +The read_response_headers() and read_entity_body() will invoke the +sysread() method when they need more data. Subclasses might want to +override this method to control how reading takes place. + +The object itself is a glob. Subclasses should avoid using hash key +names prefixed with C and C. + +=head1 SEE ALSO + +L, L, L + +=head1 AUTHOR + +Gisle Aas + +=head1 COPYRIGHT AND LICENSE + +This software is copyright (c) 2001-2017 by Gisle Aas. + +This is free software; you can redistribute it and/or modify it under +the same terms as the Perl 5 programming language system itself. + +=cut + +__END__ + +# ABSTRACT: Low-level HTTP connection (client) + diff --git a/lib/Net/HTTP/Methods.pm b/lib/Net/HTTP/Methods.pm new file mode 100644 index 0000000..745a56c --- /dev/null +++ b/lib/Net/HTTP/Methods.pm @@ -0,0 +1,669 @@ +package Net::HTTP::Methods; +$Net::HTTP::Methods::VERSION = '6.17'; +use strict; +use warnings; +use URI; + +my $CRLF = "\015\012"; # "\r\n" is not portable + +*_bytes = defined(&utf8::downgrade) ? + sub { + unless (utf8::downgrade($_[0], 1)) { + require Carp; + Carp::croak("Wide character in HTTP request (bytes required)"); + } + return $_[0]; + } + : + sub { + return $_[0]; + }; + + +sub new { + my $class = shift; + unshift(@_, "Host") if @_ == 1; + my %cnf = @_; + require Symbol; + my $self = bless Symbol::gensym(), $class; + return $self->http_configure(\%cnf); +} + +sub http_configure { + my($self, $cnf) = @_; + + die "Listen option not allowed" if $cnf->{Listen}; + my $explicit_host = (exists $cnf->{Host}); + my $host = delete $cnf->{Host}; + my $peer = $cnf->{PeerAddr} || $cnf->{PeerHost}; + if (!$peer) { + die "No Host option provided" unless $host; + $cnf->{PeerAddr} = $peer = $host; + } + + # CONNECTIONS + # PREFER: port number from PeerAddr, then PeerPort, then http_default_port + my $peer_uri = URI->new("http://$peer"); + $cnf->{"PeerPort"} = $peer_uri->_port || $cnf->{PeerPort} || $self->http_default_port; + $cnf->{"PeerAddr"} = $peer_uri->host; + + # HOST header: + # If specified but blank, ignore. + # If specified with a value, add the port number + # If not specified, set to PeerAddr and port number + # ALWAYS: If IPv6 address, use [brackets] (thanks to the URI package) + # ALWAYS: omit port number if http_default_port + if (($host) || (! $explicit_host)) { + my $uri = ($explicit_host) ? URI->new("http://$host") : $peer_uri->clone; + if (!$uri->_port) { + # Always use *our* $self->http_default_port instead of URI's (Covers HTTP, HTTPS) + $uri->port( $cnf->{PeerPort} || $self->http_default_port); + } + my $host_port = $uri->host_port; # Returns host:port or [ipv6]:port + my $remove = ":" . $self->http_default_port; # we want to remove the default port number + if (substr($host_port,0-length($remove)) eq $remove) { + substr($host_port,0-length($remove)) = ""; + } + $host = $host_port; + } + + $cnf->{Proto} = 'tcp'; + + my $keep_alive = delete $cnf->{KeepAlive}; + my $http_version = delete $cnf->{HTTPVersion}; + $http_version = "1.1" unless defined $http_version; + my $peer_http_version = delete $cnf->{PeerHTTPVersion}; + $peer_http_version = "1.0" unless defined $peer_http_version; + my $send_te = delete $cnf->{SendTE}; + my $max_line_length = delete $cnf->{MaxLineLength}; + $max_line_length = 8*1024 unless defined $max_line_length; + my $max_header_lines = delete $cnf->{MaxHeaderLines}; + $max_header_lines = 128 unless defined $max_header_lines; + + return undef unless $self->http_connect($cnf); + + $self->host($host); + $self->keep_alive($keep_alive); + $self->send_te($send_te); + $self->http_version($http_version); + $self->peer_http_version($peer_http_version); + $self->max_line_length($max_line_length); + $self->max_header_lines($max_header_lines); + + ${*$self}{'http_buf'} = ""; + + return $self; +} + +sub http_default_port { + 80; +} + +# set up property accessors +for my $method (qw(host keep_alive send_te max_line_length max_header_lines peer_http_version)) { + my $prop_name = "http_" . $method; + no strict 'refs'; + *$method = sub { + my $self = shift; + my $old = ${*$self}{$prop_name}; + ${*$self}{$prop_name} = shift if @_; + return $old; + }; +} + +# we want this one to be a bit smarter +sub http_version { + my $self = shift; + my $old = ${*$self}{'http_version'}; + if (@_) { + my $v = shift; + $v = "1.0" if $v eq "1"; # float + unless ($v eq "1.0" or $v eq "1.1") { + require Carp; + Carp::croak("Unsupported HTTP version '$v'"); + } + ${*$self}{'http_version'} = $v; + } + $old; +} + +sub format_request { + my $self = shift; + my $method = shift; + my $uri = shift; + + my $content = (@_ % 2) ? pop : ""; + + for ($method, $uri) { + require Carp; + Carp::croak("Bad method or uri") if /\s/ || !length; + } + + push(@{${*$self}{'http_request_method'}}, $method); + my $ver = ${*$self}{'http_version'}; + my $peer_ver = ${*$self}{'http_peer_http_version'} || "1.0"; + + my @h; + my @connection; + my %given = (host => 0, "content-length" => 0, "te" => 0); + while (@_) { + my($k, $v) = splice(@_, 0, 2); + my $lc_k = lc($k); + if ($lc_k eq "connection") { + $v =~ s/^\s+//; + $v =~ s/\s+$//; + push(@connection, split(/\s*,\s*/, $v)); + next; + } + if (exists $given{$lc_k}) { + $given{$lc_k}++; + } + push(@h, "$k: $v"); + } + + if (length($content) && !$given{'content-length'}) { + push(@h, "Content-Length: " . length($content)); + } + + my @h2; + if ($given{te}) { + push(@connection, "TE") unless grep lc($_) eq "te", @connection; + } + elsif ($self->send_te && gunzip_ok()) { + # gzip is less wanted since the IO::Uncompress::Gunzip interface for + # it does not really allow chunked decoding to take place easily. + push(@h2, "TE: deflate,gzip;q=0.3"); + push(@connection, "TE"); + } + + unless (grep lc($_) eq "close", @connection) { + if ($self->keep_alive) { + if ($peer_ver eq "1.0") { + # from looking at Netscape's headers + push(@h2, "Keep-Alive: 300"); + unshift(@connection, "Keep-Alive"); + } + } + else { + push(@connection, "close") if $ver ge "1.1"; + } + } + push(@h2, "Connection: " . join(", ", @connection)) if @connection; + unless ($given{host}) { + my $h = ${*$self}{'http_host'}; + push(@h2, "Host: $h") if $h; + } + + return _bytes(join($CRLF, "$method $uri HTTP/$ver", @h2, @h, "", $content)); +} + + +sub write_request { + my $self = shift; + $self->print($self->format_request(@_)); +} + +sub format_chunk { + my $self = shift; + return $_[0] unless defined($_[0]) && length($_[0]); + return _bytes(sprintf("%x", length($_[0])) . $CRLF . $_[0] . $CRLF); +} + +sub write_chunk { + my $self = shift; + return 1 unless defined($_[0]) && length($_[0]); + $self->print(_bytes(sprintf("%x", length($_[0])) . $CRLF . $_[0] . $CRLF)); +} + +sub format_chunk_eof { + my $self = shift; + my @h; + while (@_) { + push(@h, sprintf "%s: %s$CRLF", splice(@_, 0, 2)); + } + return _bytes(join("", "0$CRLF", @h, $CRLF)); +} + +sub write_chunk_eof { + my $self = shift; + $self->print($self->format_chunk_eof(@_)); +} + + +sub my_read { + die if @_ > 3; + my $self = shift; + my $len = $_[1]; + for (${*$self}{'http_buf'}) { + if (length) { + $_[0] = substr($_, 0, $len, ""); + return length($_[0]); + } + else { + die "read timeout" unless $self->can_read; + return $self->sysread($_[0], $len); + } + } +} + + +sub my_readline { + my $self = shift; + my $what = shift; + for (${*$self}{'http_buf'}) { + my $max_line_length = ${*$self}{'http_max_line_length'}; + my $pos; + while (1) { + # find line ending + $pos = index($_, "\012"); + last if $pos >= 0; + die "$what line too long (limit is $max_line_length)" + if $max_line_length && length($_) > $max_line_length; + + # need to read more data to find a line ending + my $new_bytes = 0; + + READ: + { # wait until bytes start arriving + $self->can_read + or die "read timeout"; + + # consume all incoming bytes + my $bytes_read = $self->sysread($_, 1024, length); + if(defined $bytes_read) { + $new_bytes += $bytes_read; + } + elsif($!{EINTR} || $!{EAGAIN} || $!{EWOULDBLOCK}) { + redo READ; + } + else { + # if we have already accumulated some data let's at + # least return that as a line + length or die "$what read failed: $!"; + } + + # no line-ending, no new bytes + return length($_) ? substr($_, 0, length($_), "") : undef + if $new_bytes==0; + } + } + die "$what line too long ($pos; limit is $max_line_length)" + if $max_line_length && $pos > $max_line_length; + + my $line = substr($_, 0, $pos+1, ""); + $line =~ s/(\015?\012)\z// || die "Assert"; + return wantarray ? ($line, $1) : $line; + } +} + + +sub can_read { + my $self = shift; + return 1 unless defined(fileno($self)); + return 1 if $self->isa('IO::Socket::SSL') && $self->pending; + return 1 if $self->isa('Net::SSL') && $self->can('pending') && $self->pending; + + # With no timeout, wait forever. An explicit timeout of 0 can be + # used to just check if the socket is readable without waiting. + my $timeout = @_ ? shift : (${*$self}{io_socket_timeout} || undef); + + my $fbits = ''; + vec($fbits, fileno($self), 1) = 1; + SELECT: + { + my $before; + $before = time if $timeout; + my $nfound = select($fbits, undef, undef, $timeout); + if ($nfound < 0) { + if ($!{EINTR} || $!{EAGAIN} || $!{EWOULDBLOCK}) { + # don't really think EAGAIN/EWOULDBLOCK can happen here + if ($timeout) { + $timeout -= time - $before; + $timeout = 0 if $timeout < 0; + } + redo SELECT; + } + die "select failed: $!"; + } + return $nfound > 0; + } +} + + +sub _rbuf { + my $self = shift; + if (@_) { + for (${*$self}{'http_buf'}) { + my $old; + $old = $_ if defined wantarray; + $_ = shift; + return $old; + } + } + else { + return ${*$self}{'http_buf'}; + } +} + +sub _rbuf_length { + my $self = shift; + return length ${*$self}{'http_buf'}; +} + + +sub _read_header_lines { + my $self = shift; + my $junk_out = shift; + + my @headers; + my $line_count = 0; + my $max_header_lines = ${*$self}{'http_max_header_lines'}; + while (my $line = my_readline($self, 'Header')) { + if ($line =~ /^(\S+?)\s*:\s*(.*)/s) { + push(@headers, $1, $2); + } + elsif (@headers && $line =~ s/^\s+//) { + $headers[-1] .= " " . $line; + } + elsif ($junk_out) { + push(@$junk_out, $line); + } + else { + die "Bad header: '$line'\n"; + } + if ($max_header_lines) { + $line_count++; + if ($line_count >= $max_header_lines) { + die "Too many header lines (limit is $max_header_lines)"; + } + } + } + return @headers; +} + + +sub read_response_headers { + my($self, %opt) = @_; + my $laxed = $opt{laxed}; + + my($status, $eol) = my_readline($self, 'Status'); + unless (defined $status) { + die "Server closed connection without sending any data back"; + } + + my($peer_ver, $code, $message) = split(/\s+/, $status, 3); + if (!$peer_ver || $peer_ver !~ s,^HTTP/,, || $code !~ /^[1-5]\d\d$/) { + die "Bad response status line: '$status'" unless $laxed; + # assume HTTP/0.9 + ${*$self}{'http_peer_http_version'} = "0.9"; + ${*$self}{'http_status'} = "200"; + substr(${*$self}{'http_buf'}, 0, 0) = $status . ($eol || ""); + return 200 unless wantarray; + return (200, "Assumed OK"); + }; + + ${*$self}{'http_peer_http_version'} = $peer_ver; + ${*$self}{'http_status'} = $code; + + my $junk_out; + if ($laxed) { + $junk_out = $opt{junk_out} || []; + } + my @headers = $self->_read_header_lines($junk_out); + + # pick out headers that read_entity_body might need + my @te; + my $content_length; + for (my $i = 0; $i < @headers; $i += 2) { + my $h = lc($headers[$i]); + if ($h eq 'transfer-encoding') { + my $te = $headers[$i+1]; + $te =~ s/^\s+//; + $te =~ s/\s+$//; + push(@te, $te) if length($te); + } + elsif ($h eq 'content-length') { + # ignore bogus and overflow values + if ($headers[$i+1] =~ /^\s*(\d{1,15})(?:\s|$)/) { + $content_length = $1; + } + } + } + ${*$self}{'http_te'} = join(",", @te); + ${*$self}{'http_content_length'} = $content_length; + ${*$self}{'http_first_body'}++; + delete ${*$self}{'http_trailers'}; + return $code unless wantarray; + return ($code, $message, @headers); +} + + +sub read_entity_body { + my $self = shift; + my $buf_ref = \$_[0]; + my $size = $_[1]; + die "Offset not supported yet" if $_[2]; + + my $chunked; + my $bytes; + + if (${*$self}{'http_first_body'}) { + ${*$self}{'http_first_body'} = 0; + delete ${*$self}{'http_chunked'}; + delete ${*$self}{'http_bytes'}; + my $method = shift(@{${*$self}{'http_request_method'}}); + my $status = ${*$self}{'http_status'}; + if ($method eq "HEAD") { + # this response is always empty regardless of other headers + $bytes = 0; + } + elsif (my $te = ${*$self}{'http_te'}) { + my @te = split(/\s*,\s*/, lc($te)); + die "Chunked must be last Transfer-Encoding '$te'" + unless pop(@te) eq "chunked"; + pop(@te) while @te && $te[-1] eq "chunked"; # ignore repeated chunked spec + + for (@te) { + if ($_ eq "deflate" && inflate_ok()) { + #require Compress::Raw::Zlib; + my ($i, $status) = Compress::Raw::Zlib::Inflate->new(); + die "Can't make inflator: $status" unless $i; + $_ = sub { my $out; $i->inflate($_[0], \$out); $out } + } + elsif ($_ eq "gzip" && gunzip_ok()) { + #require IO::Uncompress::Gunzip; + my @buf; + $_ = sub { + push(@buf, $_[0]); + return "" unless $_[1]; + my $input = join("", @buf); + my $output; + IO::Uncompress::Gunzip::gunzip(\$input, \$output, Transparent => 0) + or die "Can't gunzip content: $IO::Uncompress::Gunzip::GunzipError"; + return \$output; + }; + } + elsif ($_ eq "identity") { + $_ = sub { $_[0] }; + } + else { + die "Can't handle transfer encoding '$te'"; + } + } + + @te = reverse(@te); + + ${*$self}{'http_te2'} = @te ? \@te : ""; + $chunked = -1; + } + elsif (defined(my $content_length = ${*$self}{'http_content_length'})) { + $bytes = $content_length; + } + elsif ($status =~ /^(?:1|[23]04)/) { + # RFC 2616 says that these responses should always be empty + # but that does not appear to be true in practice [RT#17907] + $bytes = 0; + } + else { + # XXX Multi-Part types are self delimiting, but RFC 2616 says we + # only has to deal with 'multipart/byteranges' + + # Read until EOF + } + } + else { + $chunked = ${*$self}{'http_chunked'}; + $bytes = ${*$self}{'http_bytes'}; + } + + if (defined $chunked) { + # The state encoded in $chunked is: + # $chunked == 0: read CRLF after chunk, then chunk header + # $chunked == -1: read chunk header + # $chunked > 0: bytes left in current chunk to read + + if ($chunked <= 0) { + my $line = my_readline($self, 'Entity body'); + if ($chunked == 0) { + die "Missing newline after chunk data: '$line'" + if !defined($line) || $line ne ""; + $line = my_readline($self, 'Entity body'); + } + die "EOF when chunk header expected" unless defined($line); + my $chunk_len = $line; + $chunk_len =~ s/;.*//; # ignore potential chunk parameters + unless ($chunk_len =~ /^([\da-fA-F]+)\s*$/) { + die "Bad chunk-size in HTTP response: $line"; + } + $chunked = hex($1); + ${*$self}{'http_chunked'} = $chunked; + if ($chunked == 0) { + ${*$self}{'http_trailers'} = [$self->_read_header_lines]; + $$buf_ref = ""; + + my $n = 0; + if (my $transforms = delete ${*$self}{'http_te2'}) { + for (@$transforms) { + $$buf_ref = &$_($$buf_ref, 1); + } + $n = length($$buf_ref); + } + + # in case somebody tries to read more, make sure we continue + # to return EOF + delete ${*$self}{'http_chunked'}; + ${*$self}{'http_bytes'} = 0; + + return $n; + } + } + + my $n = $chunked; + $n = $size if $size && $size < $n; + $n = my_read($self, $$buf_ref, $n); + return undef unless defined $n; + + ${*$self}{'http_chunked'} = $chunked - $n; + + if ($n > 0) { + if (my $transforms = ${*$self}{'http_te2'}) { + for (@$transforms) { + $$buf_ref = &$_($$buf_ref, 0); + } + $n = length($$buf_ref); + $n = -1 if $n == 0; + } + } + return $n; + } + elsif (defined $bytes) { + unless ($bytes) { + $$buf_ref = ""; + return 0; + } + my $n = $bytes; + $n = $size if $size && $size < $n; + $n = my_read($self, $$buf_ref, $n); + ${*$self}{'http_bytes'} = defined $n ? $bytes - $n : $bytes; + return $n; + } + else { + # read until eof + $size ||= 8*1024; + return my_read($self, $$buf_ref, $size); + } +} + +sub get_trailers { + my $self = shift; + @{${*$self}{'http_trailers'} || []}; +} + +BEGIN { +my $gunzip_ok; +my $inflate_ok; + +sub gunzip_ok { + return $gunzip_ok if defined $gunzip_ok; + + # Try to load IO::Uncompress::Gunzip. + local $@; + local $SIG{__DIE__}; + $gunzip_ok = 0; + + eval { + require IO::Uncompress::Gunzip; + $gunzip_ok++; + }; + + return $gunzip_ok; +} + +sub inflate_ok { + return $inflate_ok if defined $inflate_ok; + + # Try to load Compress::Raw::Zlib. + local $@; + local $SIG{__DIE__}; + $inflate_ok = 0; + + eval { + require Compress::Raw::Zlib; + $inflate_ok++; + }; + + return $inflate_ok; +} + +} # BEGIN + +1; + +=pod + +=encoding UTF-8 + +=head1 NAME + +Net::HTTP::Methods - Methods shared by Net::HTTP and Net::HTTPS + +=head1 VERSION + +version 6.17 + +=head1 AUTHOR + +Gisle Aas + +=head1 COPYRIGHT AND LICENSE + +This software is copyright (c) 2001-2017 by Gisle Aas. + +This is free software; you can redistribute it and/or modify it under +the same terms as the Perl 5 programming language system itself. + +=cut + +__END__ + +# ABSTRACT: Methods shared by Net::HTTP and Net::HTTPS diff --git a/lib/Net/HTTP/NB.pm b/lib/Net/HTTP/NB.pm new file mode 100644 index 0000000..b69a502 --- /dev/null +++ b/lib/Net/HTTP/NB.pm @@ -0,0 +1,121 @@ +package Net::HTTP::NB; +$Net::HTTP::NB::VERSION = '6.17'; +use strict; +use warnings; + +use base 'Net::HTTP'; + +sub can_read { + return 1; +} + +sub sysread { + my $self = $_[0]; + if (${*$self}{'httpnb_read_count'}++) { + ${*$self}{'http_buf'} = ${*$self}{'httpnb_save'}; + die "Multi-read\n"; + } + my $buf; + my $offset = $_[3] || 0; + my $n = sysread($self, $_[1], $_[2], $offset); + ${*$self}{'httpnb_save'} .= substr($_[1], $offset); + return $n; +} + +sub read_response_headers { + my $self = shift; + ${*$self}{'httpnb_read_count'} = 0; + ${*$self}{'httpnb_save'} = ${*$self}{'http_buf'}; + my @h = eval { $self->SUPER::read_response_headers(@_) }; + if ($@) { + return if $@ eq "Multi-read\n"; + die; + } + return @h; +} + +sub read_entity_body { + my $self = shift; + ${*$self}{'httpnb_read_count'} = 0; + ${*$self}{'httpnb_save'} = ${*$self}{'http_buf'}; + # XXX I'm not so sure this does the correct thing in case of + # transfer-encoding transforms + my $n = eval { $self->SUPER::read_entity_body(@_); }; + if ($@) { + $_[0] = ""; + return -1; + } + return $n; +} + +1; + +=pod + +=encoding UTF-8 + +=head1 NAME + +Net::HTTP::NB - Non-blocking HTTP client + +=head1 VERSION + +version 6.17 + +=head1 SYNOPSIS + + use Net::HTTP::NB; + my $s = Net::HTTP::NB->new(Host => "www.perl.com") || die $@; + $s->write_request(GET => "/"); + + use IO::Select; + my $sel = IO::Select->new($s); + + READ_HEADER: { + die "Header timeout" unless $sel->can_read(10); + my($code, $mess, %h) = $s->read_response_headers; + redo READ_HEADER unless $code; + } + + while (1) { + die "Body timeout" unless $sel->can_read(10); + my $buf; + my $n = $s->read_entity_body($buf, 1024); + last unless $n; + print $buf; + } + +=head1 DESCRIPTION + +Same interface as C but it will never try multiple reads +when the read_response_headers() or read_entity_body() methods are +invoked. This make it possible to multiplex multiple Net::HTTP::NB +using select without risk blocking. + +If read_response_headers() did not see enough data to complete the +headers an empty list is returned. + +If read_entity_body() did not see new entity data in its read +the value -1 is returned. + +=head1 SEE ALSO + +L + +=head1 AUTHOR + +Gisle Aas + +=head1 COPYRIGHT AND LICENSE + +This software is copyright (c) 2001-2017 by Gisle Aas. + +This is free software; you can redistribute it and/or modify it under +the same terms as the Perl 5 programming language system itself. + +=cut + +__END__ + +#ABSTRACT: Non-blocking HTTP client + diff --git a/lib/Net/HTTPS.pm b/lib/Net/HTTPS.pm new file mode 100644 index 0000000..aa8e1c1 --- /dev/null +++ b/lib/Net/HTTPS.pm @@ -0,0 +1,135 @@ +package Net::HTTPS; +$Net::HTTPS::VERSION = '6.17'; +use strict; +use warnings; + +# Figure out which SSL implementation to use +use vars qw($SSL_SOCKET_CLASS); +if ($SSL_SOCKET_CLASS) { + # somebody already set it +} +elsif ($SSL_SOCKET_CLASS = $ENV{PERL_NET_HTTPS_SSL_SOCKET_CLASS}) { + unless ($SSL_SOCKET_CLASS =~ /^(IO::Socket::SSL|Net::SSL)\z/) { + die "Bad socket class [$SSL_SOCKET_CLASS]"; + } + eval "require $SSL_SOCKET_CLASS"; + die $@ if $@; +} +elsif ($IO::Socket::SSL::VERSION) { + $SSL_SOCKET_CLASS = "IO::Socket::SSL"; # it was already loaded +} +elsif ($Net::SSL::VERSION) { + $SSL_SOCKET_CLASS = "Net::SSL"; +} +else { + eval { require IO::Socket::SSL; }; + if ($@) { + my $old_errsv = $@; + eval { + require Net::SSL; # from Crypt-SSLeay + }; + if ($@) { + $old_errsv =~ s/\s\(\@INC contains:.*\)/)/g; + die $old_errsv . $@; + } + $SSL_SOCKET_CLASS = "Net::SSL"; + } + else { + $SSL_SOCKET_CLASS = "IO::Socket::SSL"; + } +} + +require Net::HTTP::Methods; + +our @ISA=($SSL_SOCKET_CLASS, 'Net::HTTP::Methods'); + +sub configure { + my($self, $cnf) = @_; + $self->http_configure($cnf); +} + +sub http_connect { + my($self, $cnf) = @_; + if ($self->isa("Net::SSL")) { + if ($cnf->{SSL_verify_mode}) { + if (my $f = $cnf->{SSL_ca_file}) { + $ENV{HTTPS_CA_FILE} = $f; + } + if (my $f = $cnf->{SSL_ca_path}) { + $ENV{HTTPS_CA_DIR} = $f; + } + } + if ($cnf->{SSL_verifycn_scheme}) { + $@ = "Net::SSL from Crypt-SSLeay can't verify hostnames; either install IO::Socket::SSL or turn off verification by setting the PERL_LWP_SSL_VERIFY_HOSTNAME environment variable to 0"; + return undef; + } + } + $self->SUPER::configure($cnf); +} + +sub http_default_port { + 443; +} + +if ($SSL_SOCKET_CLASS eq "Net::SSL") { + # The underlying SSLeay classes fails to work if the socket is + # placed in non-blocking mode. This override of the blocking + # method makes sure it stays the way it was created. + *blocking = sub { }; +} + +1; + +=pod + +=encoding UTF-8 + +=head1 NAME + +Net::HTTPS - Low-level HTTP over SSL/TLS connection (client) + +=head1 VERSION + +version 6.17 + +=head1 DESCRIPTION + +The C is a low-level HTTP over SSL/TLS client. The interface is the same +as the interface for C, but the constructor takes additional parameters +as accepted by L. The C object is an C +too, which makes it inherit additional methods from that base class. + +For historical reasons this module also supports using C (from the +Crypt-SSLeay distribution) as its SSL driver and base class. This base is +automatically selected if available and C isn't. You might +also force which implementation to use by setting $Net::HTTPS::SSL_SOCKET_CLASS +before loading this module. If not set this variable is initialized from the +C environment variable. + +=head1 ENVIRONMENT + +You might set the C environment variable to the name +of the base SSL implementation (and Net::HTTPS base class) to use. The default +is C. Currently the only other supported value is C. + +=head1 SEE ALSO + +L, L + +=head1 AUTHOR + +Gisle Aas + +=head1 COPYRIGHT AND LICENSE + +This software is copyright (c) 2001-2017 by Gisle Aas. + +This is free software; you can redistribute it and/or modify it under +the same terms as the Perl 5 programming language system itself. + +=cut + +__END__ + +#ABSTRACT: Low-level HTTP over SSL/TLS connection (client) + diff --git a/perlcriticrc b/perlcriticrc new file mode 100644 index 0000000..7819a28 --- /dev/null +++ b/perlcriticrc @@ -0,0 +1,86 @@ +severity = 3 +verbose = 11 + +theme = core + pbp + bugs + maintenance + cosmetic + complexity + security + tests + moose + +exclude = Subroutines::ProhibitCallsToUndeclaredSubs + +[BuiltinFunctions::ProhibitStringySplit] +severity = 3 + +[CodeLayout::RequireTrailingCommas] +severity = 3 + +[ControlStructures::ProhibitCStyleForLoops] +severity = 3 + +[InputOutput::RequireCheckedSyscalls] +functions = :builtins +exclude_functions = sleep +severity = 3 + +[Moose::RequireCleanNamespace] +modules = Moose Moose::Role MooseX::Role::Parameterized Moose::Util::TypeConstraints +cleaners = namespace::autoclean + +[NamingConventions::Capitalization] +package_exemptions = [A-Z]\w+|minFraud +file_lexical_variables = [A-Z]\w+|[^A-Z]+ +global_variables = :starts_with_upper +scoped_lexical_variables = [A-Z]\w+|[^A-Z]+ +severity = 3 + +# Given our code base, leaving this at 5 would be a huge pain +[Subroutines::ProhibitManyArgs] +max_arguments = 10 + +[RegularExpressions::ProhibitComplexRegexes] +max_characters = 200 + +[RegularExpressions::ProhibitUnusualDelimiters] +severity = 3 + +[Subroutines::ProhibitUnusedPrivateSubroutines] +private_name_regex = _(?!build)\w+ +skip_when_using = Moo::Role Moose::Role MooseX::Role::Parameterized Role::Tiny Test::Class::Moose::Role + +[TestingAndDebugging::ProhibitNoWarnings] +allow = redefine + +[ValuesAndExpressions::ProhibitEmptyQuotes] +severity = 3 + +[ValuesAndExpressions::ProhibitInterpolationOfLiterals] +severity = 3 + +[ValuesAndExpressions::RequireUpperCaseHeredocTerminator] +severity = 3 + +[Variables::ProhibitPackageVars] +add_packages = Test::Builder + +[TestingAndDebugging::RequireUseStrict] + +[TestingAndDebugging::RequireUseWarnings] + +[-ControlStructures::ProhibitCascadingIfElse] + +[-ErrorHandling::RequireCarping] +[-InputOutput::RequireBriefOpen] + +[-ValuesAndExpressions::ProhibitConstantPragma] + +# No need for /xsm everywhere +[-RegularExpressions::RequireDotMatchAnything] +[-RegularExpressions::RequireExtendedFormatting] +[-RegularExpressions::RequireLineBoundaryMatching] + +[-Subroutines::ProhibitExplicitReturnUndef] + +# http://stackoverflow.com/questions/2275317/why-does-perlcritic-dislike-using-shift-to-populate-subroutine-variables +[-Subroutines::RequireArgUnpacking] + +[-Subroutines::RequireFinalReturn] + +# "use v5.14" is more readable than "use 5.014" +[-ValuesAndExpressions::ProhibitVersionStrings] diff --git a/perltidyrc b/perltidyrc new file mode 100644 index 0000000..b7ed624 --- /dev/null +++ b/perltidyrc @@ -0,0 +1,12 @@ +--blank-lines-before-packages=0 +--iterations=2 +--no-outdent-long-comments +-b +-bar +-boc +-ci=4 +-i=4 +-l=78 +-nolq +-se +-wbb="% + - * / x != == >= <= =~ !~ < > | & >= < = **= += *= &= <<= &&= -= /= |= >>= ||= .= %= ^= x=" diff --git a/t/00-report-prereqs.dd b/t/00-report-prereqs.dd new file mode 100644 index 0000000..54095fb --- /dev/null +++ b/t/00-report-prereqs.dd @@ -0,0 +1,46 @@ +do { my $x = { + 'configure' => { + 'requires' => { + 'ExtUtils::MakeMaker' => '0' + }, + 'suggests' => { + 'JSON::PP' => '2.27300' + } + }, + 'runtime' => { + 'requires' => { + 'Carp' => '0', + 'Compress::Raw::Zlib' => '0', + 'IO::Socket::INET' => '0', + 'IO::Uncompress::Gunzip' => '0', + 'URI' => '0', + 'base' => '0', + 'perl' => '5.006002', + 'strict' => '0', + 'vars' => '0', + 'warnings' => '0' + }, + 'suggests' => { + 'IO::Socket' => '0', + 'IO::Socket::INET6' => '0', + 'IO::Socket::IP' => '0', + 'IO::Socket::SSL' => '2.012', + 'Symbol' => '0' + } + }, + 'test' => { + 'recommends' => { + 'CPAN::Meta' => '2.120900' + }, + 'requires' => { + 'Data::Dumper' => '0', + 'ExtUtils::MakeMaker' => '0', + 'File::Spec' => '0', + 'IO::Select' => '0', + 'Socket' => '0', + 'Test::More' => '0' + } + } + }; + $x; + } \ No newline at end of file diff --git a/t/00-report-prereqs.t b/t/00-report-prereqs.t new file mode 100644 index 0000000..c72183a --- /dev/null +++ b/t/00-report-prereqs.t @@ -0,0 +1,193 @@ +#!perl + +use strict; +use warnings; + +# This test was generated by Dist::Zilla::Plugin::Test::ReportPrereqs 0.027 + +use Test::More tests => 1; + +use ExtUtils::MakeMaker; +use File::Spec; + +# from $version::LAX +my $lax_version_re = + qr/(?: undef | (?: (?:[0-9]+) (?: \. | (?:\.[0-9]+) (?:_[0-9]+)? )? + | + (?:\.[0-9]+) (?:_[0-9]+)? + ) | (?: + v (?:[0-9]+) (?: (?:\.[0-9]+)+ (?:_[0-9]+)? )? + | + (?:[0-9]+)? (?:\.[0-9]+){2,} (?:_[0-9]+)? + ) + )/x; + +# hide optional CPAN::Meta modules from prereq scanner +# and check if they are available +my $cpan_meta = "CPAN::Meta"; +my $cpan_meta_pre = "CPAN::Meta::Prereqs"; +my $HAS_CPAN_META = eval "require $cpan_meta; $cpan_meta->VERSION('2.120900')" && eval "require $cpan_meta_pre"; ## no critic + +# Verify requirements? +my $DO_VERIFY_PREREQS = 1; + +sub _max { + my $max = shift; + $max = ( $_ > $max ) ? $_ : $max for @_; + return $max; +} + +sub _merge_prereqs { + my ($collector, $prereqs) = @_; + + # CPAN::Meta::Prereqs object + if (ref $collector eq $cpan_meta_pre) { + return $collector->with_merged_prereqs( + CPAN::Meta::Prereqs->new( $prereqs ) + ); + } + + # Raw hashrefs + for my $phase ( keys %$prereqs ) { + for my $type ( keys %{ $prereqs->{$phase} } ) { + for my $module ( keys %{ $prereqs->{$phase}{$type} } ) { + $collector->{$phase}{$type}{$module} = $prereqs->{$phase}{$type}{$module}; + } + } + } + + return $collector; +} + +my @include = qw( + +); + +my @exclude = qw( + +); + +# Add static prereqs to the included modules list +my $static_prereqs = do './t/00-report-prereqs.dd'; + +# Merge all prereqs (either with ::Prereqs or a hashref) +my $full_prereqs = _merge_prereqs( + ( $HAS_CPAN_META ? $cpan_meta_pre->new : {} ), + $static_prereqs +); + +# Add dynamic prereqs to the included modules list (if we can) +my ($source) = grep { -f } 'MYMETA.json', 'MYMETA.yml'; +my $cpan_meta_error; +if ( $source && $HAS_CPAN_META + && (my $meta = eval { CPAN::Meta->load_file($source) } ) +) { + $full_prereqs = _merge_prereqs($full_prereqs, $meta->prereqs); +} +else { + $cpan_meta_error = $@; # capture error from CPAN::Meta->load_file($source) + $source = 'static metadata'; +} + +my @full_reports; +my @dep_errors; +my $req_hash = $HAS_CPAN_META ? $full_prereqs->as_string_hash : $full_prereqs; + +# Add static includes into a fake section +for my $mod (@include) { + $req_hash->{other}{modules}{$mod} = 0; +} + +for my $phase ( qw(configure build test runtime develop other) ) { + next unless $req_hash->{$phase}; + next if ($phase eq 'develop' and not $ENV{AUTHOR_TESTING}); + + for my $type ( qw(requires recommends suggests conflicts modules) ) { + next unless $req_hash->{$phase}{$type}; + + my $title = ucfirst($phase).' '.ucfirst($type); + my @reports = [qw/Module Want Have/]; + + for my $mod ( sort keys %{ $req_hash->{$phase}{$type} } ) { + next if $mod eq 'perl'; + next if grep { $_ eq $mod } @exclude; + + my $file = $mod; + $file =~ s{::}{/}g; + $file .= ".pm"; + my ($prefix) = grep { -e File::Spec->catfile($_, $file) } @INC; + + my $want = $req_hash->{$phase}{$type}{$mod}; + $want = "undef" unless defined $want; + $want = "any" if !$want && $want == 0; + + my $req_string = $want eq 'any' ? 'any version required' : "version '$want' required"; + + if ($prefix) { + my $have = MM->parse_version( File::Spec->catfile($prefix, $file) ); + $have = "undef" unless defined $have; + push @reports, [$mod, $want, $have]; + + if ( $DO_VERIFY_PREREQS && $HAS_CPAN_META && $type eq 'requires' ) { + if ( $have !~ /\A$lax_version_re\z/ ) { + push @dep_errors, "$mod version '$have' cannot be parsed ($req_string)"; + } + elsif ( ! $full_prereqs->requirements_for( $phase, $type )->accepts_module( $mod => $have ) ) { + push @dep_errors, "$mod version '$have' is not in required range '$want'"; + } + } + } + else { + push @reports, [$mod, $want, "missing"]; + + if ( $DO_VERIFY_PREREQS && $type eq 'requires' ) { + push @dep_errors, "$mod is not installed ($req_string)"; + } + } + } + + if ( @reports ) { + push @full_reports, "=== $title ===\n\n"; + + my $ml = _max( map { length $_->[0] } @reports ); + my $wl = _max( map { length $_->[1] } @reports ); + my $hl = _max( map { length $_->[2] } @reports ); + + if ($type eq 'modules') { + splice @reports, 1, 0, ["-" x $ml, "", "-" x $hl]; + push @full_reports, map { sprintf(" %*s %*s\n", -$ml, $_->[0], $hl, $_->[2]) } @reports; + } + else { + splice @reports, 1, 0, ["-" x $ml, "-" x $wl, "-" x $hl]; + push @full_reports, map { sprintf(" %*s %*s %*s\n", -$ml, $_->[0], $wl, $_->[1], $hl, $_->[2]) } @reports; + } + + push @full_reports, "\n"; + } + } +} + +if ( @full_reports ) { + diag "\nVersions for all modules listed in $source (including optional ones):\n\n", @full_reports; +} + +if ( $cpan_meta_error || @dep_errors ) { + diag "\n*** WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING ***\n"; +} + +if ( $cpan_meta_error ) { + my ($orig_source) = grep { -f } 'MYMETA.json', 'MYMETA.yml'; + diag "\nCPAN::Meta->load_file('$orig_source') failed with: $cpan_meta_error\n"; +} + +if ( @dep_errors ) { + diag join("\n", + "\nThe following REQUIRED prerequisites were not satisfied:\n", + @dep_errors, + "\n" + ); +} + +pass; + +# vim: ts=4 sts=4 sw=4 et: diff --git a/t/http-nb.t b/t/http-nb.t new file mode 100644 index 0000000..0c7b187 --- /dev/null +++ b/t/http-nb.t @@ -0,0 +1,52 @@ +use strict; +use warnings; +use Test::More; +plan skip_all => "This test doesn't work on Windows" if $^O eq "MSWin32"; + +plan tests => 14; + +require Net::HTTP::NB; +use IO::Socket::INET; +use Data::Dumper; +use IO::Select; +use Socket qw(TCP_NODELAY); +my $buf; + +# bind a random TCP port for testing +my %lopts = ( + LocalAddr => "127.0.0.1", + LocalPort => 0, + Proto => "tcp", + ReuseAddr => 1, + Listen => 1024 +); + +my $srv = IO::Socket::INET->new(%lopts); +is(ref($srv), "IO::Socket::INET"); +my $host = $srv->sockhost . ':' . $srv->sockport; +my $nb = Net::HTTP::NB->new(Host => $host, Blocking => 0); +is(ref($nb), "Net::HTTP::NB"); +is(IO::Select->new($nb)->can_write(3), 1); + +ok($nb->write_request("GET", "/")); +my $acc = $srv->accept; +is(ref($acc), "IO::Socket::INET"); +$acc->sockopt(TCP_NODELAY, 1); +ok($acc->sysread($buf, 4096)); +ok($acc->syswrite("HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\n")); + +is(1, IO::Select->new($nb)->can_read(3)); +my @r = $nb->read_response_headers; +is($r[0], 200); + +# calling read_entity_body before response body is readable causes +# EOF to never happen eventually +ok(!defined($nb->read_entity_body($buf, 4096)) && $!{EAGAIN}); + +is($acc->syswrite("hello"), 5, "server wrote response body"); + +is(IO::Select->new($nb)->can_read(3), 1, "client body is readable"); +is($nb->read_entity_body($buf, 4096), 5, "client gets 5 bytes"); + +# this fails if we got EAGAIN from the first read_entity_body call: +is($nb->read_entity_body($buf, 4096), 0, "client gets EOF"); diff --git a/t/http.t b/t/http.t new file mode 100644 index 0000000..358f15d --- /dev/null +++ b/t/http.t @@ -0,0 +1,206 @@ +use strict; +use warnings; +use Test::More; + +plan tests => 37; +#use Data::Dump (); + +my $CRLF = "\015\012"; +my $LF = "\012"; + +{ + package HTTP; + use base 'Net::HTTP::Methods'; + + my %servers = ( + a => { "/" => "HTTP/1.0 200 OK${CRLF}Content-Type: text/plain${CRLF}Content-Length: 6${CRLF}${CRLF}Hello\n", + "/bad1" => "HTTP/1.0 200 OK${LF}Server: foo${LF}HTTP/1.0 200 OK${LF}Content-type: text/foo${LF}${LF}abc\n", + "/09" => "Hello${CRLF}World!${CRLF}", + "/chunked" => "HTTP/1.1 200 OK${CRLF}Transfer-Encoding: chunked${CRLF}${CRLF}0002; foo=3; bar${CRLF}He${CRLF}1${CRLF}l${CRLF}2${CRLF}lo${CRLF}0000${CRLF}Content-MD5: xxx${CRLF}${CRLF}", + "/chunked,chunked" => "HTTP/1.1 200 OK${CRLF}Transfer-Encoding: chunked${CRLF}Transfer-Encoding: chunked${CRLF}${CRLF}0002; foo=3; bar${CRLF}He${CRLF}1${CRLF}l${CRLF}2${CRLF}lo${CRLF}0000${CRLF}Content-MD5: xxx${CRLF}${CRLF}", + "/head" => "HTTP/1.1 200 OK${CRLF}Content-Length: 16${CRLF}Content-Type: text/plain${CRLF}${CRLF}", + "/colon-header" => "HTTP/1.1 200 OK${CRLF}Content-Type: text/plain${CRLF}Content-Length: 6${CRLF}Bad-Header: :foo${CRLF}${CRLF}Hello\n", + }, + ); + + sub http_connect { + my($self, $cnf) = @_; + my $server = $servers{$cnf->{PeerAddr}} || return undef; + ${*$self}{server} = $server; + ${*$self}{read_chunk_size} = $cnf->{ReadChunkSize}; + return $self; + } + + sub print { + my $self = shift; + #Data::Dump::dump("PRINT", @_); + my $in = shift; + my($method, $uri) = split(' ', $in); + + my $out; + if ($method eq "TRACE") { + my $len = length($in); + $out = "HTTP/1.0 200 OK${CRLF}Content-Length: $len${CRLF}" . + "Content-Type: message/http${CRLF}${CRLF}" . + $in; + } + else { + $out = ${*$self}{server}{$uri}; + $out = "HTTP/1.0 404 Not found${CRLF}${CRLF}" unless defined $out; + } + + ${*$self}{out} .= $out; + return 1; + } + + sub sysread { + my $self = shift; + #Data::Dump::dump("SYSREAD", @_); + my $length = $_[1]; + my $offset = $_[2] || 0; + + if (my $read_chunk_size = ${*$self}{read_chunk_size}) { + $length = $read_chunk_size if $read_chunk_size < $length; + } + + my $data = substr(${*$self}{out}, 0, $length, ""); + return 0 unless length($data); + + $_[0] = "" unless defined $_[0]; + substr($_[0], $offset) = $data; + return length($data); + } + + # ---------------- + + sub request { + my($self, $method, $uri, $headers, $opt) = @_; + $headers ||= []; + $opt ||= {}; + + my($code, $message, @h); + my $buf = ""; + eval { + $self->write_request($method, $uri, @$headers) || die "Can't write request"; + ($code, $message, @h) = $self->read_response_headers(%$opt); + + my $tmp; + my $n; + while ($n = $self->read_entity_body($tmp, 32)) { + #Data::Dump::dump($tmp, $n); + $buf .= $tmp; + } + + push(@h, $self->get_trailers); + + }; + + my %res = ( code => $code, + message => $message, + headers => \@h, + content => $buf, + ); + + if ($@) { + $res{error} = $@; + } + + return \%res; + } +} + +# Start testing +my $h; +my $res; + +$h = HTTP->new(Host => "a", KeepAlive => 1) || die; +$res = $h->request(GET => "/"); + +#Data::Dump::dump($res); + +is($res->{code}, 200); +is($res->{content}, "Hello\n"); + +$res = $h->request(GET => "/404"); +is($res->{code}, 404); + +$res = $h->request(TRACE => "/foo"); +is($res->{code}, 200); +is($res->{content}, "TRACE /foo HTTP/1.1${CRLF}Keep-Alive: 300${CRLF}Connection: Keep-Alive${CRLF}Host: a${CRLF}${CRLF}"); + +# try to turn off keep alive +$h->keep_alive(0); +$res = $h->request(TRACE => "/foo"); +is($res->{code}, "200"); +is($res->{content}, "TRACE /foo HTTP/1.1${CRLF}Connection: close${CRLF}Host: a${CRLF}${CRLF}"); + +# try a bad one +# It's bad because 2nd 'HTTP/1.0 200' is illegal. But passes anyway if laxed => 1. +$res = $h->request(GET => "/bad1", [], {laxed => 1}); +is($res->{code}, "200"); +is($res->{message}, "OK"); +is("@{$res->{headers}}", "Server foo Content-type text/foo"); +is($res->{content}, "abc\n"); + +$res = $h->request(GET => "/bad1"); +like($res->{error}, qr/Bad header/); +ok(!$res->{code}); +$h = undef; # it is in a bad state now + +$h = HTTP->new("a") || die; # reconnect +$res = $h->request(GET => "/09", [], {laxed => 1}); +is($res->{code}, "200"); +is($res->{message}, "Assumed OK"); +is($res->{content}, "Hello${CRLF}World!${CRLF}"); +is($h->peer_http_version, "0.9"); + +$res = $h->request(GET => "/09"); +like($res->{error}, qr/^Bad response status line: 'Hello'/); +$h = undef; # it's in a bad state again + +$h = HTTP->new(Host => "a", KeepAlive => 1, ReadChunkSize => 1) || die; # reconnect +$res = $h->request(GET => "/chunked"); +is($res->{code}, 200); +is($res->{content}, "Hello"); +is("@{$res->{headers}}", "Transfer-Encoding chunked Content-MD5 xxx"); + +# once more +$res = $h->request(GET => "/chunked"); +is($res->{code}, "200"); +is($res->{content}, "Hello"); +is("@{$res->{headers}}", "Transfer-Encoding chunked Content-MD5 xxx"); + +# Test bogus headers. Chunked appearing twice is illegal, but happens anyway sometimes. [RT#77240] +$res = $h->request(GET => "/chunked,chunked"); +is($res->{code}, "200"); +is($res->{content}, "Hello"); +is("@{$res->{headers}}", "Transfer-Encoding chunked Transfer-Encoding chunked Content-MD5 xxx"); + +# test head +$res = $h->request(HEAD => "/head"); +is($res->{code}, "200"); +is($res->{content}, ""); +is("@{$res->{headers}}", "Content-Length 16 Content-Type text/plain"); + +$res = $h->request(GET => "/"); +is($res->{code}, "200"); +is($res->{content}, "Hello\n"); + +$h = HTTP->new(Host => undef, PeerAddr => "a", ); +$h->http_version("1.0"); +ok(!defined $h->host); +$res = $h->request(TRACE => "/"); +is($res->{code}, "200"); +is($res->{content}, "TRACE / HTTP/1.0\r\n\r\n"); + +# check that headers with colons at the start of values don't break +$res = $h->request(GET => '/colon-header'); +is("@{$res->{headers}}", "Content-Type text/plain Content-Length 6 Bad-Header :foo"); + +require Net::HTTP; +eval { + $h = Net::HTTP->new; +}; +print "# $@"; +ok($@); + diff --git a/t/live-https.t b/t/live-https.t new file mode 100644 index 0000000..10a8544 --- /dev/null +++ b/t/live-https.t @@ -0,0 +1,71 @@ +BEGIN { + if ( $ENV{NO_NETWORK_TESTING} ) { + print "1..0 # SKIP Live tests disabled due to NO_NETWORK_TESTING\n"; + exit; + } + eval { + require IO::Socket::INET; + my $s = IO::Socket::INET->new( + PeerHost => "www.cpan.org:443", + Timeout => 5, + ); + die "Can't connect: $@" unless $s; + }; + if ($@) { + print "1..0 # SKIP Can't connect to www.cpan.org:443\n"; + print $@; + exit; + } + + unless ( eval { require IO::Socket::SSL } || eval { require Net::SSL } ) { + print "1..0 # SKIP IO::Socket::SSL or Net::SSL not available\n"; + print $@; + exit; + } +} + +use strict; +use warnings; +use Test::More; +plan tests => 6; + +use Net::HTTPS; + +my $s = Net::HTTPS->new( + Host => "www.cpan.org", + KeepAlive => 1, + Timeout => 15, + PeerHTTPVersion => "1.1", + MaxLineLength => 512 +) || die "$@"; + +for ( 1 .. 2 ) { + $s->write_request( + GET => "/", + 'User-Agent' => 'Mozilla/5.0', + 'Accept-Language' => 'no,en', + Accept => '*/*' + ); + + my ( $code, $mess, %h ) = $s->read_response_headers; + print "# ----------------------------\n"; + print "# $code $mess\n"; + for ( sort keys %h ) { + print "# $_: $h{$_}\n"; + } + print "#\n"; + + my $buf; + while (1) { + my $tmp; + my $n = $s->read_entity_body( $tmp, 20 ); + last unless $n; + $buf .= $tmp; + } + $buf =~ s/\r//g; + + ok( $code == 302 || $code == 200, 'success' ); + like( $h{'Content-Type'}, qr{text/html} ); + like( $buf, qr{}i ); +} + diff --git a/t/live.t b/t/live.t new file mode 100644 index 0000000..45b2ba8 --- /dev/null +++ b/t/live.t @@ -0,0 +1,64 @@ +BEGIN { + if ( $ENV{NO_NETWORK_TESTING} ) { + print "1..0 # SKIP Live tests disabled due to NO_NETWORK_TESTING\n"; + exit; + } + eval { + require IO::Socket::INET; + my $s = IO::Socket::INET->new( + PeerHost => "www.neverssl.com:80", + Timeout => 5, + ); + die "Can't connect: $@" unless $s; + }; + if ($@) { + print "1..0 # SKIP Can't connect to www.neverssl.com\n"; + print $@; + exit; + } +} + +use strict; +use warnings; +use Test::More; +plan tests => 6; + +use Net::HTTP; + +my $s = Net::HTTP->new( + Host => "www.neverssl.com", + KeepAlive => 1, + Timeout => 15, + PeerHTTPVersion => "1.1", + MaxLineLength => 512 +) || die "$@"; + +for ( 1 .. 2 ) { + $s->write_request( + GET => "/", + 'User-Agent' => 'Mozilla/5.0', + 'Accept-Language' => 'no,en', + Accept => '*/*' + ); + + my ( $code, $mess, %h ) = $s->read_response_headers; + print "# ----------------------------\n"; + print "# $code $mess\n"; + for ( sort keys %h ) { + print "# $_: $h{$_}\n"; + } + print "#\n"; + + my $buf; + while (1) { + my $tmp; + my $n = $s->read_entity_body( $tmp, 20 ); + last unless $n; + $buf .= $tmp; + } + $buf =~ s/\r//g; + + ok( $code == 302 || $code == 200, 'success' ); + like( $h{'Content-Type'}, qr{text/html} ); + like( $buf, qr{}i ); +} diff --git a/t/rt-112313.t b/t/rt-112313.t new file mode 100644 index 0000000..d33f32d --- /dev/null +++ b/t/rt-112313.t @@ -0,0 +1,118 @@ +BEGIN { + if ( $ENV{NO_NETWORK_TESTING} ) { + print "1..0 # SKIP Live tests disabled due to NO_NETWORK_TESTING\n"; + exit; + } + eval { + require IO::Socket::INET; + my $s = IO::Socket::INET->new( + PeerHost => "httpbin.org:80", + Timeout => 5, + ); + die "Can't connect: $@" unless $s; + }; + if ($@) { + print "1..0 # SKIP Can't connect to httpbin.org\n"; + print $@; + exit; + } +} + +use strict; +use warnings; +use Test::More; +use Net::HTTP; + +# Attempt to verify that RT#112313 (Hang in my_readline() when keep-alive => 1 and $reponse_size % 1024 == 0) is fixed + +# To do that, we need responses (headers + body) that are even multiples of 1024 bytes. So we +# iterate over the same URL, trying to grow the response size incrementally... + +# There's a chance this test won't work if, for example, the response body grows by one byte while +# the Content-Length also rolls over to one more digit, thus increasing the total response by two +# bytes. + +# So, we check that the reponse growth is only one byte after each iteration and also test multiple +# times across the 1024, 2048 and 3072 boundaries... + + +sub try +{ + my $n = shift; + + # Need a new socket every time because we're testing with Keep-Alive... + my $s = Net::HTTP->new( + Host => "httpbin.org", + KeepAlive => 1, + PeerHTTPVersion => "1.1", + ) or die "$@"; + + $s->write_request(GET => '/headers', + 'User-Agent' => "Net::HTTP - $0", + 'X-Foo' => ('x' x $n), + ); + + # Wait until all data is probably available on the socket... + sleep 1; + + my ($code, $mess, @headers) = $s->read_response_headers(); + + # XXX remove X-Processed-Time header + for my $i (0..$#headers) { + if ($headers[$i] eq 'X-Processed-Time') { + splice @headers, $i, 2; + last; + } + } + + my $body = ''; + while ($s->read_entity_body(my $buf, 1024)) + { + $body .= $buf; + } + + # Compute what is probably the total response length... + my $total_len = length(join "\r\n", 'HTTP/1.1', "$code $mess", @headers, '', $body) - 1; + + # diag("$n - $code $mess => $total_len"); + # diag(join "\r\n", 'HTTP/1.1', "$code $mess", @headers, '', $body); + + $code == 200 + or die "$code $mess"; + + return $total_len; +} + +my $timeout = 15; +my $wiggle_room = 3; + +local $SIG{ALRM} = sub { die 'timeout' }; + +my $base_len = try(1); +ok($base_len < 1024, "base response length is less than 1024: $base_len"); + +for my $kb (1024, 2048, 3072) +{ + my $last; + + # Calculate range that will take us across the 1024 boundary... + for my $n (($kb - $base_len - $wiggle_room) .. ($kb - $base_len + $wiggle_room)) + { + my $len = -1; + + eval { + alarm $timeout; + $len = try($n); + }; + + ok(!$@, "ok for n $n -> response length $len") + or diag("error: $@"); + + # Verify that response length only increased by one since the whole test rests on that assumption... + is($len - $last, 1, 'reponse length increased by 1') if $last; + + $last = $len; + } +} + +done_testing(); diff --git a/tidyall.ini b/tidyall.ini new file mode 100644 index 0000000..3bd7f33 --- /dev/null +++ b/tidyall.ini @@ -0,0 +1,23 @@ +[PerlCritic] +select = **/*.{pl,pm,t,psgi} +ignore = .build/**/* +ignore = Net-HTTP-*/**/* +ignore = blib/**/* +ignore = t/00-* +ignore = t/author-* +ignore = t/release-* +ignore = t/zzz-* +ignore = xt/**/* +argv = --profile=$ROOT/perlcriticrc + +[PerlTidy] +select = **/*.{pl,pm,t,psgi} +ignore = .build/**/* +ignore = Net-HTTP-*/**/* +ignore = blib/**/* +ignore = t/00-* +ignore = t/author-* +ignore = t/release-* +ignore = t/zzz-* +ignore = xt/**/* +argv = --profile=$ROOT/perltidyrc