AnyEvent-FTP-0.20/000755 000000 000000 00000000000 15123245460 013573 5ustar00rootroot000000 000000 AnyEvent-FTP-0.20/Changes000644 000000 000000 00000004340 15123245460 015067 0ustar00rootroot000000 000000 Revision history for AnyEvent-FTP 0.20 2025-12-25 07:33:15 -0700 - Remove EV as a prereq (gh#36) 0.19 2022-04-12 10:33:09 -0600 - Fix prereq on autodie (gh#33) - Fix bug in where socket handle wasn't being properly destroyed after use (gh#32) 0.18 2021-10-28 09:18:40 -0600 - Fix test that could fail on some platforms because regexp wasn't correctly escaped (gh#31) 0.17 2021-07-12 08:43:17 -0600 - Fix bug related to space being in %PATH% on Windows - Another fix on Perl where test suite could fail if installed from a different volume than the temp directory. - Add dependency on Capture::Tiny - Relax Perl requirement to 5.10.0 (had been 5.10.1) 0.16 2017-08-30 09:08:58 -0400 - Fix to timeout in now longer running client test - Fix to test that was not optionally using EV 0.15 2017-08-29 18:39:58 -0400 - Migrate to EUMM 0.14 2017-07-26 07:42:52 -0400 - Remove dependency on List::MoreUtils - Migrate from File::ShareDir to File::ShareDir::Dist 0.11 2017-07-22 23:58:59 -0400 - Remove dependency on File::HomeDir 0.10 2017-07-07 18:13:24 -0400 - Bundle ls from Perl Power Tools for platforms that do not have a compliant ls 0.09 2015-08-03 14:38:44 -0400 - AnyEvent::FTP::Server::Context::FSRW no longer attempts to return a directory as a file via RETR 0.08 2015-03-03 23:30:40 -0500 - Require Moo 2.0, as older versions inadvertently turn on fatal warnings 0.07 2015-01-26 09:43:21 -0500 - Grammar and spacing fixes in documentation (GH#22 thanks shlomif) - Improved documentation coverage (GH#22 thanks shlomif) 0.06 2014-10-29 07:55:23 -0400 - Build.PL now exits instead of dies on wrong Perl version 0.05 2014-09-09 08:49:13 -0400 - promote to production 0.04_01 2014-08-21 13:34:15 -0400 - set LC_ALL=C in tests to avoid possible locale missmatches 0.04 2014-08-09 07:32:40 -0400 - make default for test timeout be 120s 0.03 2014-08-05 08:13:16 -0400 - fix for test failure on MSWin32 0.02 2014-08-05 07:20:55 -0400 - fix test failure on non C/English locales (thanks syohex) - fix test on OS X (thanks ryochin) 0.01 2014-08-04 16:49:27 -0400 - initial version AnyEvent-FTP-0.20/INSTALL000644 000000 000000 00000004553 15123245460 014633 0ustar00rootroot000000 000000 This is the Perl distribution AnyEvent-FTP. Installing AnyEvent-FTP is straightforward. ## Installation with cpanm If you have cpanm, you only need one line: % cpanm AnyEvent::FTP 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 AnyEvent::FTP ## Manual installation As a last resort, you can manually install it. If you have not already downloaded the release tarball, you can find the download link on the module's MetaCPAN page: https://metacpan.org/pod/AnyEvent::FTP Untar the tarball, install configure prerequisites (see below), then build it: % perl Makefile.PL % make && make test Then install it: % make install On Windows platforms, you should use `dmake` or `nmake`, instead of `make`. 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 The prerequisites of this distribution will also have to be installed manually. The prerequisites are listed in one of the files: `MYMETA.yml` or `MYMETA.json` generated by running the manual build process described above. ## Configure Prerequisites This distribution requires other modules to be installed before this distribution's installer can be run. They can be found under the "configure_requires" key of META.yml or the "{prereqs}{configure}{requires}" key of META.json. ## Other Prerequisites This distribution may require additional modules to be installed after running Makefile.PL. Look for prerequisites in the following phases: * to run make, PHASE = build * to use the module code itself, PHASE = runtime * to run tests, PHASE = test They can all be found in the "PHASE_requires" key of MYMETA.yml or the "{prereqs}{PHASE}{requires}" key of MYMETA.json. ## Documentation AnyEvent-FTP documentation is available as POD. You can run `perldoc` from a shell to read the documentation: % perldoc AnyEvent::FTP For more information on installing Perl modules via CPAN, please see: https://www.cpan.org/modules/INSTALL.html AnyEvent-FTP-0.20/LICENSE000644 000000 000000 00000046320 15123245460 014605 0ustar00rootroot000000 000000 This software is copyright (c) 2017-2022 by Graham Ollis. 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) 2017-2022 by Graham Ollis. 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. 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, see . 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 Moe Ghoul, President of Vice That's all there is to it! --- The Perl Artistic License 1.0 --- This software is Copyright (c) 2017-2022 by Graham Ollis. This is free software, licensed under: The Perl 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 as specified below. "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 uunet.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) give non-standard executables non-standard names, and clearly document 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. You may embed this Package's interpreter within an executable of yours (by linking); this shall be construed as a mere form of aggregation, provided that the complete Standard Version of the interpreter is so embedded. 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 whoever generated them, and may be sold commercially, and may be aggregated with this Package. If such scripts or library files are aggregated with this Package via the so-called "undump" or "unexec" methods of producing a binary executable image, then distribution of such an image shall neither be construed as a distribution of this Package nor shall it fall under the restrictions of Paragraphs 3 and 4, provided that you do not represent such an executable image as a Standard Version of this Package. 7. C subroutines (or comparably compiled subroutines in other languages) supplied by you and linked into this Package in order to emulate subroutines and variables of the language defined by this Package shall not be considered part of this Package, but are the equivalent of input as in Paragraph 6, provided these subroutines do not change the language in any way that would cause it to fail the regression tests for the language. 8. Aggregation of this Package with a commercial distribution is always permitted provided that the use of this Package is embedded; that is, when no overt attempt is made to make this Package's interfaces visible to the end user of the commercial distribution. Such use shall not be construed as a distribution of this Package. 9. The name of the Copyright Holder may not be used to endorse or promote products derived from this software without specific prior written permission. 10. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. The End AnyEvent-FTP-0.20/MANIFEST000644 000000 000000 00000006452 15123245460 014733 0ustar00rootroot000000 000000 # This file was automatically generated by Dist::Zilla::Plugin::Manifest v6.036. Changes INSTALL LICENSE MANIFEST META.json META.yml Makefile.PL README author.yml bin/aeftpd corpus/nlst/one.txt corpus/nlst/three.txt corpus/nlst/two.txt dist.ini example/blocking_retr.pl example/fget.pl example/fls.pl example/fput.pl example/lib/AnyEvent/FTP/Server/Context/EchoContext.pm example/list.pl example/non_blocking_retr.pl inc/ServerTests.pm lib/AnyEvent/FTP.pm lib/AnyEvent/FTP/Client.pm lib/AnyEvent/FTP/Client/Response.pm lib/AnyEvent/FTP/Client/Role/FetchTransfer.pm lib/AnyEvent/FTP/Client/Role/ListTransfer.pm lib/AnyEvent/FTP/Client/Role/RequestBuffer.pm lib/AnyEvent/FTP/Client/Role/ResponseBuffer.pm lib/AnyEvent/FTP/Client/Role/StoreTransfer.pm lib/AnyEvent/FTP/Client/Site.pm lib/AnyEvent/FTP/Client/Site/Base.pm lib/AnyEvent/FTP/Client/Site/Microsoft.pm lib/AnyEvent/FTP/Client/Site/NetFtpServer.pm lib/AnyEvent/FTP/Client/Site/Proftpd.pm lib/AnyEvent/FTP/Client/Transfer.pm lib/AnyEvent/FTP/Client/Transfer/Active.pm lib/AnyEvent/FTP/Client/Transfer/Passive.pm lib/AnyEvent/FTP/Request.pm lib/AnyEvent/FTP/Response.pm lib/AnyEvent/FTP/Role/Event.pm lib/AnyEvent/FTP/Server.pm lib/AnyEvent/FTP/Server/Connection.pm lib/AnyEvent/FTP/Server/Context.pm lib/AnyEvent/FTP/Server/Context/FS.pm lib/AnyEvent/FTP/Server/Context/FSRO.pm lib/AnyEvent/FTP/Server/Context/FSRW.pm lib/AnyEvent/FTP/Server/Context/Memory.pm lib/AnyEvent/FTP/Server/OS/UNIX.pm lib/AnyEvent/FTP/Server/Role/Auth.pm lib/AnyEvent/FTP/Server/Role/Context.pm lib/AnyEvent/FTP/Server/Role/Help.pm lib/AnyEvent/FTP/Server/Role/Old.pm lib/AnyEvent/FTP/Server/Role/ResponseEncoder.pm lib/AnyEvent/FTP/Server/Role/TransferPrep.pm lib/AnyEvent/FTP/Server/Role/Type.pm lib/AnyEvent/FTP/Server/UnambiguousResponseEncoder.pm lib/Test/AnyEventFTPServer.pm maint/gen.pl maint/travis-install-file-sharedir-dist perlcriticrc share/ppt/ls.pl t/00_diag.t t/01_use.t t/anyevent_ftp.t t/anyevent_ftp_client.t t/anyevent_ftp_client__cwd.t t/anyevent_ftp_client_response.t t/anyevent_ftp_client_role_responsebuffer.t t/anyevent_ftp_client_site.t t/anyevent_ftp_role_event.t t/anyevent_ftp_server_connection.t t/anyevent_ftp_server_context_fs.t t/anyevent_ftp_server_context_fsrw__ascii.t t/anyevent_ftp_server_context_fsrw__help_coverage.t t/anyevent_ftp_server_context_fsrw__unauth.t t/anyevent_ftp_server_context_memory__cdup.t t/anyevent_ftp_server_context_memory__cwd.t t/anyevent_ftp_server_context_memory__dele.t t/anyevent_ftp_server_context_memory__help_coverage.t t/anyevent_ftp_server_context_memory__mkd.t t/anyevent_ftp_server_context_memory__nlst.t t/anyevent_ftp_server_context_memory__pwd.t t/anyevent_ftp_server_context_memory__rename.t t/anyevent_ftp_server_context_memory__rmd.t t/anyevent_ftp_server_context_memory__size.t t/anyevent_ftp_server_context_memory__stat.t t/anyevent_ftp_server_role_auth.t t/anyevent_ftp_server_role_help.t t/anyevent_ftp_server_role_old.t t/anyevent_ftp_server_role_transferprep.t t/anyevent_ftp_server_role_type.t t/anyevent_ftp_server_unambiguousresponseencoder.t t/lib/Test2/Tools/ClientTests.pm t/test_anyeventftpserver.t tools/issue10.pl tools/test_client.pl xt/author/critic.t xt/author/eol.t xt/author/no_tabs.t xt/author/pod.t xt/author/pod_coverage.t xt/author/pod_spelling_common.t xt/author/strict.t xt/author/version.t xt/release/changes.t xt/release/fixme.t AnyEvent-FTP-0.20/META.json000644 000000 000000 00000015514 15123245460 015222 0ustar00rootroot000000 000000 { "abstract" : "Simple asynchronous FTP client and server", "author" : [ "Graham Ollis " ], "dynamic_config" : 0, "generated_by" : "Dist::Zilla version 6.036, CPAN::Meta::Converter version 2.150010", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "AnyEvent-FTP", "no_index" : { "directory" : [ "example", "tools" ] }, "prereqs" : { "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0", "File::ShareDir::Install" : "0.06", "perl" : "5.010" } }, "develop" : { "recommends" : { "Dist::Zilla::Plugin::Author::Plicease::Thanks" : "0", "Dist::Zilla::Plugin::Author::Plicease::Upload" : "0", "Dist::Zilla::Plugin::InsertExample" : "0", "Dist::Zilla::Plugin::MetaNoIndex" : "0", "Dist::Zilla::Plugin::Prereqs" : "0", "Dist::Zilla::Plugin::PruneFiles" : "0", "Dist::Zilla::Plugin::RemovePrereqs" : "0", "Dist::Zilla::PluginBundle::Author::Plicease" : "2.69", "Perl::Critic::Policy::BuiltinFunctions::ProhibitBooleanGrep" : "0", "Perl::Critic::Policy::BuiltinFunctions::ProhibitStringySplit" : "0", "Perl::Critic::Policy::BuiltinFunctions::ProhibitVoidGrep" : "0", "Perl::Critic::Policy::BuiltinFunctions::ProhibitVoidMap" : "0", "Perl::Critic::Policy::ClassHierarchies::ProhibitExplicitISA" : "0", "Perl::Critic::Policy::ClassHierarchies::ProhibitOneArgBless" : "0", "Perl::Critic::Policy::CodeLayout::ProhibitHardTabs" : "0", "Perl::Critic::Policy::CodeLayout::ProhibitTrailingWhitespace" : "0", "Perl::Critic::Policy::CodeLayout::RequireConsistentNewlines" : "0", "Perl::Critic::Policy::Community::ArrayAssignAref" : "0", "Perl::Critic::Policy::Community::BarewordFilehandles" : "0", "Perl::Critic::Policy::Community::ConditionalDeclarations" : "0", "Perl::Critic::Policy::Community::ConditionalImplicitReturn" : "0", "Perl::Critic::Policy::Community::DeprecatedFeatures" : "0", "Perl::Critic::Policy::Community::DollarAB" : "0", "Perl::Critic::Policy::Community::Each" : "0", "Perl::Critic::Policy::Community::IndirectObjectNotation" : "0", "Perl::Critic::Policy::Community::LexicalForeachIterator" : "0", "Perl::Critic::Policy::Community::LoopOnHash" : "0", "Perl::Critic::Policy::Community::ModPerl" : "0", "Perl::Critic::Policy::Community::OpenArgs" : "0", "Perl::Critic::Policy::Community::OverloadOptions" : "0", "Perl::Critic::Policy::Community::POSIXImports" : "0", "Perl::Critic::Policy::Community::PackageMatchesFilename" : "0", "Perl::Critic::Policy::Community::PreferredAlternatives" : "0", "Perl::Critic::Policy::Community::StrictWarnings" : "0", "Perl::Critic::Policy::Community::Threads" : "0", "Perl::Critic::Policy::Community::Wantarray" : "0", "Perl::Critic::Policy::Community::WarningsSwitch" : "0", "Perl::Critic::Policy::Community::WhileDiamondDefaultAssignment" : "0", "Perl::Critic::Policy::ControlStructures::ProhibitLabelsWithSpecialBlockNames" : "0", "Perl::Critic::Policy::ControlStructures::ProhibitMutatingListFunctions" : "0", "Perl::Critic::Policy::ControlStructures::ProhibitUnreachableCode" : "0", "Perl::Critic::Policy::InputOutput::ProhibitBarewordFileHandles" : "0", "Perl::Critic::Policy::InputOutput::ProhibitJoinedReadline" : "0", "Perl::Critic::Policy::InputOutput::ProhibitTwoArgOpen" : "0", "Perl::Critic::Policy::Miscellanea::ProhibitFormats" : "0", "Perl::Critic::Policy::Miscellanea::ProhibitUselessNoCritic" : "0", "Perl::Critic::Policy::Modules::ProhibitConditionalUseStatements" : "0", "Perl::Critic::Policy::Modules::RequireNoMatchVarsWithUseEnglish" : "0", "Perl::Critic::Policy::Objects::ProhibitIndirectSyntax" : "0", "Perl::Critic::Policy::RegularExpressions::ProhibitUselessTopic" : "0", "Perl::Critic::Policy::Subroutines::ProhibitNestedSubs" : "0", "Perl::Critic::Policy::ValuesAndExpressions::ProhibitLeadingZeros" : "0", "Perl::Critic::Policy::ValuesAndExpressions::ProhibitMixedBooleanOperators" : "0", "Perl::Critic::Policy::ValuesAndExpressions::ProhibitSpecialLiteralHeredocTerminator" : "0", "Perl::Critic::Policy::ValuesAndExpressions::RequireUpperCaseHeredocTerminator" : "0", "Perl::Critic::Policy::Variables::ProhibitPerl4PackageNames" : "0", "Perl::Critic::Policy::Variables::ProhibitUnusedVariables" : "0", "Software::License::Perl_5" : "0" }, "requires" : { "Perl::Critic" : "0", "Test2::Require::Module" : "0.000121", "Test2::Tools::PerlCritic" : "0", "Test2::V0" : "0.000121", "Test::CPAN::Changes" : "0", "Test::EOL" : "0", "Test::Fixme" : "0.07", "Test::More" : "0.98", "Test::NoTabs" : "0", "Test::Pod" : "0", "Test::Pod::Coverage" : "0", "Test::Pod::Spelling::CommonMistakes" : "0", "Test::Strict" : "0" } }, "runtime" : { "requires" : { "AnyEvent" : "0", "Capture::Tiny" : "0", "File::ShareDir::Dist" : "0", "File::Which" : "0", "File::chdir" : "0", "Moo" : "2.0", "Path::Class" : "0.26", "PerlIO::eol" : "0", "Test2::API" : "1.302015", "URI" : "0", "autodie" : "0", "perl" : "5.010" } }, "test" : { "requires" : { "Test2::V0" : "0.000121", "perl" : "5.010" } } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "https://github.com/uperl/AnyEvent-FTP/issues" }, "homepage" : "https://metacpan.org/pod/AnyEvent::FTP", "repository" : { "type" : "git", "url" : "git://github.com/uperl/AnyEvent-FTP.git", "web" : "https://github.com/uperl/AnyEvent-FTP" } }, "version" : "0.20", "x_contributors" : [ "Graham Ollis ", "Ryo Okamoto", "Shlomi Fish", "Jos\u00e9 Joaqu\u00edn Atria" ], "x_generated_by_perl" : "v5.42.0", "x_serialization_backend" : "Cpanel::JSON::XS version 4.40", "x_spdx_expression" : "Artistic-1.0-Perl OR GPL-1.0-or-later", "x_use_unsafe_inc" : 0 } AnyEvent-FTP-0.20/META.yml000644 000000 000000 00000002321 15123245460 015042 0ustar00rootroot000000 000000 --- abstract: 'Simple asynchronous FTP client and server' author: - 'Graham Ollis ' build_requires: Test2::V0: '0.000121' perl: '5.010' configure_requires: ExtUtils::MakeMaker: '0' File::ShareDir::Install: '0.06' perl: '5.010' dynamic_config: 0 generated_by: 'Dist::Zilla version 6.036, 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: AnyEvent-FTP no_index: directory: - example - tools requires: AnyEvent: '0' Capture::Tiny: '0' File::ShareDir::Dist: '0' File::Which: '0' File::chdir: '0' Moo: '2.0' Path::Class: '0.26' PerlIO::eol: '0' Test2::API: '1.302015' URI: '0' autodie: '0' perl: '5.010' resources: bugtracker: https://github.com/uperl/AnyEvent-FTP/issues homepage: https://metacpan.org/pod/AnyEvent::FTP repository: git://github.com/uperl/AnyEvent-FTP.git version: '0.20' x_contributors: - 'Graham Ollis ' - 'Ryo Okamoto' - 'Shlomi Fish' - 'José Joaquín Atria' x_generated_by_perl: v5.42.0 x_serialization_backend: 'YAML::Tiny version 1.76' x_spdx_expression: 'Artistic-1.0-Perl OR GPL-1.0-or-later' x_use_unsafe_inc: 0 AnyEvent-FTP-0.20/Makefile.PL000644 000000 000000 00000014347 15123245460 015556 0ustar00rootroot000000 000000 BEGIN { use strict; use warnings; unless(eval q{ use 5.010; 1}) { print "Perl 5.010 or better required\n"; exit; } } # This file was automatically generated by Dist::Zilla::Plugin::Author::Plicease::MakeMaker v2.79. use strict; use warnings; use 5.010; use ExtUtils::MakeMaker; use File::ShareDir::Install; $File::ShareDir::Install::INCLUDE_DOTFILES = 1; $File::ShareDir::Install::INCLUDE_DOTDIRS = 1; install_share dist => "share"; my %WriteMakefileArgs = ( "ABSTRACT" => "Simple asynchronous FTP client and server", "AUTHOR" => "Graham Ollis ", "CONFIGURE_REQUIRES" => { "ExtUtils::MakeMaker" => 0, "File::ShareDir::Install" => "0.06" }, "DISTNAME" => "AnyEvent-FTP", "EXE_FILES" => [ "bin/aeftpd" ], "LICENSE" => "perl", "MIN_PERL_VERSION" => "5.010", "NAME" => "AnyEvent::FTP", "PM" => { "lib/AnyEvent/FTP.pm" => "\$(INST_LIB)/AnyEvent/FTP.pm", "lib/AnyEvent/FTP/Client.pm" => "\$(INST_LIB)/AnyEvent/FTP/Client.pm", "lib/AnyEvent/FTP/Client/Response.pm" => "\$(INST_LIB)/AnyEvent/FTP/Client/Response.pm", "lib/AnyEvent/FTP/Client/Role/FetchTransfer.pm" => "\$(INST_LIB)/AnyEvent/FTP/Client/Role/FetchTransfer.pm", "lib/AnyEvent/FTP/Client/Role/ListTransfer.pm" => "\$(INST_LIB)/AnyEvent/FTP/Client/Role/ListTransfer.pm", "lib/AnyEvent/FTP/Client/Role/RequestBuffer.pm" => "\$(INST_LIB)/AnyEvent/FTP/Client/Role/RequestBuffer.pm", "lib/AnyEvent/FTP/Client/Role/ResponseBuffer.pm" => "\$(INST_LIB)/AnyEvent/FTP/Client/Role/ResponseBuffer.pm", "lib/AnyEvent/FTP/Client/Role/StoreTransfer.pm" => "\$(INST_LIB)/AnyEvent/FTP/Client/Role/StoreTransfer.pm", "lib/AnyEvent/FTP/Client/Site.pm" => "\$(INST_LIB)/AnyEvent/FTP/Client/Site.pm", "lib/AnyEvent/FTP/Client/Site/Base.pm" => "\$(INST_LIB)/AnyEvent/FTP/Client/Site/Base.pm", "lib/AnyEvent/FTP/Client/Site/Microsoft.pm" => "\$(INST_LIB)/AnyEvent/FTP/Client/Site/Microsoft.pm", "lib/AnyEvent/FTP/Client/Site/NetFtpServer.pm" => "\$(INST_LIB)/AnyEvent/FTP/Client/Site/NetFtpServer.pm", "lib/AnyEvent/FTP/Client/Site/Proftpd.pm" => "\$(INST_LIB)/AnyEvent/FTP/Client/Site/Proftpd.pm", "lib/AnyEvent/FTP/Client/Transfer.pm" => "\$(INST_LIB)/AnyEvent/FTP/Client/Transfer.pm", "lib/AnyEvent/FTP/Client/Transfer/Active.pm" => "\$(INST_LIB)/AnyEvent/FTP/Client/Transfer/Active.pm", "lib/AnyEvent/FTP/Client/Transfer/Passive.pm" => "\$(INST_LIB)/AnyEvent/FTP/Client/Transfer/Passive.pm", "lib/AnyEvent/FTP/Request.pm" => "\$(INST_LIB)/AnyEvent/FTP/Request.pm", "lib/AnyEvent/FTP/Response.pm" => "\$(INST_LIB)/AnyEvent/FTP/Response.pm", "lib/AnyEvent/FTP/Role/Event.pm" => "\$(INST_LIB)/AnyEvent/FTP/Role/Event.pm", "lib/AnyEvent/FTP/Server.pm" => "\$(INST_LIB)/AnyEvent/FTP/Server.pm", "lib/AnyEvent/FTP/Server/Connection.pm" => "\$(INST_LIB)/AnyEvent/FTP/Server/Connection.pm", "lib/AnyEvent/FTP/Server/Context.pm" => "\$(INST_LIB)/AnyEvent/FTP/Server/Context.pm", "lib/AnyEvent/FTP/Server/Context/FS.pm" => "\$(INST_LIB)/AnyEvent/FTP/Server/Context/FS.pm", "lib/AnyEvent/FTP/Server/Context/FSRO.pm" => "\$(INST_LIB)/AnyEvent/FTP/Server/Context/FSRO.pm", "lib/AnyEvent/FTP/Server/Context/FSRW.pm" => "\$(INST_LIB)/AnyEvent/FTP/Server/Context/FSRW.pm", "lib/AnyEvent/FTP/Server/Context/Memory.pm" => "\$(INST_LIB)/AnyEvent/FTP/Server/Context/Memory.pm", "lib/AnyEvent/FTP/Server/OS/UNIX.pm" => "\$(INST_LIB)/AnyEvent/FTP/Server/OS/UNIX.pm", "lib/AnyEvent/FTP/Server/Role/Auth.pm" => "\$(INST_LIB)/AnyEvent/FTP/Server/Role/Auth.pm", "lib/AnyEvent/FTP/Server/Role/Context.pm" => "\$(INST_LIB)/AnyEvent/FTP/Server/Role/Context.pm", "lib/AnyEvent/FTP/Server/Role/Help.pm" => "\$(INST_LIB)/AnyEvent/FTP/Server/Role/Help.pm", "lib/AnyEvent/FTP/Server/Role/Old.pm" => "\$(INST_LIB)/AnyEvent/FTP/Server/Role/Old.pm", "lib/AnyEvent/FTP/Server/Role/ResponseEncoder.pm" => "\$(INST_LIB)/AnyEvent/FTP/Server/Role/ResponseEncoder.pm", "lib/AnyEvent/FTP/Server/Role/TransferPrep.pm" => "\$(INST_LIB)/AnyEvent/FTP/Server/Role/TransferPrep.pm", "lib/AnyEvent/FTP/Server/Role/Type.pm" => "\$(INST_LIB)/AnyEvent/FTP/Server/Role/Type.pm", "lib/AnyEvent/FTP/Server/UnambiguousResponseEncoder.pm" => "\$(INST_LIB)/AnyEvent/FTP/Server/UnambiguousResponseEncoder.pm", "lib/Test/AnyEventFTPServer.pm" => "\$(INST_LIB)/Test/AnyEventFTPServer.pm" }, "PREREQ_PM" => { "AnyEvent" => 0, "Capture::Tiny" => 0, "File::ShareDir::Dist" => 0, "File::Which" => 0, "File::chdir" => 0, "Moo" => "2.0", "Path::Class" => "0.26", "PerlIO::eol" => 0, "Test2::API" => "1.302015", "URI" => 0, "autodie" => 0 }, "TEST_REQUIRES" => { "Test2::V0" => "0.000121" }, "VERSION" => "0.20", "test" => { "TESTS" => "t/*.t" } ); my %FallbackPrereqs = ( "AnyEvent" => 0, "Capture::Tiny" => 0, "File::ShareDir::Dist" => 0, "File::Which" => 0, "File::chdir" => 0, "Moo" => "2.0", "Path::Class" => "0.26", "PerlIO::eol" => 0, "Test2::API" => "1.302015", "Test2::V0" => "0.000121", "URI" => 0, "autodie" => 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); { package MY; use File::ShareDir::Install qw(postamble); }AnyEvent-FTP-0.20/README000644 000000 000000 00000005307 15123245460 014460 0ustar00rootroot000000 000000 NAME AnyEvent::FTP - Simple asynchronous FTP client and server VERSION version 0.20 SYNOPSIS # For the client use AnyEvent::FTP::Client; # For the server use AnyEvent::FTP::Server; DESCRIPTION This distribution provides client and server implementations for File Transfer Protocol (FTP) in an AnyEvent environment. For the specific interfaces, see AnyEvent::FTP::Client and AnyEvent::FTP::Server for details. Before each release, AnyEvent::FTP::Client is tested against these FTP servers using the t/client_*.t tests that come with this distribution: Proftpd wu-ftpd Net::FTPServer vsftpd Pure-FTPd bftpd AnyEvent::FTP::Server The client code is also tested less frequently against these FTP servers: NcFTPd Microsoft IIS It used to also be tested against the VMS ftp server, so it was verified to work with it, at least at one point. However, I no longer have access to that server. BUNDLED FILES This distribution comes bundled with ls from the old Perl Power Tools project. This is only used on MSWin32 if this command is not found in the path, as it is frequently not available on that platform The Perl implementation of ls was written by Mark Leighton Fisher of Thomson Consumer Electronics, fisherm@tce.com. That program is free and open software. You may use, modify, distribute, and sell it program (and any modified variants) in any way you wish, provided you do not restrict others from doing the same. SEE ALSO * AnyEvent::FTP::Client * AnyEvent::FTP::Server * Net::FTP * Net::FTPServer * AnyEvent * RFC 959 FILE TRANSFER PROTOCOL * RFC 2228 FTP Security Extensions * RFC 2640 Internationalization of the File Transfer Protocol * RFC 2773 Encryption using KEA and SKIPJACK * RFC 3659 Extensions to FTP * RFC 5797 FTP Command and Extension Registry * http://cr.yp.to/ftp.html * http://en.wikipedia.org/wiki/List_of_FTP_server_return_codes AUTHOR Author: Graham Ollis Contributors: Ryo Okamoto Shlomi Fish José Joaquín Atria COPYRIGHT AND LICENSE This software is copyright (c) 2017-2022 by Graham Ollis. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. AnyEvent-FTP-0.20/author.yml000644 000000 000000 00000004417 15123245460 015626 0ustar00rootroot000000 000000 --- pod_spelling_system: # list of words that are spelled correctly # (regardless of what spell check thinks) stopwords: - AnyEvent - BUILDARGS - IIS - PASV - URI - filename - filenames - login - proftpd - KEA - SKIPJACK - IP - unparsed - fget - fput - fls - aeftpd - CDPATH - CHECKMETHOD - CHECKSUM - cb - callbacks - callback - pl - APPE - CDUP - CWD - DELE - MKD - NLST - PWD - Readonly - RMD - RNFR - RNTO - STAT - STOR - STOU - FTPd - bftpd - ftpd - vsftpd - wu - NcFTPd - syst - SYST - ALLO - standalone - stderr - auth - cred - inet - pam - readonly - RETR - VMS - authenticator - TCP - FSRW - vett - customizations - Okamoto - Ryo - Shlomi # lol - ACFLRSTWacdfgiklmnopqrstux - dev - Joaquín - José - Leighton - RSacdfiklnrstu pod_coverage: # format is "Class#method" or "Class", regex allowed # for either Class or method. private: - .*#new - .*#BUILD - .*#BUILDARGS - AnyEvent::FTP::Client::Role::RequestBuffer - AnyEvent::FTP::Client::Role::ResponseBuffer - AnyEvent::FTP::Client::Transfer#(handle|client|command|local|restart) - AnyEvent::FTP::Client::Transfer::Active - AnyEvent::FTP::Client::Transfer::Passive - AnyEvent::FTP::Client::Role::FetchTransfer - AnyEvent::FTP::Client::Role::ListTransfer - AnyEvent::FTP::Client::Role::StoreTransfer # server context requires coverage of commands, # not methods - AnyEvent::FTP::Server::Context::(FS(|RW|RO)|Memory)#(cmd_|help_).* - AnyEvent::FTP::Server::Role::(TransferPrep|Auth|Help|Old|Type)#(cmd_|help_).* # The ::Site:: module documentation actually cover these methods, # but Test::Pod::Coverage doesn't like the multipe arrow thing that is # going on there, but I think it be more clear - AnyEvent::FTP::Client::Site::Base#client - AnyEvent::FTP::Client::Site::Microsoft#dirstyle - AnyEvent::FTP::Client::Site::NetFtpServer#version - AnyEvent::FTP::Client::Site::Proftpd#(chgrp|chmod|help|quota|ratio|rmdir|symlink|utime|mkdir) unused_vars: skip: 1 AnyEvent-FTP-0.20/bin/000755 000000 000000 00000000000 15123245460 014343 5ustar00rootroot000000 000000 AnyEvent-FTP-0.20/bin/aeftpd000755 000000 000000 00000015236 15123245460 015543 0ustar00rootroot000000 000000 #!/usr/bin/perl use strict; use warnings; use 5.010; use AnyEvent; use AnyEvent::FTP::Server; use Getopt::Long qw( GetOptions ); use URI; use Pod::Usage qw( pod2usage ); # PODNAME: aeftpd # ABSTRACT: FTP Server our $VERSION = '0.20'; # VERSION my $port; my $host; my $inet = 0; my $stderr; my $chroot = 0; my $verbose = 0; my $cred; my $simple_auth_class; my @simple_auth_args; my $default_context = 'FSRW'; GetOptions( 'port=i' => \$port, 'hostname=s' => \$host, 'inet' => \$inet, 'stderr=s' => \$stderr, 'pam=s' => sub { $simple_auth_class = 'PAM'; push @simple_auth_args, service => $_[1] }, 'chroot' => \$chroot, 'verbose' => \$verbose, 'cred=s' => \$cred, 'context=s' => \$default_context, 'auth=s' => sub { $_[1] =~ /^(.*?)=(.*)$/ ? (push @simple_auth_args, $1 => $2) : ($simple_auth_class = $_[1]) }, 'help|h' => sub { pod2usage({ -verbose => 2}) }, 'version' => sub { say 'aeftp/AnyEvent::FTP version ', ($AnyEvent::FTP::Server::VERSION // 'dev'); exit 1 }, ) || pod2usage(1); $0 = 'aeftpd'; $port //= ($> && $^O !~ /^(cygwin|MSWin32)$/) ? undef : 21; if($stderr) { open STDERR, '>>', $stderr; } $cred = 'random' if ! defined($cred) && ! defined($simple_auth_class); if(defined $cred && $cred eq 'random') { $cred = { user => (join '', map { chr(ord('a') + int rand(26)) } (1..10)), pass => (join '', map { chr(ord('a') + int rand(26)) } (1..10)), }; } elsif(defined $cred) { my($user,$pass) = split /:/, $cred; unless(defined $pass) { say STDERR "password not provided for --cred option"; exit 2; } $cred = { user => $user, pass => $pass, }; } $default_context = "AnyEvent::FTP::Server::Context::$default_context" unless $default_context =~ /::/; my $server = AnyEvent::FTP::Server->new( hostname => $host, port => $port, inet => $inet, default_context => $default_context, ); unless($inet) { $server->on_bind(sub { my $uri = URI->new('ftp:'); $uri->host($host // 'localhost'); $uri->port(shift); $uri->userinfo(join ':', $cred->{user}, $cred->{pass}) if defined $cred; say $uri; }); } if($verbose) { $server->on_connect(sub { my $con = shift; $con->on_request(sub { my $raw = shift; say STDERR "CLIENT: $raw"; }); $con->on_response(sub { my $raw = shift; $raw =~ s/\015?\012$//g; say STDERR "SERVER: $raw"; }); $con->on_close(sub { say STDERR "DISCONNECT"; }); say STDERR "CONNECT"; }); } if($cred) { $server->on_connect(sub { my $con = shift; $con->context->authenticator(sub { my($name, $pass) = @_; return $name eq $cred->{user} && $pass eq $cred->{pass}; }); }); } elsif($simple_auth_class) { eval 'use Authen::Simple::' . $simple_auth_class; if($@) { say STDERR "install Authen::Simple::$simple_auth_class in order to use $simple_auth_class authentication"; exit 2; } my $pam = "Authen::Simple::$simple_auth_class"->new( @simple_auth_args, ); $server->on_connect(sub { my $con = shift; my $user_class; if($inet && $< == 0 && $^O !~ /^(cygwin|MSWin32)$/) { $user_class = 'AnyEvent::FTP::Server::OS::UNIX'; eval "use $user_class"; die $@ if $@; } $con->context->authenticator(sub { my($name, $pass) = @_; $name = 'ftp' if $name eq 'anonymous'; my $user; if(defined $user_class) { $user = eval { $user_class->new($name) }; return 0 if $@; } return 0 if $name ne 'ftp' && ! $pam->authenticate( $name, $pass ); if(defined $user) { $user->jail if $chroot || $name eq 'ftp'; $user->drop_privileges; } }); $con->context->bad_authentication_delay(0); 1; }); } else { print STDERR "must specify at least one of --pam, --cred or --auth"; exit 2; } $server->start; AnyEvent->condvar->recv; __END__ =pod =encoding UTF-8 =head1 NAME aeftpd - FTP Server =head1 VERSION version 0.20 =head1 SYNOPSIS % aeftpd [ --port port ] [ --hostname hostname ] [ --inet ] [ --stderr path ] [ --context context ] [ --chroot ] --pam service | --cred user:pass | --cred random | --auth class [ --auth key=val ] [ --verbose ] % aeftpd --version % aeftpd --help =head1 DESCRIPTION This program starts an FTP daemon using the perl library L. =head1 OPTIONS =head2 --port I The TCP port to listen to. =head2 --hostname I The hostname or IP address to listen on. =head2 --inet Run in inet mode. By default C runs as a single process in standalone mode. By using this option you can run C from C, C or similar daemon. Here is a line for /etc/inetd.conf which was tested on Debian Wheezy, and may work for you if your operating system supports PAM and you have L installed. ftp stream tcp nowait root /usr/sbin/tcpd aeftpd --inet --stderr /tmp/aeftp.log --pam login =head2 --stderr I Redirect stderr from the daemon to the file specified by the given path =head2 --chroot Use C to restrict the user to only his home directory once he has logged in. This option requires the C function, which is supported by Perl on most UNIX and UNIX like operating systems. =head2 --cred [ I:I | random ] Allow authentication with the given username and password. If you specify C then a randomly generated username and password will be used. =head2 --auth I Specify a L class to use for authentication. You should NOT include the Authen::Simple prefix when specifying the class (that is use C instead of C). =head2 --auth I=I Specify an argument to pass into the chosen L object. =head2 --pam I Use PAM for authentication. This option is simply a shortcut. This: % aeftpd --pam login is the same as % aeftpd --auth PAM --auth service=login =head2 --verbose Print all FTP commands and their responses to stderr. =head2 --context I Set the default context. The default is FSRW. =head2 --version Print out the L version to stdout and exit. =head2 --help Display the usage for this command. =head1 AUTHOR Author: Graham Ollis Eplicease@cpan.orgE Contributors: Ryo Okamoto Shlomi Fish José Joaquín Atria =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2017-2022 by Graham Ollis. 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 AnyEvent-FTP-0.20/corpus/000755 000000 000000 00000000000 15123245460 015106 5ustar00rootroot000000 000000 AnyEvent-FTP-0.20/corpus/nlst/000755 000000 000000 00000000000 15123245460 016066 5ustar00rootroot000000 000000 AnyEvent-FTP-0.20/corpus/nlst/one.txt000644 000000 000000 00000000000 15123245460 017376 0ustar00rootroot000000 000000 AnyEvent-FTP-0.20/corpus/nlst/three.txt000644 000000 000000 00000000000 15123245460 017724 0ustar00rootroot000000 000000 AnyEvent-FTP-0.20/corpus/nlst/two.txt000644 000000 000000 00000000000 15123245460 017426 0ustar00rootroot000000 000000 AnyEvent-FTP-0.20/dist.ini000644 000000 000000 00000003257 15123245460 015246 0ustar00rootroot000000 000000 name = AnyEvent-FTP author = Graham Ollis license = Perl_5 copyright_holder = Graham Ollis copyright_year = 2017-2022 version = 0.20 [@Author::Plicease] :version = 2.69 release_tests = 1 test2_v0 = 1 github_user = uperl workflow = static workflow = linux diag_preamble = | $post_diag = sub { diag_preamble = | use AnyEvent::FTP::Server::Context::FSRW; diag_preamble = | diag "ls[] = ", $_ for AnyEvent::FTP::Server::Context::FSRW::_shared_cmd('ls'); diag_preamble = | BEGIN { eval 'use EV' } diag_preamble = | diag 'AnyEvent::detect() = ', AnyEvent::detect(); diag_preamble = | }; [RemovePrereqs] remove = strict remove = warnings remove = overload remove = base remove = bytes remove = lib remove = Exporter ; only used in release testing by t/lib.pl remove = YAML remove = YAML::XS remove = NX::Lock ; comes with AnyEvent remove = AnyEvent::Socket remove = AnyEvent::Handle ; comes with Moo remove = Moo::Role ; come with perl 5.10.1 (or better) remove = File::Spec remove = File::Temp remove = Getopt::Long remove = Carp remove = Socket remove = FindBin remove = Pod::Usage remove = Cwd remove = File::Glob ; comes with Path::Class remove = Path::Class::File remove = Path::Class::Dir ; optional for testing remove = Data::HexDump remove = EV [Prereqs] perl = 5.010 PerlIO::eol = 0 Path::Class = 0 [MetaNoIndex] directory = example directory = tools [InsertExample] ;[=inc::ServerTests] [Author::Plicease::Upload] cpan = 1 [Author::Plicease::Thanks] current = Graham Ollis contributor = Ryo Okamoto contributor = Shlomi Fish contributor = José Joaquín Atria [PruneFiles] filename = xt/author/pod_spelling_system.t AnyEvent-FTP-0.20/example/000755 000000 000000 00000000000 15123245460 015226 5ustar00rootroot000000 000000 AnyEvent-FTP-0.20/example/blocking_retr.pl000644 000000 000000 00000000651 15123245460 020411 0ustar00rootroot000000 000000 use strict; use warnings; use AnyEvent; use AnyEvent::FTP::Client; my $client = AnyEvent::FTP::Client->new( passive => 1); my $done = AnyEvent->condvar; # connect to CPAN ftp server $client->connect('ftp://ftp.cpan.org/pub/CPAN/src')->recv; # use binary mode $client->type('I')->recv; # download the file directly into a filehandle open my $fh, '>', 'perl-5.16.3.tar.gz'; $client->retr('perl-5.16.3.tar.gz', $fh)->recv; AnyEvent-FTP-0.20/example/fget.pl000644 000000 000000 00000005307 15123245460 016515 0ustar00rootroot000000 000000 #!/usr/bin/perl use strict; use warnings; use autodie; use 5.010; use AnyEvent::FTP::Client; use URI; use URI::file; use Term::ProgressBar; use Term::Prompt qw( prompt ); use Getopt::Long qw( GetOptions ); use Path::Class qw( file ); my $debug = 0; my $progress = 0; my $active = 0; GetOptions( 'd' => \$debug, 'p' => \$progress, 'a' => \$active, ); my $remote = shift; unless(defined $remote) { say STDERR "usage: perl fget.pl [ -d | -p ] [ -a ] remote"; say STDERR " where remote is a URL for a file on an FTP server"; say STDERR " and local is a local filename (optional) where to transfer it to"; say STDERR " -d (optional) prints FTP commands and responses"; say STDERR " -p (optional) displays a progress bar as the file uploads"; say STDERR " -a (optional) use active mode transfer"; exit 2; } $remote = URI->new($remote); unless($remote->scheme eq 'ftp') { say STDERR "only FTP URLs are supported"; exit 2; } unless(defined $remote->password) { $remote->password(prompt('p', 'Password: ', '', '')); say ''; } do { my $from = $remote->clone; $from->password(undef); say "SRC: ", $from; }; my @path = split /\//, $remote->path; my $fn = pop @path; if(-e $fn) { say STDERR "local file already exists"; exit 2; } my $ftp = AnyEvent::FTP::Client->new( passive => $active ? 0 : 1 ); $ftp->on_send(sub { my($cmd, $arguments) = @_; $arguments //= ''; $arguments = 'XXXX' if $cmd eq 'PASS'; say "CLIENT: $cmd $arguments" if $debug; }); $ftp->on_each_response(sub { my $res = shift; if($debug) { say sprintf "SERVER: [ %d ] %s", $res->code, $_ for @{ $res->message }; } }); $ftp->connect($remote->host, $remote->port)->recv; $ftp->login($remote->user, $remote->password)->recv; $ftp->type('I')->recv; $ftp->cwd(join '/', '', @path)->recv; my $remote_size; if($progress) { my $listing = $ftp->list($fn)->recv; foreach my $class (qw( File::Listing File::Listing::Ftpcopy )) { my $parsed_listing = eval qq{ use $class; ${class}::parse_dir(\$listing->[0]) }; next if $@; my ($name, $type, $size, $mtime, $mode) = @{ $parsed_listing->[0] }; $remote_size = $size; last; } if(defined $remote_size) { } else { say STDERR "could not determine size of remote file, cannot provide progress bar"; $progress = 0; } } open my $fh, '>', $fn; my $xfer = $ftp->retr($fn); my $pb; my $count = 0; $xfer->on_open(sub { my $handle = shift; $pb = Term::ProgressBar->new({ count => $remote_size }) if $progress; $handle->on_read(sub { $handle->push_read(sub { print $fh $_[0]{rbuf}; $pb->update($count += length($_[0]{rbuf})) if $pb; $_[0]{rbuf} = ''; }); }); }); $xfer->recv; close $fh; $ftp->quit->recv; AnyEvent-FTP-0.20/example/fls.pl000644 000000 000000 00000001754 15123245460 016356 0ustar00rootroot000000 000000 use strict; use warnings; use 5.010; use URI; use AnyEvent::FTP::Client; use Term::Prompt qw( prompt ); use Getopt::Long qw( GetOptions ); my $debug = 0; my $method = 'nlst'; GetOptions( 'd' => \$debug, 'l' => sub { $method = 'list' }, ); my $ftp = AnyEvent::FTP::Client->new; if($debug) { $ftp->on_send(sub { my($cmd, $arguments) = @_; $arguments //= ''; $arguments = 'XXXX' if $cmd eq 'PASS'; say "CLIENT: $cmd $arguments"; }); $ftp->on_each_response(sub { my $res = shift; say sprintf "SERVER: [ %d ] %s", $res->code, $_ for @{ $res->message }; }); } my $uri = shift; unless(defined $uri) { say STDERR "usage: perl fls.pl URL\n"; exit 2; } $uri = URI->new($uri); unless($uri->scheme eq 'ftp') { say STDERR "only FTP URL accpeted"; exit 2; } unless(defined $uri->password) { $uri->password(prompt('p', 'Password: ', '', '')); say ''; } my $path = $uri->path; $uri->path(''); $ftp->connect($uri); say $_ for @{ $ftp->$method($path)->recv }; AnyEvent-FTP-0.20/example/fput.pl000644 000000 000000 00000004512 15123245460 016543 0ustar00rootroot000000 000000 #!/usr/bin/perl use strict; use warnings; use autodie; use 5.010; use AnyEvent::FTP::Client; use URI; use URI::file; use Term::ProgressBar; use Term::Prompt qw( prompt ); use Getopt::Long qw( GetOptions ); use Path::Class qw( file ); my $debug = 0; my $progress = 0; my $active = 0; GetOptions( 'd' => \$debug, 'p' => \$progress, 'a' => \$active, ); my $local = shift; my $remote = shift; unless(defined $local && defined $remote) { say STDERR "usage: perl fput.pl [ -d | -p ] [ -a ] local remote"; say STDERR " where local is a local file"; say STDERR " and remote is a URL for a FTP server"; say STDERR " -d (optional) prints FTP commands and responses"; say STDERR " -p (optional) displays a progress bar as the file uploads"; say STDERR " -a (optional) use an active transfer instead of passive"; exit 2; } $local = file($local); $remote = URI->new($remote); unless($remote->scheme eq 'ftp') { say STDERR "only FTP URLs are supported"; exit 2; } unless(defined $remote->password) { $remote->password(prompt('p', 'Password: ', '', '')); say ''; } do { my $from = URI::file->new_abs($local); my $to = $remote->clone; $to->password(undef); say "SRC: ", $from; say "DST: ", $to; }; my $ftp = AnyEvent::FTP::Client->new( passive => $active ? 0 : 1 ); $ftp->on_send(sub { my($cmd, $arguments) = @_; $arguments //= ''; $arguments = 'XXXX' if $cmd eq 'PASS'; say "CLIENT: $cmd $arguments" if $debug; }); $ftp->on_each_response(sub { my $res = shift; if($debug) { say sprintf "SERVER: [ %d ] %s", $res->code, $_ for @{ $res->message }; } }); $ftp->connect($remote->host, $remote->port)->recv; $ftp->login($remote->user, $remote->password)->recv; $ftp->type('I')->recv; if(defined $remote->path) { $ftp->cwd($remote->path)->recv; } open my $fh, '<', $local; binmode $fh; my $buffer; my $count; my $pb; my $xfer = $ftp->stor($local->basename); $xfer->on_open(sub { my $whandle = shift; $pb = Term::ProgressBar->new({ count => -s $fh }) if $progress; $whandle->on_drain(sub { $pb->update($count) if $pb; my $ret = read $fh, $buffer, 1024 * 512; $count += $ret; if($ret > 0) { $whandle->push_write($buffer); } else { $pb->update($count) if $pb; $whandle->push_shutdown; close $fh; } }); }); $xfer->recv; $ftp->quit->recv; AnyEvent-FTP-0.20/example/lib/000755 000000 000000 00000000000 15123245460 015774 5ustar00rootroot000000 000000 AnyEvent-FTP-0.20/example/lib/AnyEvent/000755 000000 000000 00000000000 15123245460 017525 5ustar00rootroot000000 000000 AnyEvent-FTP-0.20/example/lib/AnyEvent/FTP/000755 000000 000000 00000000000 15123245460 020156 5ustar00rootroot000000 000000 AnyEvent-FTP-0.20/example/lib/AnyEvent/FTP/Server/000755 000000 000000 00000000000 15123245460 021424 5ustar00rootroot000000 000000 AnyEvent-FTP-0.20/example/lib/AnyEvent/FTP/Server/Context/000755 000000 000000 00000000000 15123245460 023050 5ustar00rootroot000000 000000 AnyEvent-FTP-0.20/example/lib/AnyEvent/FTP/Server/Context/EchoContext.pm000644 000000 000000 00000000517 15123245460 025634 0ustar00rootroot000000 000000 package AnyEvent::FTP::Server::Context::EchoContext; use Moo; extends 'AnyEvent::FTP::Server::Context'; with 'AnyEvent::FTP::Server::Role::Help'; # implement the non-existent echo command sub help_echo { 'ECHO text' } sub cmd_echo { my($self, $con, $req) = @_; $con->send_response(211 => $req->args); $self->done; } 1; AnyEvent-FTP-0.20/example/list.pl000644 000000 000000 00000000625 15123245460 016541 0ustar00rootroot000000 000000 use strict; use warnings; use AnyEvent; use AnyEvent::FTP::Client; my $client = AnyEvent::FTP::Client->new; my $cv = AnyEvent->condvar; # connect to CPAN ftp server $client->connect('ftp://ftp.cpan.org/pub/CPAN/src')->cb(sub { # execute LIST command and print results to stdout $client->list->cb(sub { my $list = shift->recv; print "$_\n" for @$list; $cv->send; }); }); $cv->recv; AnyEvent-FTP-0.20/example/non_blocking_retr.pl000644 000000 000000 00000001350 15123245460 021260 0ustar00rootroot000000 000000 use strict; use warnings; use AnyEvent; use AnyEvent::FTP::Client; my $client = AnyEvent::FTP::Client->new( passive => 1); my $done = AnyEvent->condvar; # connect to CPAN ftp server $client->connect('ftp://ftp.cpan.org/pub/CPAN/src')->cb(sub { # use binary mode $client->type('I')->cb(sub { # download the file directly into a filehandle open my $fh, '>', 'perl-5.16.3.tar.gz'; $client->retr('perl-5.16.3.tar.gz', $fh)->cb(sub { # notify anyone listening to $done that # the transfer is complete $done->send; }); }); }); # receive the done message once the transfer is # complete. In real code you'd probably not # want to do this because your event loop may # not support blocking. $done->recv; AnyEvent-FTP-0.20/inc/000755 000000 000000 00000000000 15123245460 014344 5ustar00rootroot000000 000000 AnyEvent-FTP-0.20/inc/ServerTests.pm000644 000000 000000 00000003702 15123245460 017175 0ustar00rootroot000000 000000 package inc::ServerTests; use Moose; use namespace::autoclean; use 5.010; use Path::Class qw( file dir ); use YAML qw( LoadFile DumpFile ); use File::Glob qw( bsd_glob ); with 'Dist::Zilla::Role::TestRunner'; sub test { my($self, $target) = @_; my $test_root = dir('.')->absolute; my @services = do { open my $fh, '<', '/etc/services'; map { [split /\s+/]->[0] } grep /^(..)?ftp\s/, <$fh>; }; foreach my $service (@services) { my $dir = $test_root->subdir('t', 'server', $service); $dir->mkpath(0,0700); my $old = $test_root->file('t', 'lib.pl'); my $new = $dir->file('lib.pl'); symlink $old, $new; $old = file( bsd_glob '~/etc/localhost/yml'); $new = $dir->file('config.yml'); my $config = LoadFile($old); $config->{port} = $service; DumpFile($new, $config); } my @remotes; foreach my $remote_config (grep { $_->basename =~ /\.yml$/ } dir(bsd_glob '~/etc')->children) { next if $remote_config->basename eq 'localhost.yml'; #$self->zilla->log($remote_config->basename); my $name = $remote_config->basename; $name =~ s/\.yml$//; push @remotes, $name; my $dir = $test_root->subdir('t','server',$name); $dir->mkpath(0,0700); my $old = $test_root->file('t', 'lib.pl'); my $new = $dir->file('lib.pl'); symlink $old, $new; $old = $remote_config; $new = $dir->file('config.yml'); my $config = LoadFile($old); DumpFile($new, $config); } foreach my $test_file (grep { $_->basename =~ /^client_/ } sort { $a->basename cmp $b->basename } $test_root->subdir('t')->children) { foreach my $service (@services, @remotes) { my $link = $test_root->file('t', 'server', $service, $test_file->basename); symlink $test_file, $link; } } local $ENV{AEF_PORT} = 'from_config'; system 'prove', '-br', ($ENV{AEF_JOBS} ? ('-j' => $ENV{AEF_JOBS}, '-s') : ()), 't/server'; $self->log_fatal('server test failure') unless $? == 0; } 1; AnyEvent-FTP-0.20/lib/000755 000000 000000 00000000000 15123245460 014341 5ustar00rootroot000000 000000 AnyEvent-FTP-0.20/lib/AnyEvent/000755 000000 000000 00000000000 15123245460 016072 5ustar00rootroot000000 000000 AnyEvent-FTP-0.20/lib/AnyEvent/FTP.pm000644 000000 000000 00000005655 15123245460 017074 0ustar00rootroot000000 000000 package AnyEvent::FTP; use strict; use warnings; use 5.010; # ABSTRACT: Simple asynchronous FTP client and server our $VERSION = '0.20'; # VERSION 1; __END__ =pod =encoding UTF-8 =head1 NAME AnyEvent::FTP - Simple asynchronous FTP client and server =head1 VERSION version 0.20 =head1 SYNOPSIS # For the client use AnyEvent::FTP::Client; # For the server use AnyEvent::FTP::Server; =head1 DESCRIPTION This distribution provides client and server implementations for File Transfer Protocol (FTP) in an AnyEvent environment. For the specific interfaces, see L and L for details. Before each release, L is tested against these FTP servers using the C tests that come with this distribution: =over 4 =item Proftpd =item wu-ftpd =item L =item vsftpd =item Pure-FTPd =item bftpd =item L =back The client code is also tested less frequently against these FTP servers: =over 4 =item NcFTPd =item Microsoft IIS =back It used to also be tested against the VMS ftp server, so it was verified to work with it, at least at one point. However, I no longer have access to that server. =head1 BUNDLED FILES This distribution comes bundled with C from the old L project. This is only used on C if this command is not found in the path, as it is frequently not available on that platform The Perl implementation of C was written by Mark Leighton Fisher of Thomson Consumer Electronics, I. That program is free and open software. You may use, modify, distribute, and sell it program (and any modified variants) in any way you wish, provided you do not restrict others from doing the same. =head1 SEE ALSO =over 4 =item * L =item * L =item * L =item * L =item * L =item * L =item * L =item * L =item * L =item * L =item * L =item * L =item * L =back =head1 AUTHOR Author: Graham Ollis Eplicease@cpan.orgE Contributors: Ryo Okamoto Shlomi Fish José Joaquín Atria =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2017-2022 by Graham Ollis. 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 AnyEvent-FTP-0.20/lib/AnyEvent/FTP/000755 000000 000000 00000000000 15123245460 016523 5ustar00rootroot000000 000000 AnyEvent-FTP-0.20/lib/AnyEvent/FTP/Client.pm000644 000000 000000 00000071325 15123245460 020307 0ustar00rootroot000000 000000 package AnyEvent::FTP::Client; use 5.010; use Moo; use AnyEvent; use AnyEvent::Socket qw( tcp_connect ); use AnyEvent::Handle; use Carp qw( croak ); use Socket qw( unpack_sockaddr_in inet_ntoa ); # ABSTRACT: Simple asynchronous ftp client our $VERSION = '0.20'; # VERSION with 'AnyEvent::FTP::Role::Event'; with 'AnyEvent::FTP::Client::Role::ResponseBuffer'; with 'AnyEvent::FTP::Client::Role::RequestBuffer'; __PACKAGE__->define_events(qw( error close send greeting )); has _connected => ( is => 'rw', default => sub { 0 }, init_arg => undef, ); has timeout => ( is => 'rw', default => sub { 30 }, ); has passive => ( is => 'ro', default => sub { 1 }, ); foreach my $xfer (qw( Store Fetch List )) { my $cb = sub { return shift->passive ? 'AnyEvent::FTP::Client::Transfer::Passive::'.$xfer : 'AnyEvent::FTP::Client::Transfer::Active::'.$xfer; }; has '_'.lc($xfer) => ( is => 'ro', lazy => 1, default => $cb, init_arg => undef ), } sub BUILD { my($self) = @_; $self->on_error(sub { warn shift }); $self->on_close(sub { $self->clear_command; $self->_connected(0); delete $self->{handle}; }); require ($self->passive ? 'AnyEvent/FTP/Client/Transfer/Passive.pm' : 'AnyEvent/FTP/Client/Transfer/Active.pm'); return; } sub connect { my($self, $host, $port) = @_; if($host =~ /^ftp:/) { require URI; $host = URI->new($host); } my $uri; if(ref($host) && eval { $host->isa('URI') }) { $uri = $host; $host = $uri->host; $port = $uri->port; } else { $port //= 21; } croak "Tried to reconnect while connected" if $self->_connected; my $cv = AnyEvent->condvar; $self->_connected(1); tcp_connect $host, $port, sub { my($fh) = @_; unless($fh) { $cv->croak("unable to connect: $!"); $self->_connected(0); $self->clear_command; return; } # Get the IP address we are sending from for when # we use the PORT command (passive=0). $self->{my_ip} = do { my($port, $addr) = unpack_sockaddr_in getsockname $fh; inet_ntoa $addr; }; $self->{handle} = AnyEvent::Handle->new( fh => $fh, on_error => sub { my ($hdl, $fatal, $msg) = @_; $_[0]->destroy; $self->emit('error', $msg); $self->emit('close'); }, on_eof => sub { $self->{handle}->destroy; $self->emit('close'); }, ); $self->on_next_response(sub { my $res = shift; return $cv->croak($res) unless $res->is_success; $self->emit(greeting => $res); if(defined $uri) { my @start_commands = ( [USER => $uri->user], [PASS => $uri->password], ); push @start_commands, [CWD => $uri->path] if $uri->path ne ''; $self->unshift_command(@start_commands, $cv); } else { $cv->send($res); $self->pop_command; } }); $self->{handle}->on_read(sub { $self->{handle}->push_read( line => sub { my($handle, $line) = @_; $self->process_message_line($line); }); }); }, sub { $self->timeout; }; return $cv; } sub login { my($self, $user, $pass) = @_; $self->push_command( [ USER => $user ], [ PASS => $pass ] ); } sub retr { my($self, $filename, $local) = (shift, shift, shift); my $args = ref $_[0] eq 'HASH' ? (\%{$_[0]}) : ({@_}); $self->_fetch->new({ command => [ RETR => $filename ], local => $local, client => $self, restart => $args->{restart}, }); } sub stor { my($self, $filename, $local) = @_; $self->_store->new( command => [STOR => $filename], local => $local, client => $self, ); } sub stou { my($self, $filename, $local) = @_; my $xfer; my $cb = sub { my $name = shift->get_file; $xfer->{remote_name} = $name if defined $name; return; }; $xfer = $self->_store->new( command => [STOU => $filename, $cb], local => $local, client => $self, ); } # for this to work under ProFTPd: AllowStoreRestart off sub appe { my($self, $filename, $local) = @_; $self->_store->new( command => [APPE => $filename], local => $local, client => $self, ); } sub list { my($self, $location, $verb) = @_; $verb //= 'LIST'; my @lines; my $cv = AnyEvent->condvar; $self->_list->new( command => [ $verb => $location ], local => \@lines, client => $self, )->cb(sub { my $res = eval { shift->recv }; $cv->croak($@) if $@; $cv->send(\@lines); }); $cv; } sub nlst { my($self, $location) = @_; $self->list($location, 'NLST'); } sub rename { my($self, $from, $to) = @_; $self->push_command( [ RNFR => $from ], [ RNTO => $to ], ); } sub pwd { my($self) = @_; my $cv = AnyEvent->condvar; $self->push_command(['PWD'])->cb(sub { my $res = eval { shift->recv } // $@; my $dir = $res->get_dir; if($dir) { $cv->send($dir) } else { $cv->croak($res) } }); $cv; } sub size { my($self, $path) = @_; my $cv = AnyEvent->condvar; $self->push_command(['SIZE', $path])->cb(sub { my $res = eval { shift->recv }; if(my $error = $@) { $cv->croak($error) } else { $cv->send($res->message->[0]) } }); $cv; } (eval sprintf('sub %s { shift->push_command([ %s => @_])};1', lc $_, $_)) // die $@ for qw( CWD CDUP NOOP ALLO SYST TYPE STRU MODE REST MKD RMD STAT HELP DELE RNFR RNTO USER PASS ACCT MDTM ); sub quit { my($self) = @_; my $cv = AnyEvent->condvar; my $res; $self->push_command(['QUIT'])->cb(sub { $res = eval { shift->recv } // $@; }); my $save = $self->{event}->{close}; $self->{event}->{close} = [ sub { if(defined $res && $res->is_success) { $cv->send($res) } elsif(defined $res) { $cv->croak($res) } else { $cv->croak("did not receive QUIT response from server") } $_->() for @$save; $self->{event}->{close} = $save; } ]; return $cv; } sub site { require AnyEvent::FTP::Client::Site; AnyEvent::FTP::Client::Site->new(shift); } 1; __END__ =pod =encoding UTF-8 =head1 NAME AnyEvent::FTP::Client - Simple asynchronous ftp client =head1 VERSION version 0.20 =head1 SYNOPSIS Non blocking example: use strict; use warnings; use AnyEvent; use AnyEvent::FTP::Client; my $client = AnyEvent::FTP::Client->new( passive => 1); my $done = AnyEvent->condvar; # connect to CPAN ftp server $client->connect('ftp://ftp.cpan.org/pub/CPAN/src')->cb(sub { # use binary mode $client->type('I')->cb(sub { # download the file directly into a filehandle open my $fh, '>', 'perl-5.16.3.tar.gz'; $client->retr('perl-5.16.3.tar.gz', $fh)->cb(sub { # notify anyone listening to $done that # the transfer is complete $done->send; }); }); }); # receive the done message once the transfer is # complete. In real code you'd probably not # want to do this because your event loop may # not support blocking. $done->recv; Same, but using recv to wait for each command to complete (not supported in all event loops): use strict; use warnings; use AnyEvent; use AnyEvent::FTP::Client; my $client = AnyEvent::FTP::Client->new( passive => 1); my $done = AnyEvent->condvar; # connect to CPAN ftp server $client->connect('ftp://ftp.cpan.org/pub/CPAN/src')->recv; # use binary mode $client->type('I')->recv; # download the file directly into a filehandle open my $fh, '>', 'perl-5.16.3.tar.gz'; $client->retr('perl-5.16.3.tar.gz', $fh)->recv; =head1 DESCRIPTION This class provides an AnyEvent client interface to the File Transfer Protocol (FTP). =head1 ROLES This class consumes these roles: =over 4 =item * L =item * L =item * L =back =head1 EVENTS For details on the event interface see L. =head2 send This event gets fired on every command sent to the remote server. Keep in mind that some methods of L may make multiple FTP commands in order to implement their functionality (for example, C, C, etc). One use of this event is to print out commands as they are sent for debugging: $client->on_send(sub { my($cmd, $arguments) = @_; $arguments //= ''; # hide passwords $arguments = 'XXXX' if $cmd =~ /^pass$/i; say "CLIENT: $cmd $arguments"; }); =head2 error This event is emitted when there is a network error with the remote server. It passes in a string which describes in human readable description of what went wrong. $client->on_error(sub { my($message) = @_; warn "network error: $message"; }); =head2 close This event is emitted when the connection with the remote server is closed, either due to an error, or when you send the FTP C command using the C method. $client->on_close(sub { # called when connection closed }); =head2 greeting This event gets fired on the first response returned from the server. This is usually a C<220> message which may or may not reveal the server software. $client->on_greeting(sub { # $res is a AnyEvent::FTP::Client::Response my($res) = @_; if($res->message->[0] =~ /ProFTPD/) { # detected a ProFTPD server } }); =head2 each_response This event gets fired for each response returned from the server. This can be useful for printing the responses for debugging. $client->on_each_response(sub { # $res isa AnyEvent::FTP::Client::Response my($res) = @_; print "SERVER: $res\n"; }); =head2 next_response Works just like C event, but only gets fired for the next response received. =head1 ATTRIBUTES =head2 timeout Timeout for the initial connection to the FTP server. The default is 30. =head2 passive If set to true (the default) then data will be transferred using the passive (PASV) command, meaning the server will open a port for the client to connect to. If set to false then data will be transferred using data port (PORT) command, meaning the client will open a port for the server to send to. =head1 METHODS Unless otherwise specified, these methods will return an AnyEvent condition variable (AnyEvent->condvar) or an object that implements its interface (methods C, C). On success the C will be used on the condition variable, on failure C will be used instead. Unless otherwise specified the object sent (for both success and failure) will be an instance of L. As an example, here is a fairly thorough handling of a response to the standard FTP C command: $client->help->cb(sub { my $res = eval { shift->recv }; if(my $error = $@) { # $error isa AnyEvent::FTP::Client::Response with a 4xx or 5xx # code my $code = $error->code; # the message component is always a list ref, even if # the response had just one message line my @msg = @{ $error->message }; # $error is stringified into something human readable when # it is streated as a string warn "error trying FTP HELP command: $error"; } else { # $res isa AnyEvent::FTP::Client::Response with a 2xx or 3xx # code my $code = $res->code; # the message component is always a list ref, even if # the response had just one message line my @msg = @{ $res->message }; # $res is stringified into something human readable when # it is streated as a string print "help message: $res"; } }); =head2 connect $client->connect(@remote_host); Connect to the FTP server. The remote host may be specified in one of these ways: =over 4 =item $client-Econnect($host, [ $port ]) The host and port of the remote server. If not specified, the default FTP port will be used (21). =item $client-Econnect($uri) The URI of the remote FTP server. C<$uri> must be either an instance of L with the C scheme, or a string with an FTP URL. If you use this method to connect to the FTP server, connect will also attempt to login with the username and password specified in the URL (or anonymous FTP if no credentials are specified). If there is a path included in the URL, then connect will also do a C so that you start in that directory. =back =head2 login $client->login($user, $pass); Attempt to login to the FTP server which has already been connected to using the C method. This is not necessary if you used C with a URI. =head2 retr $client->retr($filename, $local, %options) Retrieve the given file from the server and use C<$local> to store the results. Returns an instance of L, which supports the AnyEvent condition variable interface (that is it has C and C methods). Its callback will be called when the transfer is complete. C<$local> may be one of =over 4 =item scalar reference The contents of the file will be stored in the scalar referred to by the reference. my $local; $client->retr('foo.txt', \$local); =item file handle The content of the remote file will be written into the local file handle as it is received open my $fh, '>', 'foo.txt'; binmode $fh; $client->retr('foo.txt', $fh); =item the name of the local file If C<$local> is just a regular non reference scalar, then it will be treated as the local filename, which will be created and written to as data is received from the server. $client->retr('foo.txt', 'foo.txt'); =item subroutine reference / callback reference The contents of the file will be passed to the callback as they are received. $client->retr('foo.txt', sub { my ($data) = @_; # Do something with $data }, ); =back In order to resume a transfer, you need to include the C option after the C<$local> argument. Here is an example: # assumes foo.txt (partial download) exists in the current # loacal directory and foo.txt (full file) exists in the # current remote directory. my $filename = 'foo.txt'; open my $fh, '>>', $filename; binmode $fh; $client->retr($filename, $fh, restart => tell $fh); =head2 stor $client->stor($filename, $local); Send a file to the server with the given remote filename (C<$filename>) and using C<$local> as a source. Returns an instance of L, which supports the AnyEvent condition variable interface (that is it has C and C methods). Its callback will be called when the transfer is complete. C<$local> may be one of =over 4 =item scalar reference The contents of the file will be retrieved from the scalar referred to by the reference. my $local = 'some data for foo.txt'; $client->stor('foo.txt', \$local); =item file handle The contents of the file will be read from the file handle. open my $fh, '<', 'foo.txt'; binmode $fh; $client->stor('foo.txt', $fh); =item the name of the local file If C<$local> is just a regular non reference scalar, then it will be treated as the local filename, which will be opened and read from in order to create the file on the server. $client->stor('foo.txt', 'foo.txt'); =back =head2 stou $client->stou($filename, $local) Works exactly like the C method, except use the FTP C command instead of C. Since the remote filename is optional for C you may pass in C as the remote filename. You can get the remote filename after the fact using the C method. my $xfer; $xfer = $client->stou(undef, $local)->cb(sub { my $remote_filename = $xfer->remote_name; }); =head2 appe $client->appe($filename, $local); Works exactly like the C method, except use the FTP C command instead of C. This method will append C<$local> to the remote file. One way to resume an upload to the remote FTP server would be to open the local file, determine the remote file's size and seek to that position in the local file and use the C method with C<$local> as that file handle, as in this example: # assume that foo.txt is in the current local dir # and the remote local dir my $filename = "foo.txt"; $client->size($filename)->cb(sub { my $size = shift->recv; open my $fh, '<', $filename; binmode $fh; seek $fh, $size, 0; $client->appe($filename, $fh); }); Note that the C command is an extension to FTP, and may not be available on all servers. =head2 list $client->list($location) Execute the FTP C command. The results will be sent as a list reference (instead of a L object) to the returned condition variable. use strict; use warnings; use AnyEvent; use AnyEvent::FTP::Client; my $client = AnyEvent::FTP::Client->new; my $cv = AnyEvent->condvar; # connect to CPAN ftp server $client->connect('ftp://ftp.cpan.org/pub/CPAN/src')->cb(sub { # execute LIST command and print results to stdout $client->list->cb(sub { my $list = shift->recv; print "$_\n" for @$list; $cv->send; }); }); $cv->recv; =head2 nlst $client->nlst($location); Works exactly like the C method, except the FTP C command is used. The main difference is that this method returns filenames only. =head2 rename $client->rename($from, $to); This method renames the remote file from C<$from> to C<$to>. It uses the FTP C and C commands and thus this: my $cv = $client->rename($from, $to); is a short cut for: my $cv; $client->rnfr($from)->cb(sub { $cv = $client->rnto($to); }); Although C<$cv> may not be defined right away, so use the second with care. =head2 cwd $client->cwd( $dir ); Change to the given directory on the remote server. =head2 pwd $client->pwd; Gets the current working directory on the remote server. This gets just the string representing the directory path instead of a L object. =head2 cdup $client->cdup Change to the parent directory on the remote server. This is usually the same as $client->cwd('..'); =head2 type $client->type Set the transfer type. You almost always want to set to binary mode immediately after logging on: $client->type('I'); =head2 rest $client->rest This command is used to resume a download transfer. Typically you would not use this method directly, but instead add a C option on the C method instead. =head2 mkd $client->mkd( $path ); Create a directory on the remote server. =head2 rmd $client->rmd( $path ); Remove a directory on the remote server. =head2 help $client->help; Gets a list of commands understood by the server. The actual format depends on the server. =head2 dele $client->dele( $path ); Delete the file on the remote server. =head2 rnfr $client->rnfr; Specify the old name for renaming a file. See C method for a shortcut. =head2 rnto $client->rnto; Specify the new name for renaming a file. See C method for a shortcut. =head2 noop $client->noop; Don't do anything. The server will send an OK reply. =head2 allo $client->allo( $size ); Send the FTP C command. Is not used by modern FTP servers. See RFC959 for details. =head2 syst $client->syst; Returns the type of operating system used by the server. =head2 stru $client->stru; Specify the file structure mode. This is not used by modern FTP servers. See RFC959 for details. =head2 mode $client->mode Specify the transfer mode. This is not used by modern FTP servers. See RFC959 for details. =head2 stat $client->stat; $client->stat($path); Get information about a file or directory on the remote server. The actual format is totally server dependent. =head2 user $client->user( $username ); Specify the user to login as. See C or C methods for a shortcut. =head2 pass $client->pass( $pass ); Specify the password to use for login. See C or C methods for a shortcut. =head2 acct $client->acct( $acct ); Specify user's account. This is sometimes used for authentication and authorization when you login to some servers, but is seldom used today in practice. See RFC959 for details. =head2 size $client->size( $path ); Get the size of the remote file specified by C<$path>. This is an extension to the FTP standard specified in RFC3659, and may not be implemented by older (or even newer) servers. Send the size of the file on success, instead of the response object. =head2 mdtm $client->mdtm( $path ); Get the modification time of the remote file specified by C<$path>. This is an extension to the FTP standard specified in RFC3659, and may not be implemented by older (or even newer) servers. =head2 quit $client->quit; Send the FTP C command and close the connection to the remote server. =head2 site $client->site; The C method provides an interface to site specific FTP commands. Many FTP servers will support an extended set of commands using the standard FTP C command. This command will not check to see if the site commands are supported by the remote server, so it is up to you to determine if you can really use these interfaces yourself. =over 4 =item $client-Esite-Emicrosoft For commands specific to Microsoft's IIS FTP server. See L. =item $client-Esite-Enet_ftp_server For commands specific to L. See L. =item $client-Esite-Eproftpd For commands specific to proftpd. See L. =back =head1 EXAMPLES Here are some longer examples. They are also included with the L distribution in its C directory. =head2 fget.pl Given a URL to a file, this script will fetch the file and store it on your local machine. If you use the C<-d> option you can see the FTP commands and their responses as they happen. #!/usr/bin/perl use strict; use warnings; use autodie; use 5.010; use AnyEvent::FTP::Client; use URI; use URI::file; use Term::ProgressBar; use Term::Prompt qw( prompt ); use Getopt::Long qw( GetOptions ); use Path::Class qw( file ); my $debug = 0; my $progress = 0; my $active = 0; GetOptions( 'd' => \$debug, 'p' => \$progress, 'a' => \$active, ); my $remote = shift; unless(defined $remote) { say STDERR "usage: perl fget.pl [ -d | -p ] [ -a ] remote"; say STDERR " where remote is a URL for a file on an FTP server"; say STDERR " and local is a local filename (optional) where to transfer it to"; say STDERR " -d (optional) prints FTP commands and responses"; say STDERR " -p (optional) displays a progress bar as the file uploads"; say STDERR " -a (optional) use active mode transfer"; exit 2; } $remote = URI->new($remote); unless($remote->scheme eq 'ftp') { say STDERR "only FTP URLs are supported"; exit 2; } unless(defined $remote->password) { $remote->password(prompt('p', 'Password: ', '', '')); say ''; } do { my $from = $remote->clone; $from->password(undef); say "SRC: ", $from; }; my @path = split /\//, $remote->path; my $fn = pop @path; if(-e $fn) { say STDERR "local file already exists"; exit 2; } my $ftp = AnyEvent::FTP::Client->new( passive => $active ? 0 : 1 ); $ftp->on_send(sub { my($cmd, $arguments) = @_; $arguments //= ''; $arguments = 'XXXX' if $cmd eq 'PASS'; say "CLIENT: $cmd $arguments" if $debug; }); $ftp->on_each_response(sub { my $res = shift; if($debug) { say sprintf "SERVER: [ %d ] %s", $res->code, $_ for @{ $res->message }; } }); $ftp->connect($remote->host, $remote->port)->recv; $ftp->login($remote->user, $remote->password)->recv; $ftp->type('I')->recv; $ftp->cwd(join '/', '', @path)->recv; my $remote_size; if($progress) { my $listing = $ftp->list($fn)->recv; foreach my $class (qw( File::Listing File::Listing::Ftpcopy )) { my $parsed_listing = eval qq{ use $class; ${class}::parse_dir(\$listing->[0]) }; next if $@; my ($name, $type, $size, $mtime, $mode) = @{ $parsed_listing->[0] }; $remote_size = $size; last; } if(defined $remote_size) { } else { say STDERR "could not determine size of remote file, cannot provide progress bar"; $progress = 0; } } open my $fh, '>', $fn; my $xfer = $ftp->retr($fn); my $pb; my $count = 0; $xfer->on_open(sub { my $handle = shift; $pb = Term::ProgressBar->new({ count => $remote_size }) if $progress; $handle->on_read(sub { $handle->push_read(sub { print $fh $_[0]{rbuf}; $pb->update($count += length($_[0]{rbuf})) if $pb; $_[0]{rbuf} = ''; }); }); }); $xfer->recv; close $fh; $ftp->quit->recv; =head2 fls.pl Here is a similar example, which does a directory listing on a FTP directory URL. If you use the C<-d> option to see the FTP commands and their responses as they happen. You can use the C<-l> option to see the long form of the file listing. use strict; use warnings; use 5.010; use URI; use AnyEvent::FTP::Client; use Term::Prompt qw( prompt ); use Getopt::Long qw( GetOptions ); my $debug = 0; my $method = 'nlst'; GetOptions( 'd' => \$debug, 'l' => sub { $method = 'list' }, ); my $ftp = AnyEvent::FTP::Client->new; if($debug) { $ftp->on_send(sub { my($cmd, $arguments) = @_; $arguments //= ''; $arguments = 'XXXX' if $cmd eq 'PASS'; say "CLIENT: $cmd $arguments"; }); $ftp->on_each_response(sub { my $res = shift; say sprintf "SERVER: [ %d ] %s", $res->code, $_ for @{ $res->message }; }); } my $uri = shift; unless(defined $uri) { say STDERR "usage: perl fls.pl URL\n"; exit 2; } $uri = URI->new($uri); unless($uri->scheme eq 'ftp') { say STDERR "only FTP URL accpeted"; exit 2; } unless(defined $uri->password) { $uri->password(prompt('p', 'Password: ', '', '')); say ''; } my $path = $uri->path; $uri->path(''); $ftp->connect($uri); say $_ for @{ $ftp->$method($path)->recv }; =head2 fput.pl This script uploads a local file to the remote given a local filename and a remote FTP URL. #!/usr/bin/perl use strict; use warnings; use autodie; use 5.010; use AnyEvent::FTP::Client; use URI; use URI::file; use Term::ProgressBar; use Term::Prompt qw( prompt ); use Getopt::Long qw( GetOptions ); use Path::Class qw( file ); my $debug = 0; my $progress = 0; my $active = 0; GetOptions( 'd' => \$debug, 'p' => \$progress, 'a' => \$active, ); my $local = shift; my $remote = shift; unless(defined $local && defined $remote) { say STDERR "usage: perl fput.pl [ -d | -p ] [ -a ] local remote"; say STDERR " where local is a local file"; say STDERR " and remote is a URL for a FTP server"; say STDERR " -d (optional) prints FTP commands and responses"; say STDERR " -p (optional) displays a progress bar as the file uploads"; say STDERR " -a (optional) use an active transfer instead of passive"; exit 2; } $local = file($local); $remote = URI->new($remote); unless($remote->scheme eq 'ftp') { say STDERR "only FTP URLs are supported"; exit 2; } unless(defined $remote->password) { $remote->password(prompt('p', 'Password: ', '', '')); say ''; } do { my $from = URI::file->new_abs($local); my $to = $remote->clone; $to->password(undef); say "SRC: ", $from; say "DST: ", $to; }; my $ftp = AnyEvent::FTP::Client->new( passive => $active ? 0 : 1 ); $ftp->on_send(sub { my($cmd, $arguments) = @_; $arguments //= ''; $arguments = 'XXXX' if $cmd eq 'PASS'; say "CLIENT: $cmd $arguments" if $debug; }); $ftp->on_each_response(sub { my $res = shift; if($debug) { say sprintf "SERVER: [ %d ] %s", $res->code, $_ for @{ $res->message }; } }); $ftp->connect($remote->host, $remote->port)->recv; $ftp->login($remote->user, $remote->password)->recv; $ftp->type('I')->recv; if(defined $remote->path) { $ftp->cwd($remote->path)->recv; } open my $fh, '<', $local; binmode $fh; my $buffer; my $count; my $pb; my $xfer = $ftp->stor($local->basename); $xfer->on_open(sub { my $whandle = shift; $pb = Term::ProgressBar->new({ count => -s $fh }) if $progress; $whandle->on_drain(sub { $pb->update($count) if $pb; my $ret = read $fh, $buffer, 1024 * 512; $count += $ret; if($ret > 0) { $whandle->push_write($buffer); } else { $pb->update($count) if $pb; $whandle->push_shutdown; close $fh; } }); }); $xfer->recv; $ftp->quit->recv; =head1 SEE ALSO =over 4 =item * L =item * L =back =head1 AUTHOR Author: Graham Ollis Eplicease@cpan.orgE Contributors: Ryo Okamoto Shlomi Fish José Joaquín Atria =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2017-2022 by Graham Ollis. 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 AnyEvent-FTP-0.20/lib/AnyEvent/FTP/Client/000755 000000 000000 00000000000 15123245460 017741 5ustar00rootroot000000 000000 AnyEvent-FTP-0.20/lib/AnyEvent/FTP/Client/Response.pm000644 000000 000000 00000003306 15123245460 022077 0ustar00rootroot000000 000000 package AnyEvent::FTP::Client::Response; use strict; use warnings; use 5.010; use base qw( AnyEvent::FTP::Response ); # ABSTRACT: Response class for asynchronous ftp client our $VERSION = '0.20'; # VERSION sub get_address_and_port { return ("$1.$2.$3.$4", $5*256+$6) if shift->{message}->[0] =~ /\((\d+),(\d+),(\d+),(\d+),(\d+),(\d+)\)/; return; } sub get_dir { if(shift->{message}->[0] =~ /^"(.*)"/) { my $dir = $1; $dir =~ s/""/"/; return $dir; } return; } sub get_file { return shift->{message}->[0] =~ /^FILE: (.*)/i ? $1 : (); } 1; __END__ =pod =encoding UTF-8 =head1 NAME AnyEvent::FTP::Client::Response - Response class for asynchronous ftp client =head1 VERSION version 0.20 =head1 DESCRIPTION Instances of this class get sent to condition variables returned by commands in L. =head1 SUPER CLASS L =head1 METHODS =head2 get_address_and_port my($ip, $port) = $res->get_address_and_port This method is used to parse the response to the C command to extract the IP address and port number. =head2 get_dir my $dir = $res->get_dir This method is used to extract the path from a response to the C command. It returns the path as a simple string. =head2 get_file my $filename = $res->get_file; Returns the filename from a response to the C command. =head1 AUTHOR Author: Graham Ollis Eplicease@cpan.orgE Contributors: Ryo Okamoto Shlomi Fish José Joaquín Atria =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2017-2022 by Graham Ollis. 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 AnyEvent-FTP-0.20/lib/AnyEvent/FTP/Client/Role/000755 000000 000000 00000000000 15123245460 020642 5ustar00rootroot000000 000000 AnyEvent-FTP-0.20/lib/AnyEvent/FTP/Client/Role/FetchTransfer.pm000644 000000 000000 00000003424 15123245460 023741 0ustar00rootroot000000 000000 package AnyEvent::FTP::Client::Role::FetchTransfer; use strict; use warnings; use 5.010; use Moo::Role; # ABSTRACT: Fetch transfer interface for AnyEvent::FTP objects our $VERSION = '0.20'; # VERSION sub xfer { my($self, $fh, $local) = @_; my $handle = $self->handle($fh); return unless defined $local; $handle->on_read(sub { $handle->push_read(sub { $local->($_[0]{rbuf}); $_[0]{rbuf} = ''; }); }); } sub convert_local { my($self, $local) = @_; return unless defined $local; return $local if ref($local) eq 'CODE'; if(ref($local) eq 'SCALAR') { return sub { $$local .= shift; }; } elsif(ref($local) eq 'GLOB') { return sub { print $local shift; }; } elsif(ref($local) eq '') { open my $fh, '>', $local; $self->on_close(sub { close $fh }); return sub { print $fh shift; }; } else { die 'unimplemented: ' . ref $local; } } sub push_command { my $self = shift; my $cv = $self->{client}->push_command( @_, ); $cv->cb(sub { eval { $cv->recv }; my $err = $@; $self->{cv}->croak($err) if $err; }); $self->on_eof(sub { $cv->cb(sub { my $res = eval { $cv->recv }; $self->{cv}->send($res) unless $@; }); }); } 1; __END__ =pod =encoding UTF-8 =head1 NAME AnyEvent::FTP::Client::Role::FetchTransfer - Fetch transfer interface for AnyEvent::FTP objects =head1 VERSION version 0.20 =head1 AUTHOR Author: Graham Ollis Eplicease@cpan.orgE Contributors: Ryo Okamoto Shlomi Fish José Joaquín Atria =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2017-2022 by Graham Ollis. 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 AnyEvent-FTP-0.20/lib/AnyEvent/FTP/Client/Role/ListTransfer.pm000644 000000 000000 00000002550 15123245460 023622 0ustar00rootroot000000 000000 package AnyEvent::FTP::Client::Role::ListTransfer; use strict; use warnings; use 5.010; use Moo::Role; # ABSTRACT: Fetch transfer interface for AnyEvent::FTP objects our $VERSION = '0.20'; # VERSION sub xfer { my($self, $fh, $local) = @_; my $handle = $self->handle($fh); $handle->on_read(sub { $handle->push_read(line => sub { my($handle, $line) = @_; $line =~ s/\015?\012//g; push @{ $local }, $line; }); }); } sub convert_local { my($self, $local) = @_; return $local; } sub push_command { my $self = shift; my $cv = $self->{client}->push_command( @_, ); $cv->cb(sub { eval { $cv->recv }; my $err = $@; $self->{cv}->croak($err) if $err; }); $self->on_eof(sub { $cv->cb(sub { my $res = eval { $cv->recv }; $self->{cv}->send($res) unless $@; }); }); } 1; __END__ =pod =encoding UTF-8 =head1 NAME AnyEvent::FTP::Client::Role::ListTransfer - Fetch transfer interface for AnyEvent::FTP objects =head1 VERSION version 0.20 =head1 AUTHOR Author: Graham Ollis Eplicease@cpan.orgE Contributors: Ryo Okamoto Shlomi Fish José Joaquín Atria =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2017-2022 by Graham Ollis. 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 AnyEvent-FTP-0.20/lib/AnyEvent/FTP/Client/Role/RequestBuffer.pm000644 000000 000000 00000005451 15123245460 023767 0ustar00rootroot000000 000000 package AnyEvent::FTP::Client::Role::RequestBuffer; use strict; use warnings; use 5.010; use Moo::Role; use AnyEvent; # ABSTRACT: Request buffer role for asynchronous ftp client our $VERSION = '0.20'; # VERSION has request_buffer => ( is => 'ro', default => sub { [] }, ); sub push_command { my $cv; $cv = pop if (ref $_[-1]) eq 'AnyEvent::CondVar'; $cv //= AnyEvent->condvar; my($self, @commands) = @_; push @{ $self->request_buffer }, { cmd => \@commands, cv => $cv }; $self->pop_command; $cv; } sub unshift_command { my $cv; $cv = pop if (ref $_[-1]) eq 'AnyEvent::CondVar'; $cv //= AnyEvent->condvar; my($self, @commands) = @_; unshift @{ $self->request_buffer }, { cmd => \@commands, cv => $cv }; $self->pop_command; $cv; } sub pop_command { my($self) = @_; $self->{ready} //= 1; return $self unless defined $self->{handle}; unless(@{ $self->request_buffer } > 0) { $self->{ready} = 1; return $self; } return unless $self->{ready}; my($cmd, $args, $cb) = @{ shift @{ $self->request_buffer->[0]->{cmd} } }; my $line = defined $args ? join(' ', $cmd, $args) : $cmd; my $handler; $handler = sub { my $res = shift; if(defined $cb) { my $error = $cb->($res); if(defined $error) { my $batch = shift @{ $self->request_buffer }; $batch->{cv}->croak($error); return; } } if($res->is_preliminary) { $self->on_next_response($handler); } else { $self->{ready} = 1; if($res->is_success) { if(@{ $self->request_buffer->[0]->{cmd} } > 0) { $self->pop_command; } else { my $batch = shift @{ $self->request_buffer }; $batch->{cv}->send($res); $self->pop_command; } } else { my $batch = shift @{ $self->request_buffer }; $batch->{cv}->croak($res); $self->pop_command; } } }; $self->on_next_response($handler); $self->{ready} = 0; $self->emit('send', $cmd, $args); $self->{handle}->push_write("$line\015\012"); $self; } sub clear_command { my($self) = @_; @{ $self->request_buffer } = (); $self->{ready} = 1; } 1; __END__ =pod =encoding UTF-8 =head1 NAME AnyEvent::FTP::Client::Role::RequestBuffer - Request buffer role for asynchronous ftp client =head1 VERSION version 0.20 =head1 DESCRIPTION Used internally by L. =head1 AUTHOR Author: Graham Ollis Eplicease@cpan.orgE Contributors: Ryo Okamoto Shlomi Fish José Joaquín Atria =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2017-2022 by Graham Ollis. 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 AnyEvent-FTP-0.20/lib/AnyEvent/FTP/Client/Role/ResponseBuffer.pm000644 000000 000000 00000003447 15123245460 024140 0ustar00rootroot000000 000000 package AnyEvent::FTP::Client::Role::ResponseBuffer; use strict; use warnings; use 5.010; use Moo::Role; use AnyEvent::FTP::Client::Response; # ABSTRACT: Response buffer role for asynchronous ftp client our $VERSION = '0.20'; # VERSION sub on_next_response { my($self, $cb) = @_; push @{ $self->{response_buffer}->{once} }, $cb; } sub on_each_response { my($self, $cb) = @_; push @{ $self->{response_buffer}->{each} }, $cb; } sub process_message_line { my($self, $line) = @_; $line =~ s/\015?\012//g; if($line =~ s/^(\d\d\d)([- ])//) { $self->{response_buffer}->{code} //= $1; push @{ $self->{response_buffer}->{message} }, $line; if($2 eq ' ') { my $response = AnyEvent::FTP::Client::Response->new( $self->{response_buffer}->{code}, $self->{response_buffer}->{message}, ); delete $self->{response_buffer}->{$_} for qw( code message ); my $once = delete $self->{response_buffer}->{once}; $_->($response) for @{ $self->{response_buffer}->{each} }, @{ $once }; } } elsif(@{ $self->{response_buffer}->{message} } > 0) { push @{ $self->{response_buffer}->{message} }, $line; } else { warn "bad message: $line"; } } 1; __END__ =pod =encoding UTF-8 =head1 NAME AnyEvent::FTP::Client::Role::ResponseBuffer - Response buffer role for asynchronous ftp client =head1 VERSION version 0.20 =head1 DESCRIPTION Used internally by L. =head1 AUTHOR Author: Graham Ollis Eplicease@cpan.orgE Contributors: Ryo Okamoto Shlomi Fish José Joaquín Atria =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2017-2022 by Graham Ollis. 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 AnyEvent-FTP-0.20/lib/AnyEvent/FTP/Client/Role/StoreTransfer.pm000644 000000 000000 00000003371 15123245460 024005 0ustar00rootroot000000 000000 package AnyEvent::FTP::Client::Role::StoreTransfer; use strict; use warnings; use 5.010; use Moo::Role; # ABSTRACT: Store transfer interface for AnyEvent::FTP objects our $VERSION = '0.20'; # VERSION sub xfer { my($self, $fh, $local) = @_; my $handle = $self->handle($fh); return unless defined $local; $handle->on_drain(sub { my $data = $local->(); if(defined $data) { $handle->push_write($data); } else { $handle->push_shutdown; $handle->destroy; } }); } sub convert_local { my($self, $local) = @_; return unless defined $local; return $local if ref($local) eq 'CODE'; if(ref($local) eq '') { open my $fh, '<', $local; $self->on_close(sub { close $fh }); return sub { local $/; <$fh>; }; } elsif(ref($local) eq 'SCALAR') { my $buffer = $$local; return sub { my $tmp = $buffer; undef $buffer; $tmp; }; } elsif(ref($local) eq 'GLOB') { sub { # TODO: for big files, maybe # break this up into batches local $/; <$local>; }; } else { die 'bad local type'; } } sub push_command { my $self = shift; $self->{client}->push_command( @_, $self->cv, ); } 1; __END__ =pod =encoding UTF-8 =head1 NAME AnyEvent::FTP::Client::Role::StoreTransfer - Store transfer interface for AnyEvent::FTP objects =head1 VERSION version 0.20 =head1 AUTHOR Author: Graham Ollis Eplicease@cpan.orgE Contributors: Ryo Okamoto Shlomi Fish José Joaquín Atria =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2017-2022 by Graham Ollis. 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 AnyEvent-FTP-0.20/lib/AnyEvent/FTP/Client/Site.pm000644 000000 000000 00000002063 15123245460 021204 0ustar00rootroot000000 000000 package AnyEvent::FTP::Client::Site; use strict; use warnings; use 5.010; # ABSTRACT: Dispatcher for site specific ftp commands our $VERSION = '0.20'; # VERSION sub new { my($class, $client) = @_; bless { client => $client }, $class; } sub AUTOLOAD { our $AUTOLOAD; my $self = shift; my $name = $AUTOLOAD; $name =~ s/^.*://; $name =~ s/_(.)/uc $1/eg; my $class = join('::', qw( AnyEvent FTP Client Site ), ucfirst($name) ); eval qq{ use $class () }; die $@ if $@; $class->new($self->{client}); } # don't autoload DESTROY sub DESTROY { } 1; __END__ =pod =encoding UTF-8 =head1 NAME AnyEvent::FTP::Client::Site - Dispatcher for site specific ftp commands =head1 VERSION version 0.20 =head1 AUTHOR Author: Graham Ollis Eplicease@cpan.orgE Contributors: Ryo Okamoto Shlomi Fish José Joaquín Atria =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2017-2022 by Graham Ollis. 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 AnyEvent-FTP-0.20/lib/AnyEvent/FTP/Client/Site/000755 000000 000000 00000000000 15123245460 020645 5ustar00rootroot000000 000000 AnyEvent-FTP-0.20/lib/AnyEvent/FTP/Client/Site/Base.pm000644 000000 000000 00000001514 15123245460 022056 0ustar00rootroot000000 000000 package AnyEvent::FTP::Client::Site::Base; use strict; use warnings; use 5.010; use Moo; # ABSTRACT: base class for AnyEvent::FTP::Client::Site::* classes our $VERSION = '0.20'; # VERSION sub BUILDARGS { my($class, $client) = @_; return { client => $client }; } has client => ( is => 'ro', required => 1 ); 1; __END__ =pod =encoding UTF-8 =head1 NAME AnyEvent::FTP::Client::Site::Base - base class for AnyEvent::FTP::Client::Site::* classes =head1 VERSION version 0.20 =head1 AUTHOR Author: Graham Ollis Eplicease@cpan.orgE Contributors: Ryo Okamoto Shlomi Fish José Joaquín Atria =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2017-2022 by Graham Ollis. 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 AnyEvent-FTP-0.20/lib/AnyEvent/FTP/Client/Site/Microsoft.pm000644 000000 000000 00000003020 15123245460 023143 0ustar00rootroot000000 000000 package AnyEvent::FTP::Client::Site::Microsoft; use strict; use warnings; use 5.010; use Moo; extends 'AnyEvent::FTP::Client::Site::Base'; # ABSTRACT: Site specific commands for Microsoft FTP Service our $VERSION = '0.20'; # VERSION # TODO add a test for this sub dirstyle { shift->client->push_command([SITE => 'DIRSTYLE'] ) } 1; __END__ =pod =encoding UTF-8 =head1 NAME AnyEvent::FTP::Client::Site::Microsoft - Site specific commands for Microsoft FTP Service =head1 VERSION version 0.20 =head1 SYNOPSIS use AnyEvent::FTP::Client; my $client = AnyEvent::FTP::Client->new; $client->connect('ftp://iisserver')->cb(sub { # toggle dir style $client->site->microsoft->dirstyle->cb(sub { $client->list->cb(sub { my $list = shift # $list is in first style. $client->site->microsoft->dirstyle->cb(sub { $client->list->cb(sub { my $list = shift; # list is in second style. }); }); }); }); }); =head1 DESCRIPTION This class provides Microsoft's IIS SITE commands. =head1 METHODS =head2 dirstyle $client->site->microsoft->dirstyle Toggle between directory listing output styles. =head1 AUTHOR Author: Graham Ollis Eplicease@cpan.orgE Contributors: Ryo Okamoto Shlomi Fish José Joaquín Atria =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2017-2022 by Graham Ollis. 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 AnyEvent-FTP-0.20/lib/AnyEvent/FTP/Client/Site/NetFtpServer.pm000644 000000 000000 00000003250 15123245460 023572 0ustar00rootroot000000 000000 package AnyEvent::FTP::Client::Site::NetFtpServer; use strict; use warnings; use 5.010; use Moo; extends 'AnyEvent::FTP::Client::Site::Base'; # ABSTRACT: Site specific commands for Net::FTPServer our $VERSION = '0.20'; # VERSION # TODO add a test for this sub version { shift->client->push_command([SITE => 'VERSION'] ) } 1; __END__ =pod =encoding UTF-8 =head1 NAME AnyEvent::FTP::Client::Site::NetFtpServer - Site specific commands for Net::FTPServer =head1 VERSION version 0.20 =head1 SYNOPSIS use AnyEvent::FTP::Client; my $client = AnyEvent::FTP::Client->new; $client->connect('ftp://netftpserver')->cb(sub { $client->site->net_ftp_server->version->cb(sub { my($res) = @_; # $res isa AnyEvent::FTP::Client::Response where # the message includes the server version }); }); =head1 DESCRIPTION This class provides the C specific commands for L. =head1 METHODS =head2 version $client->site->net_ftp_server->version Get the L version. =head1 CAVEATS Other C commands supported by L, but not implemented by this class include: =over 4 =item SITE ALIAS =item SITE ARCHIVE =item SITE CDPATH =item SITE CHECKMETHOD =item SITE CHECKSUM =item SITE EXEC =item SITE IDLE =item SITE SYNC =back patches that include tests are welcome. =head1 AUTHOR Author: Graham Ollis Eplicease@cpan.orgE Contributors: Ryo Okamoto Shlomi Fish José Joaquín Atria =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2017-2022 by Graham Ollis. 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 AnyEvent-FTP-0.20/lib/AnyEvent/FTP/Client/Site/Proftpd.pm000644 000000 000000 00000005017 15123245460 022624 0ustar00rootroot000000 000000 package AnyEvent::FTP::Client::Site::Proftpd; use strict; use warnings; use 5.010; use Moo; extends 'AnyEvent::FTP::Client::Site::Base'; # ABSTRACT: Site specific commands for Proftpd our $VERSION = '0.20'; # VERSION sub utime { shift->client->push_command([SITE => "UTIME $_[0] $_[1]"] ) } sub mkdir { shift->client->push_command([SITE => "MKDIR $_[0]"] ) } sub rmdir { shift->client->push_command([SITE => "RMDIR $_[0]"] ) } sub symlink { shift->client->push_command([SITE => "SYMLINK $_[0] $_[1]"] ) } sub ratio { shift->client->push_command([SITE => "RATIO"] ) } sub quota { shift->client->push_command([SITE => "QUOTA"] ) } sub help { shift->client->push_command([SITE => "HELP $_[0]"] ) } sub chgrp { shift->client->push_command([SITE => "CHGRP $_[0] $_[1]"] ) } sub chmod { shift->client->push_command([SITE => "CHMOD $_[0] $_[1]"] ) } 1; __END__ =pod =encoding UTF-8 =head1 NAME AnyEvent::FTP::Client::Site::Proftpd - Site specific commands for Proftpd =head1 VERSION version 0.20 =head1 SYNOPSIS use AnyEvent::FTP::Client; my $client = AnyEvent::FTP::Client->new; $client->connect('ftp://proftpdserver')->cb(sub { $client->site->proftpd->symlink('foo', 'bar'); }); =head1 DESCRIPTION This class implements site specific commands for the Proftpd server. The implementation may be incomplete, and the documentation definitely is. Patches are welcome to fix this. =head1 METHODS =head2 utime $client->site->proftpd->utime( $arg1, $arg2 ); Execute C command. =head2 mkdir $client->site->proftpd->mkdir( $arg1 ); Execute C command. =head2 rmdir $client->site->proftpd->rmdir( $arg1 ); Execute C command. =head2 symlink $client->site->proftpd->symlink( $arg1, $arg2 ); Execute C command. =head2 ratio $client->site->proftpd->ratio; Execute C command. =head2 help $client->site->proftpd->help( $arg1 ); Execute C command. =head2 chgrp $client->site->proftpd->chgrp( $arg1, $arg2 ); Execute C command. =head2 chmodk $client->site->proftpd->chmodk( $arg1, $arg2 ); Execute C command. =head1 AUTHOR Author: Graham Ollis Eplicease@cpan.orgE Contributors: Ryo Okamoto Shlomi Fish José Joaquín Atria =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2017-2022 by Graham Ollis. 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 AnyEvent-FTP-0.20/lib/AnyEvent/FTP/Client/Transfer.pm000644 000000 000000 00000010127 15123245460 022064 0ustar00rootroot000000 000000 package AnyEvent::FTP::Client::Transfer; use strict; use warnings; use 5.010; use Moo; use AnyEvent; use AnyEvent::Handle; use Carp qw( confess ); # ABSTRACT: Transfer class for asynchronous ftp client our $VERSION = '0.20'; # VERSION # TODO: implement ABOR with 'AnyEvent::FTP::Role::Event'; __PACKAGE__->define_events(qw( open close eof )); has cv => ( is => 'ro', lazy => 1, default => sub { AnyEvent->condvar }, ); has client => ( is => 'ro', required => 1, ); has remote_name => ( is => 'rw', lazy => 1, default => sub { shift->command->[1] }, ); has local => ( is => 'ro', required => 1, ); has command => ( is => 'ro', required => 1, ); has restart => ( is => 'ro', default => sub { 0 }, coerce => sub { $_[0] // 0 }, ); sub cb { shift->{cv}->cb(@_) } sub ready { shift->{cv}->ready } sub recv { shift->{cv}->recv } sub handle { my($self, $fh) = @_; my $handle; $handle = AnyEvent::Handle->new( fh => $fh, on_error => sub { my($hdl, $fatal, $msg) = @_; $self->emit('eof'); $_[0]->destroy; }, on_eof => sub { $self->emit('eof'); $handle->destroy; }, # this avoids deep recursion exception error (usually # a warning) in example fput.pl when the buffer is # small (1024 on my debian VM) autocork => 1, ); $self->emit(open => $handle); $handle; } 1; __END__ =pod =encoding UTF-8 =head1 NAME AnyEvent::FTP::Client::Transfer - Transfer class for asynchronous ftp client =head1 VERSION version 0.20 =head1 SYNOPSIS use AnyEvent::FTP::Client; my $client = AnyEvent::FTP::Client; $client->connect('ftp://ftp.cpan.org')->cb(sub { # $upload_transfer and $download_transfer are instances of # AnyEvent::FTP::Client::Transfer my $upload_transfer = $client->stor('remote_filename.txt', 'content'); my $buffer; my $download_transfer = $client->retr('remote_filename.txt', \$buffer); }); =head1 DESCRIPTION This class represents a file transfer with a remote server. Transfers may be initiated between a remote file name and a local object. The local object may be a regular scalar, reference to a scalar or a file handle, for details, see the C, C, C and C methods in L. This documentation covers what you can do with the transfer object once it has been initiated. =head1 ROLES This class consumes these roles: =over 4 =item * L =back =head1 EVENTS This class provides these events: =head2 open Emitted when the data connection is opened, and passes in as its first argument the L instance used to transfer the file. $xfer->on_open(sub { my($handle) = @_; # $handle isa AnyEvent::Handle }); =head2 close Emitted when the transfer is complete, either due to a successful transfer or the server returned a failure status. Passes in the final L message associated with the transfer. $xfer->on_close(sub { my($res) = @_; # $res isa AnyEvent::FTP::Client::Response }); =head2 eof Emitted when the data connection closes. $xfer->on_eof(sub { # no args passed in }); =head1 METHODS =head2 cb Register a callback with the transfer to be executed when the transfer successfully completes, or fails. Works exactly like the L condition variable C method. =head2 ready Returns true if the transfer has completed (either successfully or not). If true, then it is safe to call C to retrieve the response (Some event loops do not support calling C before there is a message waiting). =head2 recv Retrieve the L object. =head2 remote_name For C transfers ONLY, this returns the remote file name. =head1 AUTHOR Author: Graham Ollis Eplicease@cpan.orgE Contributors: Ryo Okamoto Shlomi Fish José Joaquín Atria =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2017-2022 by Graham Ollis. 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 AnyEvent-FTP-0.20/lib/AnyEvent/FTP/Client/Transfer/000755 000000 000000 00000000000 15123245460 021525 5ustar00rootroot000000 000000 AnyEvent-FTP-0.20/lib/AnyEvent/FTP/Client/Transfer/Active.pm000644 000000 000000 00000004233 15123245460 023300 0ustar00rootroot000000 000000 package AnyEvent::FTP::Client::Transfer::Active; use strict; use warnings; use Moo; use 5.010; use AnyEvent; use AnyEvent::Socket qw( tcp_server ); extends 'AnyEvent::FTP::Client::Transfer'; # ABSTRACT: Active transfer class for asynchronous ftp client our $VERSION = '0.20'; # VERSION sub BUILD { my($self) = @_; my $local = $self->convert_local($self->local); my $count = 0; my $guard; $guard = tcp_server $self->client->{my_ip}, undef, sub { my($fh, $host, $port) = @_; # TODO double check the host/port combo here. return close $fh if ++$count > 1; undef $guard; # close to additional connections. $self->xfer($fh,$local); }, sub { my($fh, $host, $port) = @_; my $ip_and_port = join(',', split(/\./, $self->client->{my_ip}), $port >> 8, $port & 0xff); my $w; $w = AnyEvent->timer(after => 0, cb => sub { $self->push_command( [ PORT => $ip_and_port ], ($self->restart > 0 ? ([ REST => $self->restart ]) : ()), $self->command, ); undef $w; }); }; $self->cv->cb(sub { my $res = eval { shift->recv } // $@; $self->emit('close' => $res); }); } package AnyEvent::FTP::Client::Transfer::Active::Fetch; use Moo; extends 'AnyEvent::FTP::Client::Transfer::Active'; with 'AnyEvent::FTP::Client::Role::FetchTransfer'; package AnyEvent::FTP::Client::Transfer::Active::Store; use Moo; extends 'AnyEvent::FTP::Client::Transfer::Active'; with 'AnyEvent::FTP::Client::Role::StoreTransfer'; package AnyEvent::FTP::Client::Transfer::Active::List; use Moo; extends 'AnyEvent::FTP::Client::Transfer::Active'; with 'AnyEvent::FTP::Client::Role::ListTransfer'; 1; __END__ =pod =encoding UTF-8 =head1 NAME AnyEvent::FTP::Client::Transfer::Active - Active transfer class for asynchronous ftp client =head1 VERSION version 0.20 =head1 AUTHOR Author: Graham Ollis Eplicease@cpan.orgE Contributors: Ryo Okamoto Shlomi Fish José Joaquín Atria =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2017-2022 by Graham Ollis. 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 AnyEvent-FTP-0.20/lib/AnyEvent/FTP/Client/Transfer/Passive.pm000644 000000 000000 00000004064 15123245460 023501 0ustar00rootroot000000 000000 package AnyEvent::FTP::Client::Transfer::Passive; use strict; use warnings; use Moo; use 5.010; use AnyEvent::Socket qw( tcp_connect ); extends 'AnyEvent::FTP::Client::Transfer'; # ABSTRACT: Passive transfer class for asynchronous ftp client our $VERSION = '0.20'; # VERSION sub BUILD { my($self) = @_; my $local = $self->convert_local($self->local); my $data_connection = sub { my $res = shift; return if $res->is_preliminary; my($ip, $port) = $res->get_address_and_port; if(defined $ip && defined $port) { tcp_connect $ip, $port, sub { my($fh) = @_; unless($fh) { return "unable to connect to data port: $!"; } $self->xfer($fh,$local); }; return; } else { $res; } }; $self->push_command( [ 'PASV', undef, $data_connection ], ($self->restart > 0 ? ([ REST => $self->restart ]) : ()), $self->command, ); $self->cv->cb(sub { my $res = eval { shift->recv } // $@; $self->emit('close' => $res); }); } package AnyEvent::FTP::Client::Transfer::Passive::Fetch; use Moo; extends 'AnyEvent::FTP::Client::Transfer::Passive'; with 'AnyEvent::FTP::Client::Role::FetchTransfer'; package AnyEvent::FTP::Client::Transfer::Passive::Store; use Moo; extends 'AnyEvent::FTP::Client::Transfer::Passive'; with 'AnyEvent::FTP::Client::Role::StoreTransfer'; package AnyEvent::FTP::Client::Transfer::Passive::List; use Moo; extends 'AnyEvent::FTP::Client::Transfer::Passive'; with 'AnyEvent::FTP::Client::Role::ListTransfer'; 1; __END__ =pod =encoding UTF-8 =head1 NAME AnyEvent::FTP::Client::Transfer::Passive - Passive transfer class for asynchronous ftp client =head1 VERSION version 0.20 =head1 AUTHOR Author: Graham Ollis Eplicease@cpan.orgE Contributors: Ryo Okamoto Shlomi Fish José Joaquín Atria =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2017-2022 by Graham Ollis. 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 AnyEvent-FTP-0.20/lib/AnyEvent/FTP/Request.pm000644 000000 000000 00000003441 15123245460 020513 0ustar00rootroot000000 000000 package AnyEvent::FTP::Request; use strict; use warnings; use 5.010; use overload '""' => sub { shift->as_string }, bool => sub { 1 }, fallback => 1; # ABSTRACT: Request class for asynchronous ftp server our $VERSION = '0.20'; # VERSION sub new { my($class, $cmd, $args, $raw) = @_; bless { command => $cmd, args => $args, raw => $raw }, $class; } sub command { shift->{command} } sub args { shift->{args} } sub raw { shift->{raw} } sub as_string { my $self = shift; join ' ', $self->command, $self->args; } 1; __END__ =pod =encoding UTF-8 =head1 NAME AnyEvent::FTP::Request - Request class for asynchronous ftp server =head1 VERSION version 0.20 =head1 DESCRIPTION Instances of this class represent client requests. =head1 ATTRIBUTES =head2 command my $command = $req->command; The command, usually something like C, C, C, etc. =head2 args my $args = $res->args; The arguments passed in with the command =head2 raw my $raw = $res->raw; The raw, unparsed request. =head1 METHODS =head2 as_string my $str = $res->as_string my $str = "$res"; Returns a string representation of the request. This may not be exactly the same as what was actually sent to the server (see C attribute for that). You can also call this by treating the object like a string (using string operators, or including it in a double quoted string), so print "$req"; is the same as print $req->as_string; =head1 AUTHOR Author: Graham Ollis Eplicease@cpan.orgE Contributors: Ryo Okamoto Shlomi Fish José Joaquín Atria =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2017-2022 by Graham Ollis. 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 AnyEvent-FTP-0.20/lib/AnyEvent/FTP/Response.pm000644 000000 000000 00000005357 15123245460 020671 0ustar00rootroot000000 000000 package AnyEvent::FTP::Response; use strict; use warnings; use 5.010; use overload '""' => sub { shift->as_string }, fallback => 1, bool => sub { 1 }, fallback => 1; # ABSTRACT: Response class for asynchronous ftp client our $VERSION = '0.20'; # VERSION sub new { my($class, $code, $message) = @_; $message = [ $message ] unless ref($message) eq 'ARRAY'; bless { code => $code, message => $message }, $class; } sub code { shift->{code} } sub message { shift->{message} } sub is_success { shift->{code} !~ /^[45]/ } sub is_preliminary { shift->{code} =~ /^1/ } sub as_string { my($self) = @_; sprintf "[%d] %s%s", $self->{code}, $self->{message}->[0], @{ $self->{message} } > 1 ? '...' : ''; } 1; __END__ =pod =encoding UTF-8 =head1 NAME AnyEvent::FTP::Response - Response class for asynchronous ftp client =head1 VERSION version 0.20 =head1 DESCRIPTION Instances of this class represent a FTP server response. =head1 ATTRIBUTES =head2 code my $code = $client->code; Integer code for the message. These can be categorized thus: =over 4 =item 1xx Positive preliminary reply =item 2xx Positive completion reply =item 3xx Positive intermediate reply =item 4xx Transient negative reply =item 5xx Permanent negative reply =back Generally C<4xx> and C<5xx> messages are errors, where as C<1xx>, C<3xx> are various states of (at least so far) successful operations. C<2xx> indicates a completely successful operation. =head2 message my $message = $res->message; The human readable message returned from the server. This is always a list reference, even if the server only returned one line. =head1 METHODS =head2 is_success my $bool = $res->is_success; True if the response does not represent an error condition (codes C<1xx>, C<2xx> or C<3xx>). =head2 is_preliminary my $bool = $res->is_preliminary; True if the response is a preliminary positive reply (code C<1xx>). =head2 as_string my $str = $res->as_string; my $str = "$res"; Returns a string representation of the response. This may not be exactly what was returned by the server, but will include the code and at least part of the message in a human readable format. You can also get this string by treating objects of this class as a string (using it in a double quoted string, or by using string operators): print "$res"; is the same as print $res->as_string; =head1 AUTHOR Author: Graham Ollis Eplicease@cpan.orgE Contributors: Ryo Okamoto Shlomi Fish José Joaquín Atria =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2017-2022 by Graham Ollis. 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 AnyEvent-FTP-0.20/lib/AnyEvent/FTP/Role/000755 000000 000000 00000000000 15123245460 017424 5ustar00rootroot000000 000000 AnyEvent-FTP-0.20/lib/AnyEvent/FTP/Role/Event.pm000644 000000 000000 00000004757 15123245460 021060 0ustar00rootroot000000 000000 package AnyEvent::FTP::Role::Event; use strict; use warnings; use 5.010; use Moo::Role; # ABSTRACT: Event interface for AnyEvent::FTP objects our $VERSION = '0.20'; # VERSION sub define_events { my $class = shift; foreach my $name (@_) { my $method_name = join '::', $class, "on_$name"; my $method = sub { my($self, $cb) = @_; push @{ $self->{event}->{$name} }, $cb; $self; }; no strict 'refs'; *$method_name = $method; } } sub emit { my($self, $name, @args) = @_; for(@{ $self->{event}->{$name} }) { eval { $_->(@args) }; warn $@ if $@; } } 1; __END__ =pod =encoding UTF-8 =head1 NAME AnyEvent::FTP::Role::Event - Event interface for AnyEvent::FTP objects =head1 VERSION version 0.20 =head1 SYNOPSIS package AnyEvent::FTP::Foo; use Moo; with 'AnyEvent::FTP::Role::Event'; __PACKAGE__->define_events(qw( error good )); sub some_method { my($self) = @_; if($self->other_method) { $self->emit(good => 'paylod message'); } else { $self->emit(error => 'something went wrong!'); } } later on somewhere else use AnyEvent::FTP::Foo; my $foo = AnyEvent::FTP::Foo->new; $foo->on_good(sub { my($message) = @_; print "worked: $message"; }); $foo->on_error(sub { my($message) = @_; print "failed: $message"; }); $foo->some_method =head1 DESCRIPTION This role provides a uniform even callback mechanism for classes in L. You declare events by using the C method. Once declared you can use CI to add a callback to a particular event and C to trigger those callbacks. =head1 METHODS =head2 define_events __PACKAGE__->define_events( @list_of_event_names ); This is called within the class package to declare the event names for all events used by the class. It creates methods of the form CI which can be used to add callbacks to events. =head2 emit $obj->emit($event_name, @arguments); This calls the callbacks associated with the given C<$event_name>. It will pass to that callback the given C<@arguments>. =head1 SEE ALSO =over 4 =item * L =back =head1 AUTHOR Author: Graham Ollis Eplicease@cpan.orgE Contributors: Ryo Okamoto Shlomi Fish José Joaquín Atria =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2017-2022 by Graham Ollis. 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 AnyEvent-FTP-0.20/lib/AnyEvent/FTP/Server.pm000644 000000 000000 00000014013 15123245460 020326 0ustar00rootroot000000 000000 package AnyEvent::FTP::Server; use strict; use warnings; use 5.010; use Moo; use AnyEvent::Handle; use AnyEvent::Socket qw( tcp_server ); use AnyEvent::FTP::Server::Connection; use Socket qw( unpack_sockaddr_in inet_ntoa ); # ABSTRACT: Simple asynchronous ftp server our $VERSION = '0.20'; # VERSION $AnyEvent::FTP::Server::VERSION //= 'dev'; with 'AnyEvent::FTP::Role::Event'; __PACKAGE__->define_events(qw( bind connect )); has hostname => ( is => 'ro', ); has port => ( is => 'ro', default => sub { 21 }, ); has default_context => ( is => 'ro', default => sub { 'AnyEvent::FTP::Server::Context::FSRW' }, ); has welcome => ( is => 'ro', default => sub { [ 220 => "aeftpd $AnyEvent::FTP::Server::VERSION" ] }, ); has bindport => ( is => 'rw', ); has inet => ( is => 'ro', default => sub { 0 }, ); sub BUILD { eval 'use ' . shift->default_context; die $@ if $@; } sub start { my($self) = @_; $self->inet ? $self->_start_inet : $self->_start_standalone; } sub _start_inet { my($self) = @_; my $con = AnyEvent::FTP::Server::Connection->new( context => $self->{default_context}->new, ip => do { my $sockname = getsockname STDIN; my ($sockport, $sockaddr) = unpack_sockaddr_in ($sockname); inet_ntoa ($sockaddr); }, ); my $handle; $handle = AnyEvent::Handle->new( fh => *STDIN, on_error => sub { my($hdl, $fatal, $msg) = @_; $con->close; $_[0]->destroy; undef $handle; undef $con; }, on_eof => sub { $con->close; $handle->destroy; undef $handle; undef $con; }, ); $self->emit(connect => $con); STDOUT->autoflush(1); STDIN->autoflush(1); $con->on_response(sub { my($raw) = @_; print STDOUT $raw; }); $con->on_close(sub { close STDOUT; exit; }); $con->send_response(@{ $self->welcome }); $handle->on_read(sub { $handle->push_read( line => sub { my($handle, $line) = @_; $con->process_request($line); }); }); $self; } sub _start_standalone { my($self) = @_; my $prepare = sub { my($fh, $host, $port) = @_; $self->bindport($port); $self->emit(bind => $port); }; my $connect = sub { my($fh, $host, $port) = @_; my $con = AnyEvent::FTP::Server::Connection->new( context => $self->{default_context}->new, ip => do { my($port, $addr) = unpack_sockaddr_in getsockname $fh; inet_ntoa $addr; }, ); my $handle; $handle = AnyEvent::Handle->new( fh => $fh, on_error => sub { my($hdl, $fatal, $msg) = @_; $con->close; $_[0]->destroy; undef $handle; undef $con; }, on_eof => sub { $con->close; $handle->destroy; undef $handle; undef $con; }, ); $self->emit(connect => $con); $con->on_response(sub { my($raw) = @_; $handle->push_write($raw); }); $con->on_close(sub { $handle->push_shutdown; }); $con->send_response(@{ $self->welcome }); $handle->on_read(sub { $handle->push_read( line => sub { my($handle, $line) = @_; $con->process_request($line); }); }); }; tcp_server $self->hostname, $self->port || undef, $connect, $prepare; $self; } 1; __END__ =pod =encoding UTF-8 =head1 NAME AnyEvent::FTP::Server - Simple asynchronous ftp server =head1 VERSION version 0.20 =head1 SYNOPSIS use AnyEvent; use AnyEvent::FTP::Server; my $server = AnyEvent::FTP::Server->new; $server->start; AnyEvent->condvar->recv; =head1 DESCRIPTION B L hasn't been audited by anyone, including its author, in order to ensure that it is secure. It is intended to be used primarily in testing the companion client L. It can also be used to write your own context or personality (to use the L terminology) that use alternate back ends (say a database or memory store) that could theoretically be made to be secure, but you will need to carefully vett both the L code as well as your own customizations before you deploy on the Internet or on an untrusted network. This class is used for L server instances. Each time a client connects to the server a L instance is created to manage the TCP connection. Each connection also has a L which defines the behavior or personality of the server, and each context instance keeps track of the current directory, user authentication and authorization status of each connected client. =head1 ATTRIBUTES =head2 hostname my $hostname = $server->hostname; Readonly, and should be assigned at the constructor. The hostname to listen on. =head2 port my $port = $server->port; The port to listen to. Default is 21 - a different port can be assigned at the constructor. =head2 default_context my $context = $server->default_context; Readonly: the default context class (can be set as a parameter in the constructor). =head2 welcome my($code, $message) = @{ $server->welcome }; The welcome messages as key value pairs. Read only and can be overridden by the constructor. =head2 bindport my $port = $server->bindport; $server->bindport($port); Retrieves or sets the TCP port to bind to. =head2 inet my $bool = $server->inet; Readonly (assignable via the constructor). If true, then assume a TCP connection has been established by inet. The default (false) is to start a standalone server. =head1 METHODS =head2 start $server->start; Call this method to start the service. =head1 SEE ALSO L =head1 AUTHOR Author: Graham Ollis Eplicease@cpan.orgE Contributors: Ryo Okamoto Shlomi Fish José Joaquín Atria =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2017-2022 by Graham Ollis. 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 AnyEvent-FTP-0.20/lib/AnyEvent/FTP/Server/000755 000000 000000 00000000000 15123245460 017771 5ustar00rootroot000000 000000 AnyEvent-FTP-0.20/lib/AnyEvent/FTP/Server/Connection.pm000644 000000 000000 00000004277 15123245460 022440 0ustar00rootroot000000 000000 package AnyEvent::FTP::Server::Connection; use strict; use warnings; use 5.010; use Moo; use AnyEvent::FTP::Request; # ABSTRACT: FTP Server connection class our $VERSION = '0.20'; # VERSION with 'AnyEvent::FTP::Role::Event'; __PACKAGE__->define_events(qw( request response close )); has context => ( is => 'ro', required => 1, ); has response_encoder => ( is => 'ro', lazy => 1, default => sub { require AnyEvent::FTP::Server::UnambiguousResponseEncoder; AnyEvent::FTP::Server::UnambiguousResponseEncoder->new; }, ); has ip => ( is => 'ro', required => 1, ); sub process_request { my($self, $line) = @_; my $raw = $line; $self->emit(request => $raw); $line =~ s/\015?\012//g; if($line =~ s/^([A-Z]{1,4})\s?//i) { $self->context->push_request($self, AnyEvent::FTP::Request->new(uc $1, $line, $raw)); } else { $self->context->invalid_syntax($self, $raw); } $self; } sub send_response { my $self = shift; my $raw = $self->response_encoder->encode(@_); $self->emit(response => $raw); $self; } sub close { my($self) = shift; $self->emit('close'); } 1; __END__ =pod =encoding UTF-8 =head1 NAME AnyEvent::FTP::Server::Connection - FTP Server connection class =head1 VERSION version 0.20 =head1 METHODS =head2 close $conn->close; Emits a close signal. =head2 context my $context_obj = $conn->context; Required to be specified by the constructor and readonly - this is the context object. =head2 ip my $ip = $conn->ip; Required to be specified by the constructor and readonly - this is the IP address. =head2 process_request $conn->process_request($line); Process a single request. Returns the connection object ($conn). =head2 send_response $conn->send_response($res) $conn->send_response($code, $message) Sends the response. Returns the connection object ($conn). =head1 AUTHOR Author: Graham Ollis Eplicease@cpan.orgE Contributors: Ryo Okamoto Shlomi Fish José Joaquín Atria =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2017-2022 by Graham Ollis. 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 AnyEvent-FTP-0.20/lib/AnyEvent/FTP/Server/Context.pm000644 000000 000000 00000006242 15123245460 021757 0ustar00rootroot000000 000000 package AnyEvent::FTP::Server::Context; use strict; use warnings; use 5.010; use Moo; # ABSTRACT: FTP Server client context class our $VERSION = '0.20'; # VERSION with 'AnyEvent::FTP::Role::Event'; with 'AnyEvent::FTP::Server::Role::Context'; __PACKAGE__->define_events(qw( auth )); has ready => ( is => 'rw', default => sub { 1 }, ); has ascii_layer => ( is => 'rw', default => ':raw:eol(CRLF-Native)' ); sub push_request { my($self, $con, $req) = @_; push @{ $self->{request_queue} }, [ $con, $req ]; $self->process_queue if $self->ready; $self; } sub process_queue { my($self) = @_; return $self unless @{ $self->{request_queue} } > 0; $self->ready(0); my($con, $req) = @{ shift @{ $self->{request_queue} } }; my $command = lc $req->command; if($self->can('auth_command_check_hook')) { return unless $self->auth_command_check_hook($con, $command); } my $method = join '_', 'cmd', $command; if($self->can($method)) { $self->$method($con, $req); } else { $self->invalid_command($con, $req); } $self; } sub invalid_command { my($self, $con, $req) = @_; $con->send_response(500 => $req->command . ' not understood'); $self->done; } sub invalid_syntax { my($self, $con, $raw) = @_; $con->send_response(500 => 'Command not understood'); $self->done; } sub help_quit { "QUIT" } sub cmd_quit { my($self, $con, $req) = @_; $con->send_response(221 => 'Goodbye'); $con->close; $self; } sub done { my($self) = @_; $self->ready(1); $self->process_queue; $self; } 1; __END__ =pod =encoding UTF-8 =head1 NAME AnyEvent::FTP::Server::Context - FTP Server client context class =head1 VERSION version 0.20 =head1 METHODS =head2 cmd_quit $ctx->cmd_quit($con, $req); Sends a quit command through $con ($req is unused.). Returns the $ctx object. =head2 done $ctx->done; B: document. Returns the $ctx object. =head2 help_quit my $quit_str = $ctx->help_quit; Returns the string "QUIT". =head2 invalid_command $ctx->invalid_command($con, $req); Sends an invalid command due to the request $req through $con. =head2 invalid_syntax $ctx->invalid_syntax($con, $raw); Sends a command not understood response through $con. =head2 process_queue $ctx->process_queue; Processes the request queue. =head2 push_request $ctx->push_request($con, $req); Pushes the request to the queue. =head2 ready my $bool = $ctx->ready; $ctx->ready($bool) Gets or sets the "is ready" status, which is a boolean. =head2 ascii_layer $ctx->ascii_layer; The L layer to apply for writing (C, C, C) and reading (C) when operating under ASCII file transfer mode. By default a layer that takes C and emits native line endings is used for writing and a takes native line endings and emits C when reading is used. =head1 AUTHOR Author: Graham Ollis Eplicease@cpan.orgE Contributors: Ryo Okamoto Shlomi Fish José Joaquín Atria =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2017-2022 by Graham Ollis. 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 AnyEvent-FTP-0.20/lib/AnyEvent/FTP/Server/Context/000755 000000 000000 00000000000 15123245460 021415 5ustar00rootroot000000 000000 AnyEvent-FTP-0.20/lib/AnyEvent/FTP/Server/Context/FS.pm000644 000000 000000 00000014771 15123245460 022275 0ustar00rootroot000000 000000 package AnyEvent::FTP::Server::Context::FS; use strict; use warnings; use 5.010; use Moo; use File::chdir; use File::Spec; extends 'AnyEvent::FTP::Server::Context'; # ABSTRACT: FTP server context that uses real file system (no transfers) our $VERSION = '0.20'; # VERSION with 'AnyEvent::FTP::Server::Role::Auth'; with 'AnyEvent::FTP::Server::Role::Help'; with 'AnyEvent::FTP::Server::Role::Old'; with 'AnyEvent::FTP::Server::Role::Type'; sub cwd { my($self, $value) = @_; $self->{cwd} = $value if defined $value; $self->{cwd} //= '/'; } sub rename_from { my($self, $value) = @_; $self->{rename_from} = $value if defined $value; $self->{rename_from}; } sub help_cwd { 'CWD pathname' } sub cmd_cwd { my($self, $con, $req) = @_; my $dir = $req->args; eval { die unless $dir; use autodie; local $CWD = $self->cwd; $CWD = $dir; $self->cwd($CWD); $con->send_response(250 => 'CWD command successful'); }; $con->send_response(550 => 'CWD error') if $@; $self->done; } sub help_cdup { 'CDUP' } sub cmd_cdup { my($self, $con, $req) = @_; eval { use autodie; local $CWD = $self->cwd; $CWD = File::Spec->updir; $self->cwd($CWD); $con->send_response(250 => 'CDUP command successful'); }; $con->send_response(550 => 'CDUP error') if $@; $self->done; } sub help_pwd { 'PWD' } sub cmd_pwd { my($self, $con, $req) = @_; my $cwd = $self->cwd; if($^O eq 'MSWin32') { (undef,$cwd) = File::Spec->splitpath($cwd, 1); $cwd =~ s{\\}{/}g; } $con->send_response(257 => "\"$cwd\" is the current directory"); $self->done; } sub help_size { 'SIZE pathname' } sub cmd_size { my($self, $con, $req) = @_; eval { use autodie; local $CWD = $self->cwd; if(-f $req->args) { my $size = -s $req->args; $con->send_response(213 => $size); } elsif(-e $req->args) { $con->send_response(550 => $req->args . ": not a regular file"); } else { die; } }; if($@) { $con->send_response(550 => $req->args . ": No such file or directory"); } $self->done; } sub help_mkd { 'MKD pathname' } sub cmd_mkd { my($self, $con, $req) = @_; my $dir = $req->args; eval { use autodie; local $CWD = $self->cwd; mkdir $dir; $con->send_response(257 => "Directory created"); }; $con->send_response(550 => "MKD error") if $@; $self->done; } sub help_rmd { 'RMD pathname' } sub cmd_rmd { my($self, $con, $req) = @_; my $dir = $req->args; eval { use autodie; local $CWD = $self->cwd; rmdir $dir; $con->send_response(250 => "Directory removed"); }; $con->send_response(550 => "RMD error") if $@; $self->done; } sub help_dele { 'DELE pathname' } sub cmd_dele { my($self, $con, $req) = @_; my $file = $req->args; eval { use autodie; local $CWD = $self->cwd; unlink $file; $con->send_response(250 => "File removed"); }; $con->send_response(550 => "DELE error") if $@; $self->done; } sub help_rnfr { 'RNFR pathname' } sub cmd_rnfr { my($self, $con, $req) = @_; my $path = $req->args; if($path) { eval { local $CWD = $self->cwd; if(!-e $path) { $con->send_response(550 => 'No such file or directory'); } elsif(-w $path) { $self->rename_from($path); $con->send_response(350 => 'File or directory exists, ready for destination name'); } else { $con->send_response(550 => 'Permission denied'); } }; if(my $error = $@) { warn $error; $con->send_response(550 => 'Rename failed'); } } else { $con->send_response(501 => 'Invalid number of arguments'); } $self->done; } sub help_rnto { 'RNTO pathname' } sub cmd_rnto { my($self, $con, $req) = @_; my $path = $req->args; if(! defined $self->rename_from) { $con->send_response(503 => 'Bad sequence of commands'); } elsif(!$path) { $con->send_response(501 => 'Invalid number of arguments'); } else { eval { local $CWD = $self->cwd; if(! -e $path) { rename $self->rename_from, $path; $con->send_response(250 => 'Rename successful'); } else { $con->send_response(550 => 'File already exists'); } }; if(my $error = $@) { warn $error; $con->send_response(550 => 'Rename failed'); } } $self->done; } sub help_stat { 'STAT [ pathname]' } sub cmd_stat { my($self, $con, $req) = @_; my $path = $req->args; if($path) { do { local $CWD = $self->cwd; if(-d $path) { $con->send_response(211 => "it's a directory"); } elsif(-f $path) { $con->send_response(211 => "it's a file"); } else { $con->send_response(450 => 'No such file or directory'); } }; } else { # TODO: did I have a good reason for making this # not be an error? $con->send_response(211 => "it's all good."); } $self->done; } 1; __END__ =pod =encoding UTF-8 =head1 NAME AnyEvent::FTP::Server::Context::FS - FTP server context that uses real file system (no transfers) =head1 VERSION version 0.20 =head1 SYNOPSIS use AnyEvent::FTP::Server; my $server = AnyEvent::FTP::Server->new( default_context => 'AnyEvent::FTP::Server::Context::FS', ); =head1 DESCRIPTION This is the base class for L and L. =head1 ROLES This class consumes these roles: =over 4 =item * L =item * L =item * L =item * L =back =head1 ATTRIBUTES =head2 cwd my $dir = $context->cwd; The current working directory as a string. =head2 rename_from my $filename = $context-Erename_from; The filename specified by the last FTP C command. =head1 COMMANDS In addition to the commands provided by the above roles, this context provides these FTP commands: =over 4 =item CWD =item CDUP =item PWD =item SIZE =item MKD =item RMD =item DELE =item RNFR =item RNTO =item STAT =back =head1 AUTHOR Author: Graham Ollis Eplicease@cpan.orgE Contributors: Ryo Okamoto Shlomi Fish José Joaquín Atria =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2017-2022 by Graham Ollis. 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 AnyEvent-FTP-0.20/lib/AnyEvent/FTP/Server/Context/FSRO.pm000644 000000 000000 00000003076 15123245460 022532 0ustar00rootroot000000 000000 package AnyEvent::FTP::Server::Context::FSRO; use strict; use warnings; use 5.010; use Moo; extends 'AnyEvent::FTP::Server::Context::FSRW'; # ABSTRACT: FTP Server client context class with read-only access our $VERSION = '0.20'; # VERSION sub cmd_stor { my($self, $con, $req) = @_; unless(defined $self->data) { $con->send_response(425 => 'Unable to build data connection') } else { $con->send_response(553 => "Permission denied") } $self->done; } *cmd_appe = \&cmd_stor; *cmd_stou = \&cmd_stor; 1; __END__ =pod =encoding UTF-8 =head1 NAME AnyEvent::FTP::Server::Context::FSRO - FTP Server client context class with read-only access =head1 VERSION version 0.20 =head1 SYNOPSIS use AnyEvent::FTP::Server; my $server = AnyEvent::FTP::Server->new( default_context => 'AnyEvent::FTP::Server::Context::FSRO', ); =head1 DESCRIPTION This class provides a context for L which uses the actual filesystem to provide storage. =head1 SUPER CLASS This class inherits from L =head1 COMMANDS In addition to the commands provided by the above user class, this context provides these FTP commands: =over 4 =item STOR =item APPE =item STOU =back =head1 AUTHOR Author: Graham Ollis Eplicease@cpan.orgE Contributors: Ryo Okamoto Shlomi Fish José Joaquín Atria =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2017-2022 by Graham Ollis. 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 AnyEvent-FTP-0.20/lib/AnyEvent/FTP/Server/Context/FSRW.pm000644 000000 000000 00000021050 15123245460 022532 0ustar00rootroot000000 000000 package AnyEvent::FTP::Server::Context::FSRW; use strict; use warnings; use 5.010; use Moo; use File::chdir; use File::ShareDir::Dist qw( dist_share ); use File::Which qw( which ); use File::Temp qw( tempfile ); use Capture::Tiny qw( capture ); extends 'AnyEvent::FTP::Server::Context::FS'; # ABSTRACT: FTP Server client context class with full read/write access our $VERSION = '0.20'; # VERSION with 'AnyEvent::FTP::Server::Role::TransferPrep'; sub _layer { $_[0]->type eq 'A' ? $_[0]->ascii_layer : ':raw'; } sub help_retr { 'RETR pathname' } sub cmd_retr { my($self, $con, $req) = @_; my $fn = $req->args; unless(defined $self->data) { $con->send_response(425 => 'Unable to build data connection'); return; } eval { use autodie; local $CWD = $self->cwd; if(-f $fn) { # TODO: re-write so that this does not blocks my $type = $self->type eq 'A' ? 'ASCII' : 'Binary'; my $size = -s $fn; $con->send_response(150 => "Opening $type mode data connection for $fn ($size bytes)"); open my $fh, '<', $fn; binmode $fh, $self->_layer; seek $fh, $self->restart_offset, 0 if $self->restart_offset; $self->data->push_write(do { local $/; <$fh> }); close $fh; $self->data->push_shutdown; $con->send_response(226 => 'Transfer complete'); } elsif(-e $fn && !-d $fn) { $con->send_response(550 => 'Permission denied'); } else { $con->send_response(550 => 'No such file'); } }; if(my $error = $@) { warn $error; if(eval { $error->can('errno') }) { $con->send_response(550 => $error->errno) } else { $con->send_response(550 => 'Internal error') } }; $self->clear_data; $self->done; } sub help_nlst { 'NLST [ (pathname)]' } sub cmd_nlst { my($self, $con, $req) = @_; my $dir = $req->args || '.'; unless(defined $self->data) { $con->send_response(425 => 'Unable to build data connection'); return; } eval { use autodie; local $CWD = $self->cwd; $con->send_response(150 => "Opening ASCII mode data connection for file list"); my $dh; opendir $dh, $dir; my @list = map { $req->args ? join('/', $dir, $_) : $_ } sort grep !/^\.\.?$/, readdir $dh; closedir $dh; $self->data->push_write(join '', map { $_ . "\015\012" } @list); $self->data->push_shutdown; $con->send_response(226 => 'Transfer complete'); }; if(my $error = $@) { warn $error; if(eval { $error->can('errno') }) { $con->send_response(550 => $error->errno) } else { $con->send_response(550 => 'Internal error') } }; $self->clear_data; $self->done; } sub help_list { 'LIST [ pathname]' } sub cmd_list { my($self, $con, $req) = @_; my $dir = $req->args || '.'; $dir = '.' if $dir eq '-l'; unless(defined $self->data) { $con->send_response(425 => 'Unable to build data connection'); return; } eval { use autodie; my @cmd = _shared_cmd('ls', '-l', $dir); local $CWD = $self->cwd; $con->send_response(150 => "Opening ASCII mode data connection for file list"); my $dh; opendir $dh, $dir; $self->data->push_write(join "\015\012", split /\n/, scalar capture { system @cmd }); closedir $dh; $self->data->push_write("\015\012"); $self->data->push_shutdown; $con->send_response(226 => 'Transfer complete'); }; if(my $error = $@) { warn $error; if(eval { $error->can('errno') }) { $con->send_response(550 => $error->errno) } else { $con->send_response(550 => 'Internal error') } }; $self->clear_data; $self->done; } sub help_stor { 'STOR pathname' } sub cmd_stor { my($self, $con, $req) = @_; my $fn = $req->args; unless(defined $self->data) { $con->send_response(425 => 'Unable to build data connection'); return; } eval { use autodie; local $CWD = $self->cwd; my $type = $self->type eq 'A' ? 'ASCII' : 'Binary'; $con->send_response(150 => "Opening $type mode data connection for $fn"); open my $fh, '>', $fn; binmode $fh, $self->_layer; $self->data->on_read(sub { $self->data->push_read(sub { print $fh $_[0]{rbuf}; $_[0]{rbuf} = ''; }); }); $self->data->on_error(sub { close $fh; $self->data->push_shutdown; $con->send_response(226 => 'Transfer complete'); $self->clear_data; $self->done; }); }; if(my $error = $@) { warn $error; if(eval { $error->can('errno') }) { $con->send_response(550 => $error->errno) } else { $con->send_response(550 => 'Internal error') } $self->clear_data; $self->done; }; } sub help_appe { 'APPE pathname' } sub cmd_appe { my($self, $con, $req) = @_; my $fn = $req->args; unless(defined $self->data) { $con->send_response(425 => 'Unable to build data connection'); return; } eval { use autodie; local $CWD = $self->cwd; my $type = $self->type eq 'A' ? 'ASCII' : 'Binary'; $con->send_response(150 => "Opening $type mode data connection for $fn"); open my $fh, '>>', $fn; binmode $fh, $self->_layer; $self->data->on_read(sub { $self->data->push_read(sub { print $fh $_[0]{rbuf}; $_[0]{rbuf} = ''; }); }); $self->data->on_error(sub { close $fh; $self->data->push_shutdown; $con->send_response(226 => 'Transfer complete'); $self->clear_data; $self->done; }); }; if(my $error = $@) { warn $error; if(eval { $error->can('errno') }) { $con->send_response(550 => $error->errno) } else { $con->send_response(550 => 'Internal error') } $self->clear_data; $self->done; }; } sub help_stou { 'STOU (store unique filename)' } sub cmd_stou { my($self, $con, $req) = @_; my $fn = $req->args; unless(defined $self->data) { $con->send_response(425 => 'Unable to build data connection'); return; } eval { use autodie; local $CWD = $self->cwd; my $fh; if($fn && ! -e $fn) { open $fh, '>', $fn; } else { ($fh,$fn) = tempfile( "aefXXXXXX", TMPDIR => 0 ) } my $type = $self->type eq 'A' ? 'ASCII' : 'Binary'; $con->send_response(150 => "FILE: $fn"); binmode $fh, $self->_layer; $self->data->on_read(sub { $self->data->push_read(sub { print $fh $_[0]{rbuf}; $_[0]{rbuf} = ''; }); }); $self->data->on_error(sub { close $fh; $self->data->push_shutdown; $con->send_response(226 => 'Transfer complete'); $self->clear_data; $self->done; }); }; if(my $error = $@) { warn $error; if(eval { $error->can('errno') }) { $con->send_response(550 => $error->errno) } else { $con->send_response(550 => 'Internal error') } $self->clear_data; $self->done; }; } { state $always_use_bundled_cmd = $ENV{ANYEVENT_FTP_BUNDLED_CMD}; my %shared; sub _shared_cmd { my ($cmd, @args) = @_; unless (defined $shared{$cmd}) { my $which = which $cmd; if ($which && !$always_use_bundled_cmd) { $shared{$cmd} = [ $which ]; } else { $shared{$cmd} = [ $^X, # use the same Perl File::Spec->catfile((dist_share('AnyEvent-FTP') or die "unable to find share directory") , 'ppt', "$cmd.pl"), ]; } } return @{ $shared{$cmd} }, @args; } } 1; __END__ =pod =encoding UTF-8 =head1 NAME AnyEvent::FTP::Server::Context::FSRW - FTP Server client context class with full read/write access =head1 VERSION version 0.20 =head1 SYNOPSIS use AnyEvent::FTP::Server; my $server = AnyEvent::FTP::Server->new( default_context => 'AnyEvent::FTP::Server::Context::FSRW', ); =head1 DESCRIPTION This class provides a context for L which uses the actual filesystem to provide storage. =head1 SUPER CLASS This class inherits from L =head1 ROLES This class consumes these roles: =over 4 L =back =head1 COMMANDS In addition to the commands provided by the above roles, and super class, this context provides these FTP commands: =over 4 =item RETR =item NLST =item LIST =item STOR =item APPE =item STOU =back =head1 AUTHOR Author: Graham Ollis Eplicease@cpan.orgE Contributors: Ryo Okamoto Shlomi Fish José Joaquín Atria =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2017-2022 by Graham Ollis. 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 AnyEvent-FTP-0.20/lib/AnyEvent/FTP/Server/Context/Memory.pm000644 000000 000000 00000024035 15123245460 023227 0ustar00rootroot000000 000000 package AnyEvent::FTP::Server::Context::Memory; use strict; use warnings; use 5.010; use Moo; use Path::Class::File; use Path::Class::Dir; extends 'AnyEvent::FTP::Server::Context'; # ABSTRACT: FTP Server client context class with full read/write access our $VERSION = '0.20'; # VERSION with 'AnyEvent::FTP::Server::Role::Auth'; with 'AnyEvent::FTP::Server::Role::Help'; with 'AnyEvent::FTP::Server::Role::Old'; with 'AnyEvent::FTP::Server::Role::Type'; with 'AnyEvent::FTP::Server::Role::TransferPrep'; sub store { # The store for this class is global. # if you wanted each connection or user # to have their own store you could subclass # and redefine the store method as apropriate state $store = {}; $store; } has cwd => ( is => 'rw', default => sub { Path::Class::Dir->new_foreign('Unix', '/'); }, ); sub _first_index (&@) { my $f = shift; foreach my $i ( 0 .. $#_ ) { local *_ = \$_[$i]; return $i if $f->(); } return -1; } sub find { my($self, $path) = @_; $path = Path::Class::Dir->new_foreign('Unix', $path) unless ref $path; $path = Path::Class::Dir->new_foreign('Unix', $self->cwd, $path) unless $path->is_absolute; my $store = $self->store; return $store if $path eq '/'; my @list = $path->components; while(1) { my $i = _first_index { $_ eq '..' } @list; last if $i == -1; if($i > 1) { splice @list, $i-1, 2; } else { splice @list, $i, 1; } } shift @list; # shift off the root my $top = pop @list; foreach my $part (@list) { if(exists($store->{$part}) && ref($store->{$part}) eq 'HASH') { $store = $store->{$part}; } else { return; } } if(exists $store->{$top}) { return $store->{$top} } else { return } } sub rename_from { my($self, $value) = @_; $self->{rename_from} = $value if defined $value; $self->{rename_from}; } sub help_cwd { 'CWD pathname' } sub cmd_cwd { my($self, $con, $req) = @_; my $dir = Path::Class::Dir->new_foreign('Unix', $req->args)->cleanup; $dir = $dir->absolute($self->cwd) unless $dir->is_absolute; my @list = grep !/^\.$/, $dir->components; while(1) { my $i = _first_index { $_ eq '..' } @list; last if $i == -1; if($i > 1) { splice @list, $i-1, 2; } else { splice @list, $i, 1; } } $dir = Path::Class::Dir->new_foreign('Unix', @list); if(ref($self->find($dir)) eq 'HASH') { $self->cwd($dir); $con->send_response(250 => 'CWD command successful'); } else { $con->send_response(550 => 'CWD error'); } $self->done; } sub help_cdup { 'CDUP' } sub cmd_cdup { my($self, $con, $req) = @_; my $dir = $self->cwd->parent; if(ref($self->find($dir)) eq 'HASH') { $self->cwd($dir); $con->send_response(250 => 'CDUP command successful'); } else { $con->send_response(550 => 'CDUP error'); } $self->done; } sub help_pwd { 'PWD' } sub cmd_pwd { my($self, $con, $req) = @_; my $cwd = $self->cwd; $con->send_response(257 => "\"$cwd\" is the current directory"); $self->done; } sub help_size { 'SIZE pathname' } sub cmd_size { my($self, $con, $req) = @_; my $file = $self->find(Path::Class::File->new_foreign('Unix', $req->args)); if(defined($file) && !ref($file)) { $con->send_response(213 => length $file); } elsif(defined $file) { $con->send_response(550 => $req->args . ": not a regular file"); } else { $con->send_response(550 => $req->args . ": No such file or directory"); } $self->done; } sub help_mkd { 'MKD pathname' } sub cmd_mkd { my($self, $con, $req) = @_; my $path = Path::Class::Dir->new_foreign('Unix', $req->args); my $file = $self->find($path->parent); if($path->basename ne '' && defined($file) && ref($file) eq 'HASH') { if(exists $file->{$path->basename}) { $con->send_response(521 => "\"$path\" directory exists"); } else { $file->{$path->basename} = {}; $con->send_response(257 => "\"$path\" new directory created"); } } else { $con->send_response(550 => "MKD error"); } $self->done; } sub help_rmd { 'RMD pathname' } sub cmd_rmd { my($self, $con, $req) = @_; # TODO: be more picky about rmd and file or dele a directory my $path = Path::Class::Dir->new_foreign('Unix', $req->args); my $file = $self->find($path->parent); if(defined($file) && ref($file) eq 'HASH') { if(exists $file->{$path->basename}) { delete $file->{$path->basename}; $con->send_response(250 => "RMD command successful"); } else { $con->send_response(550 => "$path: No such file or directory"); } } else { $con->send_response(550 => "$path: No such file or directory"); } $self->done; } sub help_dele { 'DELE pathname' } sub cmd_dele { my($self, $con, $req) = @_; my $path = Path::Class::File->new_foreign('Unix', $req->args); my $file = $self->find($path->parent); if(defined($file) && ref($file) eq 'HASH') { if(exists $file->{$path->basename}) { delete $file->{$path->basename}; $con->send_response(250 => "File removed"); } else { $con->send_response(550 => "$path: No such file or directory"); } } else { $con->send_response(550 => "$path: No such file or directory"); } $self->done; } sub help_rnfr { 'RNFR pathname' } sub cmd_rnfr { my($self, $con, $req) = @_; my $path = Path::Class::File->new_foreign('Unix', $req->args); my $dir = $self->find($path->parent); if(ref($dir) eq 'HASH') { if(exists $dir->{$path->basename}) { $self->rename_from([$dir,$path->basename]); $con->send_response(350 => 'File or directory exists, ready for destination name'); } else { $con->send_response(550 => 'No such file or directory'); } } else { $con->send_response(550 => 'No such file or directory'); } $self->done; } sub help_rnto { 'RNTO pathname' } sub cmd_rnto { my($self, $con, $req) = @_; my $from = $self->rename_from; unless(defined $from) { $con->send_response(503 => 'Bad sequence of commands'); $self->done; return; } my $path = Path::Class::File->new_foreign('Unix', $req->args); my $dir = $self->find($path->parent); if(ref($dir) eq 'HASH') { if(exists $dir->{$path->basename}) { $con->send_response(550 => 'File already exists'); } else { $dir->{$path->basename} = delete $from->[0]->{$from->[1]}; $con->send_response(250 => 'Rename successful'); } } else { $con->send_response(550 => 'Rename failed'); } $self->done; } sub help_stat { 'STAT [ pathname]' } sub cmd_stat { my($self, $con, $req) = @_; my $file = $self->find($req->args); if(defined $file) { if(ref($file) eq 'HASH') { $con->send_response(211 => "It's a directory"); } else { $con->send_response(211 => "It's a file"); } } else { $con->send_response(450 => 'No such file or directory'); } $self->done; } sub help_nlst { 'NLST [ (pathname)]' } sub cmd_nlst { my($self, $con, $req) = @_; my $dir = $req->args; unless(defined $self->data) { $con->send_response(425 => 'Unable to build data connection'); return; } eval { $con->send_response(150 => "Opening ASCII mode data connection for file list"); my @list; if($dir) { my $h = $self->find($dir); if(ref($h) eq 'HASH') { $dir = Path::Class::Dir->new_foreign('Unix', $dir); @list = map { $dir->file($_) } sort keys %$h; } else { $dir = Path::Class::File->new_foreign('Unix', $dir); @list = "$dir"; } } else { my $h = $self->find($self->cwd); die 'unable to find cwd' unless defined $h; @list = sort keys %$h; } $self->data->push_write(join '', map { $_ . "\015\012" } @list); $self->data->push_shutdown; $con->send_response(226 => 'Transfer complete'); }; if(my $error = $@) { warn $error; if(eval { $error->can('errno') }) { $con->send_response(550 => $error->errno) } else { $con->send_response(550 => 'Internal error') } }; $self->clear_data; $self->done; } 1; # TODO: cmd_retr # TODO: cmd_list # TODO: cmd_stor # TODO: cmd_appe # TODO: cmd_stou __END__ =pod =encoding UTF-8 =head1 NAME AnyEvent::FTP::Server::Context::Memory - FTP Server client context class with full read/write access =head1 VERSION version 0.20 =head1 SYNOPSIS use AnyEvent::FTP::Server; my $server = AnyEvent::FTP::Server->new( default_context => 'AnyEvent::FTP::Server::Context::Memory', ); =head1 DESCRIPTION This class provides a context for L which uses memory to provide storage. Once the server process terminates, all data stored is lost. Note that this implementation is incomplete. =head1 ROLES This class consumes these roles: =over 4 =item * L =item * L =item * L =item * L =back =head1 ATTRIBUTES =head2 store Has containing the directory tree for the context. =head2 cwd The current working directory for the context. This will be an L. =head2 find Returns the hash (for directory) or scalar (for file) of a file in the filesystem. =head2 rename_from my $filename = $context->rename_from; The filename specified by the last FTP C command. =head1 COMMANDS In addition to the commands provided by the above roles, this context provides these FTP commands: =over 4 =item CWD =item CDUP =item PWD =item SIZE =item MKD =item RMD =item DELE =item RNFR =item RNTO =item STAT =item NLST =back =head1 AUTHOR Author: Graham Ollis Eplicease@cpan.orgE Contributors: Ryo Okamoto Shlomi Fish José Joaquín Atria =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2017-2022 by Graham Ollis. 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 AnyEvent-FTP-0.20/lib/AnyEvent/FTP/Server/OS/000755 000000 000000 00000000000 15123245460 020312 5ustar00rootroot000000 000000 AnyEvent-FTP-0.20/lib/AnyEvent/FTP/Server/OS/UNIX.pm000644 000000 000000 00000004676 15123245460 021450 0ustar00rootroot000000 000000 package AnyEvent::FTP::Server::OS::UNIX; use strict; use warnings; use 5.010; use Moo; # ABSTRACT: UNIX implementations for AnyEvent::FTP our $VERSION = '0.20'; # VERSION sub BUILDARGS { my($class, $query) = @_; my($name, $pw, $uid, $gid, $quota, $comment, $gcos, $dir, $shell, $expire) = getpwnam $query; die "user not found" unless $name; return { name => $name, uid => $uid, gid => $gid, home => $dir, shell => $shell, } } has $_ => ( is => 'ro', required => 1 ) for (qw( name uid gid home shell )); has groups => ( is => 'ro', lazy => 1, default => sub { my $name = shift->name; my @groups; setgrent; my @grent; while(@grent = getgrent) { my($group,$pw,$gid,$members) = @grent; foreach my $member (split /\s+/, $members) { push @groups, $gid if $member eq $name; } } \@groups; }, ); sub jail { my($self) = @_; chroot $self->home; return $self; } sub drop_privileges { my($self) = @_; $) = join ' ', $self->gid, $self->gid, @{ $self->groups }; $> = $self->uid; $( = $self->gid; $< = $self->uid; return $self; } 1; __END__ =pod =encoding UTF-8 =head1 NAME AnyEvent::FTP::Server::OS::UNIX - UNIX implementations for AnyEvent::FTP =head1 VERSION version 0.20 =head1 SYNOPSIS use AnyEvent::FTP::Server::OS::UNIX; # interface using user fred my $unix = AnyEvent::FTP::Server::OS::UNIX->new('fred'); $unix->jail; # chroot $unix->drop_privileges; # transform into user fred =head1 DESCRIPTION This class provides some utility functionality for interacting with the UNIX and UNIX like operating systems. =head1 ATTRIBUTES =head2 name The user's username =head2 uid The user's UID =head2 gid The user's GID =head2 home The user's home directory =head2 shell The user's shell =head2 groups List of groups (as GIDs) that the user also belongs to. =head1 METHODS =head2 jail $unix->jail; C to the users' home directory. Requires root and the chroot function. =head2 drop_privileges $unix->drop_privileges; Drop super user privileges =head1 AUTHOR Author: Graham Ollis Eplicease@cpan.orgE Contributors: Ryo Okamoto Shlomi Fish José Joaquín Atria =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2017-2022 by Graham Ollis. 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 AnyEvent-FTP-0.20/lib/AnyEvent/FTP/Server/Role/000755 000000 000000 00000000000 15123245460 020672 5ustar00rootroot000000 000000 AnyEvent-FTP-0.20/lib/AnyEvent/FTP/Server/Role/Auth.pm000644 000000 000000 00000011421 15123245460 022130 0ustar00rootroot000000 000000 package AnyEvent::FTP::Server::Role::Auth; use strict; use warnings; use 5.010; use Moo::Role; # ABSTRACT: Authentication role for FTP server our $VERSION = '0.20'; # VERSION has user => (is => 'rw'); has authenticated => (is => 'rw', default => sub { 0 } ); has authenticator => ( is => 'rw', lazy => 1, default => sub { sub { 0 } }, ); has bad_authentication_delay => ( is => 'rw', default => sub { 5 }, ); has _safe_commands => ( is => 'ro', lazy => 1, default => sub { my %h = map { (lc $_ => 1) } @{ shift->unauthenticated_safe_commands }; \%h; }, ); has unauthenticated_safe_commands => ( is => 'ro', lazy => 1, default => sub { [qw( USER PASS HELP QUIT )] }, ); sub auth_command_check_hook { my($self, $con, $command) = @_; return 1 if $self->authenticated || $self->_safe_commands->{$command}; $con->send_response(530 => 'Please login with USER and PASS'); $self->done; return 0; } sub help_user { 'USER username' } sub cmd_user { my($self, $con, $req) = @_; my $user = $req->args; $user =~ s/^\s+//; $user =~ s/\s+$//; if($user ne '') { $self->user($user); $con->send_response(331 => "Password required for $user"); } else { $con->send_response(530 => "USER requires a parameter"); } $self->done; } sub help_pass { 'PASS password' } sub cmd_pass { my($self, $con, $req) = @_; my $user = $self->user; my $pass = $req->args; unless(defined $user) { $con->send_response(503 => 'Login with USER first'); $self->done; return; } if($self->authenticator->($user, $pass)) { $con->send_response(230 => "User $user logged in"); $self->{authenticated} = 1; $self->emit(auth => $user); $self->done; } else { my $delay = $self->bad_authentication_delay; if($delay > 0) { my $timer; $timer = AnyEvent->timer( after => 5, cb => sub { $con->send_response(530 => 'Login incorrect'); $self->done; undef $timer; }); } else { $con->send_response(530 => 'Login incorrect'); $self->done; } } } 1; __END__ =pod =encoding UTF-8 =head1 NAME AnyEvent::FTP::Server::Role::Auth - Authentication role for FTP server =head1 VERSION version 0.20 =head1 SYNOPSIS In your context: package AnyEvent::FTP::Server::Context::MyContext; use Moo; extends 'AnyEvent::FTP::Server::Context'; with 'AnyEvent::FTP::Server::Role::Auth'; has '+unauthenticated_safe_commands' => ( default => sub { [ qw( USER PASS HELP QUIT FOO ) ] }, ); # this command is deemed safe pre auth by # unauthenticated_safe_commands sub cmd_foo { my($self, $con, $req) = @_; $con->send_response(211 => 'Here to stay'); $self->done; } # this command can pnly be executed after # authentication sub cmd_bar { my($self, $con, $req) = @_; $con->send_response(211 => 'And another thing'); $self->done; } Then when you create your server object: use AnyEvent:FTP::Server; my $server = AnyEvent::FTP::Server->new; $server->on_connect(sub { # $con isa AnyEvent::FTP::Server::Connection my $con = shift; # $context isa AnyEvent::FTP::Server::Context::MyContext my $context = $con->context; # allow login from user 'user' with password 'secret' $context->authenticator(sub { my($user, $pass) = @_; return $user eq 'user' && $pass eq 'secret'; }); # make the client wait 5 seconds if they enter a # bad username / password $context->bad_authentication_delay(5); }); =head1 DESCRIPTION This role provides an authentication interface for your L context. =head1 ATTRIBUTES =head2 user The user specified by the last FTP C command. =head2 authenticated True if the user has successfully logged in. =head2 authenticator Sub ref used to check username password combinations. By default all authentication requests are refused. =head2 bad_authentication_delay Number of seconds to wait after a bad login attempt. =head2 unauthenticated_safe_commands List of the commands that are safe to execute before the user has authenticated. The default is USER, PASS, HELP and QUIT =head1 METHODS =head2 auth_command_check_hook $context->auth_command_check_hook($connection, $command); This hook checks that any commands executed by the client before authentication are in the C list. =head1 COMMANDS =over 4 =item USER =item PASS =back =head1 AUTHOR Author: Graham Ollis Eplicease@cpan.orgE Contributors: Ryo Okamoto Shlomi Fish José Joaquín Atria =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2017-2022 by Graham Ollis. 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 AnyEvent-FTP-0.20/lib/AnyEvent/FTP/Server/Role/Context.pm000644 000000 000000 00000001307 15123245460 022655 0ustar00rootroot000000 000000 package AnyEvent::FTP::Server::Role::Context; use strict; use warnings; use 5.010; use Moo::Role; # ABSTRACT: Server connection context role our $VERSION = '0.20'; # VERSION requires 'push_request'; 1; __END__ =pod =encoding UTF-8 =head1 NAME AnyEvent::FTP::Server::Role::Context - Server connection context role =head1 VERSION version 0.20 =head1 AUTHOR Author: Graham Ollis Eplicease@cpan.orgE Contributors: Ryo Okamoto Shlomi Fish José Joaquín Atria =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2017-2022 by Graham Ollis. 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 AnyEvent-FTP-0.20/lib/AnyEvent/FTP/Server/Role/Help.pm000644 000000 000000 00000005420 15123245460 022121 0ustar00rootroot000000 000000 package AnyEvent::FTP::Server::Role::Help; use strict; use warnings; use 5.010; use Moo::Role; # ABSTRACT: Help role for FTP server our $VERSION = '0.20'; # VERSION my %cmds; sub help_help { 'HELP [ command]' } sub cmd_help { my($self, $con, $req) = @_; my $topic = $req->args; $topic =~ s/^\s+//; $topic =~ s/\s+$//; $topic = lc $topic; if($topic eq '') { my $class = ref $self; unless(defined $cmds{$class}) { no strict 'refs'; $cmds{$class} = [ sort map { my $x = $_; $x =~ s/^cmd_//; uc $x } grep /^cmd_/, keys %{$class . '::'} ]; } $con->send_response(214, [ 'The following commands are recognized:', join(' ', @{ $cmds{$class} }), 'Direct comments to devnull@bogus', ]); } elsif($self->can("cmd_$topic")) { my $method = "help_$topic"; if($self->can("help_$topic")) { $con->send_response(214 => 'Syntax: ' . $self->$method) } else { $con->send_response(502 => uc($topic) . " is a command without help"); } } else { $con->send_response(502 => 'Unknown command'); } $self->done; } 1; __END__ =pod =encoding UTF-8 =head1 NAME AnyEvent::FTP::Server::Role::Help - Help role for FTP server =head1 VERSION version 0.20 =head1 SYNOPSIS Create a context: package AnyEvent::FTP::Server::Context::EchoContext; use Moo; extends 'AnyEvent::FTP::Server::Context'; with 'AnyEvent::FTP::Server::Role::Help'; # implement the non-existent echo command sub help_echo { 'ECHO text' } sub cmd_echo { my($self, $con, $req) = @_; $con->send_response(211 => $req->args); $self->done; } 1; Start a server with that context: % aeftpd --context EchoContext ftp://dfzcgohteq:igdcphxled@localhost:59402 Then connect to that server and test the C command: % telnet localhost 59402 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. 220 aeftpd dev help 214-The following commands are recognized: 214-ECHO HELP 214 Direct comments to devnull@bogus help help 214 Syntax: HELP [ command] help echo 214 Syntax: ECHO text help bogus 502 Unknown command quit 221 Goodbye Connection closed by foreign host. =head1 DESCRIPTION This role provides a standard FTP C command. It finds any FTP commands (C) you have defined in your context class and the associated usage functions (C) and implements the C command for you. =head1 AUTHOR Author: Graham Ollis Eplicease@cpan.orgE Contributors: Ryo Okamoto Shlomi Fish José Joaquín Atria =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2017-2022 by Graham Ollis. 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 AnyEvent-FTP-0.20/lib/AnyEvent/FTP/Server/Role/Old.pm000644 000000 000000 00000004471 15123245460 021754 0ustar00rootroot000000 000000 package AnyEvent::FTP::Server::Role::Old; use strict; use warnings; use 5.010; use Moo::Role; # ABSTRACT: Role for old archaic FTP server commands our $VERSION = '0.20'; # VERSION has syst => ( is => 'rw', lazy => 1, default => sub { 'UNIX Type: L8' } ); sub help_allo { 'ALLO is not implemented (ignored)' } sub cmd_allo { my($self, $con, $req) = @_; $con->send_response(202 => 'No storage allocation necessary'); $self->done; } sub help_noop { 'NOOP' } sub cmd_noop { my($self, $con, $req) = @_; $con->send_response(200 => 'NOOP command successful'); $self->done; } sub help_syst { 'SYST' } sub cmd_syst { my($self, $con, $req) = @_; $con->send_response(215 => $self->syst); $self->done; } 1; __END__ =pod =encoding UTF-8 =head1 NAME AnyEvent::FTP::Server::Role::Old - Role for old archaic FTP server commands =head1 VERSION version 0.20 =head1 SYNOPSIS Create a context: package AnyEvent::FTP::Server::Context::MyContext; use Moo; extends 'AnyEvent::FTP::Server::Context'; with 'AnyEvent::FTP::Server::Role::Old'; 1; Use archaic FTP commands: % telnet localhost 39835 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. 220 aeftpd dev user foo 331 Password required for foo pass bar 230 User foo logged in allo 202 No storage allocation necessary noop 200 NOOP command successful syst 215 UNIX Type: L8 quit 221 Goodbye Connection closed by foreign host. =head1 DESCRIPTION This role provides a bunch of FTP commands that don't really do anything anymore, but some older clients might try to use anyway. If you are writing a context, it is probably a good idea to consume this role rather than implementing these useless commands yourself. =head1 ATTRIBUTES =head2 syst The string returned by the SYST command. This is often "UNIX Type: L8" even if the server isn't actually running on UNIX. That is also the default. =head1 COMMANDS =over 4 =item ALLO =item NOOP =item SYST =back =head1 AUTHOR Author: Graham Ollis Eplicease@cpan.orgE Contributors: Ryo Okamoto Shlomi Fish José Joaquín Atria =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2017-2022 by Graham Ollis. 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 AnyEvent-FTP-0.20/lib/AnyEvent/FTP/Server/Role/ResponseEncoder.pm000644 000000 000000 00000001335 15123245460 024330 0ustar00rootroot000000 000000 package AnyEvent::FTP::Server::Role::ResponseEncoder; use strict; use warnings; use 5.010; use Moo::Role; # ABSTRACT: Server response encoder role our $VERSION = '0.20'; # VERSION requires 'encode'; requires 'new'; 1; __END__ =pod =encoding UTF-8 =head1 NAME AnyEvent::FTP::Server::Role::ResponseEncoder - Server response encoder role =head1 VERSION version 0.20 =head1 AUTHOR Author: Graham Ollis Eplicease@cpan.orgE Contributors: Ryo Okamoto Shlomi Fish José Joaquín Atria =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2017-2022 by Graham Ollis. 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 AnyEvent-FTP-0.20/lib/AnyEvent/FTP/Server/Role/TransferPrep.pm000644 000000 000000 00000010252 15123245460 023643 0ustar00rootroot000000 000000 package AnyEvent::FTP::Server::Role::TransferPrep; use strict; use warnings; use 5.010; use Moo::Role; use AnyEvent; use AnyEvent::Socket qw( tcp_server tcp_connect ); use AnyEvent::Handle; # ABSTRACT: Interface for PASV, PORT and REST commands our $VERSION = '0.20'; # VERSION has data => ( is => 'rw', ); has restart_offset => ( is => 'rw', ); sub clear_data { my($self) = @_; $self->data(undef); $self->restart_offset(undef); } sub help_pasv { 'PASV (returns address/port)' } sub cmd_pasv { my($self, $con, $req) = @_; my $count = 0; tcp_server undef, undef, sub { my($fh, $host, $port) = @_; return close $fh if ++$count > 1; my $handle; $handle = AnyEvent::Handle->new( fh => $fh, on_error => sub { $_[0]->destroy; undef $handle; }, on_eof => sub { $handle->destroy; undef $handle; }, autocork => 1, ); $self->data($handle); # TODO this should be with the 227 message below. # demoting this to a TODO (was a F-I-X-M-E) # since I can't remember why I thought it needed # doing. plicease 12-05-2014 $self->done; }, sub { my($fh, $host, $port) = @_; my $ip_and_port = join(',', split(/\./, $con->ip), $port >> 8, $port & 0xff); my $w; $w = AnyEvent->timer(after => 0, cb => sub { $con->send_response(227 => "Entering Passive Mode ($ip_and_port)"); undef $w; }); }; return; } sub help_port { 'PORT h1,h2,h3,h4,p1,p2' } sub cmd_port { my($self, $con, $req) = @_; if($req->args =~ /(\d+,\d+,\d+,\d+),(\d+),(\d+)/) { my $ip = join '.', split /,/, $1; my $port = $2*256 + $3; tcp_connect $ip, $port, sub { my($fh) = @_; unless($fh) { $con->send_response(500 => "Illegal PORT command"); $self->done; return; } my $handle; $handle = AnyEvent::Handle->new( fh => $fh, on_error => sub { $_[0]->destroy; undef $handle; }, on_eof => sub { $handle->destroy; undef $handle; }, ); $self->data($handle); $con->send_response(200 => "Port command successful"); $self->done; }; } else { $con->send_response(500 => "Illegal PORT command"); $self->done; return; } } sub help_rest { 'REST byte-count' } sub cmd_rest { my($self, $con, $req) = @_; if($req->args =~ /^\s*(\d+)\s*$/) { my $offset = $1; $con->send_response(350 => "Restarting at $offset. Send STORE or RETRIEVE to initiate transfer"); $self->restart_offset($offset); } else { $con->send_response(501 => "REST requires a value greater than or equal to 0"); } $self->done; } 1; __END__ =pod =encoding UTF-8 =head1 NAME AnyEvent::FTP::Server::Role::TransferPrep - Interface for PASV, PORT and REST commands =head1 VERSION version 0.20 =head1 SYNOPSIS package AnyEvent::FTP::Server::Context::MyContext; use Moo; extends 'AnyEvent::FTP::Server::Context'; with 'AnyEvent::FTP::Server::Role::TransferPrep'; =head1 DESCRIPTION This role provides the FTP transfer preparation commands C, C and C to your FTP server context. It isn't really useful by itself, and needs a transfer role, like L or L. =head1 ATTRIBUTES =head2 data my $connection = $context->data The data connection prepared from the FTP C or C command. This is an L. =head2 restart_offset my $offset = $context->restart_offset; The offset specified in the last FTP C command. This should be a positive integer. =head1 METHODS =head2 clear_data $context->clear_data; Clears the C and C attributes. =head1 COMMANDS =over 4 =item PASV =item PORT =item REST =back =head1 AUTHOR Author: Graham Ollis Eplicease@cpan.orgE Contributors: Ryo Okamoto Shlomi Fish José Joaquín Atria =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2017-2022 by Graham Ollis. 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 AnyEvent-FTP-0.20/lib/AnyEvent/FTP/Server/Role/Type.pm000644 000000 000000 00000003060 15123245460 022150 0ustar00rootroot000000 000000 package AnyEvent::FTP::Server::Role::Type; use strict; use warnings; use 5.010; use Moo::Role; # ABSTRACT: Type role for FTP server our $VERSION = '0.20'; # VERSION has type => ( is => 'rw', default => sub { 'A' }, ); sub help_type { 'TYPE type-code (A, I)' } sub cmd_type { my($self, $con, $req) = @_; my $type = uc $req->args; $type =~ s/^\s+//; $type =~ s/\s+$//; if($type eq 'A' || $type eq 'I') { $self->type($type); $con->send_response(200 => "Type set to $type"); } else { $con->send_response(500 => "Type not understood"); } $self->done; } # TODO: STRU MODE 1; __END__ =pod =encoding UTF-8 =head1 NAME AnyEvent::FTP::Server::Role::Type - Type role for FTP server =head1 VERSION version 0.20 =head1 SYNOPSIS package AnyEvent::FTP::Server::Context::MyContext; use Moo; extends 'AnyEvent::FTP::Server::Context'; with 'AnyEvent::FTP::Server::Role::Type'; =head1 DESCRIPTION This role provides an interface for the FTP C command. =head1 ATTRIBUTES =head2 type my $type = $context->type; $context->type('A'); $context->type('I'); The current transfer type 'A' for ASCII and I for binary. =head1 COMMANDS =over 4 =item TYPE =back =head1 AUTHOR Author: Graham Ollis Eplicease@cpan.orgE Contributors: Ryo Okamoto Shlomi Fish José Joaquín Atria =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2017-2022 by Graham Ollis. 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 AnyEvent-FTP-0.20/lib/AnyEvent/FTP/Server/UnambiguousResponseEncoder.pm000644 000000 000000 00000003404 15123245460 025645 0ustar00rootroot000000 000000 package AnyEvent::FTP::Server::UnambiguousResponseEncoder; use strict; use warnings; use 5.010; use Moo; # ABSTRACT: Server response encoder that encodes responses so they cannot be confused our $VERSION = '0.20'; # VERSION with 'AnyEvent::FTP::Server::Role::ResponseEncoder'; sub encode { my $self = shift; my $code; my $message; if(ref $_[0]) { $code = $_[0]->code; $message = $_[0]->message; } else { ($code, $message) = @_; } $message = [ $message ] unless ref($message) eq 'ARRAY'; my $last = pop @$message; return join "\015\012", (map { "$code-$_" } @$message), "$code $last\015\012"; } 1; __END__ =pod =encoding UTF-8 =head1 NAME AnyEvent::FTP::Server::UnambiguousResponseEncoder - Server response encoder that encodes responses so they cannot be confused =head1 VERSION version 0.20 =head1 SYNOPSIS use AnyEvent::FTP::Server::UnambiguousResponseEncoder; my $encoder = AnyEvent::FTP::Server::UnambiguousResponseEncoder->new; # encode a FTP welcome message my $message = $encoder->encode(220, 'welcome to myftpd'); =head1 DESCRIPTION Objects of this class are used to encode responses which are returned to the client from the server. =head1 METHODS =head2 encode my $str = $encoder->encode( $res ); my $str = $encoder->encode( $code, $message ); Returns the encoded message. You can pass in either a L object, or a code message pair. =head1 AUTHOR Author: Graham Ollis Eplicease@cpan.orgE Contributors: Ryo Okamoto Shlomi Fish José Joaquín Atria =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2017-2022 by Graham Ollis. 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 AnyEvent-FTP-0.20/lib/Test/000755 000000 000000 00000000000 15123245460 015260 5ustar00rootroot000000 000000 AnyEvent-FTP-0.20/lib/Test/AnyEventFTPServer.pm000644 000000 000000 00000035700 15123245460 021115 0ustar00rootroot000000 000000 package Test::AnyEventFTPServer; use strict; use warnings; use 5.010; use Moo; use URI; use AnyEvent; use Test2::API qw( context ); use Path::Class qw( tempdir ); extends 'AnyEvent::FTP::Server'; # ABSTRACT: Test (non-blocking) ftp clients against a real FTP server our $VERSION = '0.20'; # VERSION has test_uri => ( is => 'ro', required => 1, ); has res => ( is => 'rw', ); has content => ( is => 'rw', default => '', ); has auto_login => ( is => 'rw', default => sub { 1 }, ); has _client => ( is => 'ro', lazy => 1, default => sub { my $self = shift; require AnyEvent::FTP::Client; my $client = AnyEvent::FTP::Client->new; my $cv = AnyEvent->condvar; my $timer = AnyEvent->timer( after => 5, cb => sub { $cv->croak("timeout connecting with ftp client") }, ); if($self->auto_login) { $client->connect($self->test_uri) ->cb(sub { $cv->send }); } else { $client->connect($self->test_uri->host, $self->test_uri->port) ->cb(sub { $cv->send }); } $cv->recv; $client; }, ); sub create_ftpserver_ok (;$$) { my($context, $message) = @_; my $ctx = context(); my $uri = URI->new("ftp://127.0.0.1"); $context //= 'Memory'; $context = "AnyEvent::FTP::Server::Context::$context" unless $context =~ /::/; my $name = (split /::/, $context)[-1]; my $user = join '', map { chr(ord('a') + int rand(26)) } (1..10); my $pass = join '', map { chr(ord('a') + int rand(26)) } (1..10); $uri->userinfo(join(':', $user, $pass)); my $server; eval { $server = Test::AnyEventFTPServer->new( default_context => $context, hostname => '127.0.0.1', port => undef, test_uri => $uri, ); if($ENV{AEF_DEBUG}) { $server->on_connect(sub { my $con = shift; $ctx->note("CONNECT"); $con->on_request(sub { my $raw = shift; $ctx->note("CLIENT: $raw"); }); $con->on_response(sub { my $raw = shift; $ctx->note("SERVER: $raw"); }); $con->on_close(sub { $ctx->note("DISCONNECT"); }); }); } $server->on_connect(sub { shift->context->authenticator(sub { return $_[0] eq $user && $_[1] eq $pass; }); }); my $cv = AnyEvent->condvar; my $timer = AnyEvent->timer( after => 5, cb => sub { $cv->croak("timeout creating ftp server") }, ); $server->on_bind(sub { $uri->port(shift); $cv->send; }); $server->start; $cv->recv; }; my $error = $@; $message //= "created FTP ($name) server at $uri"; $ctx->ok($error eq '', $message); $ctx->diag($error) if $error; $ctx->release; $server; } sub connect_ftpclient_ok { my($self, $message) = @_; my $client; eval { require AnyEvent::FTP::Client; $client = AnyEvent::FTP::Client->new; my $cv = AnyEvent->condvar; my $timer = AnyEvent->timer( after => 5, cb => sub { $cv->croak("timeout connecting with ftp client") }, ); if($self->auto_login) { $client->connect($self->test_uri) ->cb(sub { $cv->send }); } else { $client->connect($self->tesT_uri->host, $self->test_uri->port) ->cb(sub { $cv->send }); } $cv->recv; }; my $error = $@; $message //= "connected to FTP server at " . $self->test_uri; my $ctx = context(); $ctx->ok($error eq '', $message); $ctx->diag($error) if $error; $ctx->release; $client; } sub help_coverage_ok { my($self, $class, $message) = @_; $class //= $self->default_context; my @missing; my $client = eval { $self->_client }; my $error = $@; my $count = 0; unless($error) { foreach my $cmd (map { uc $_ } grep s/^cmd_//, eval qq{ use $class; keys \%${class}::;}) { if((eval { $client->help($cmd)->recv } || $@)->code != 214) { push @missing, $cmd } $count++; } } $message //= "help coverage for $class"; my $ctx = context(); $ctx->ok($error eq '' && @missing == 0, $message); $ctx->diag($error) if $error; $ctx->diag("commands missing help: @missing") if @missing; $ctx->diag("didn't find ANY commands for class: $class") if $count == 0; $ctx->release; $self; } sub command_ok { my($self, $command, $args, $message) = @_; my $client = eval { $self->_client }; my $error = $@; unless($error) { my $res = (eval { $client->push_command([$command, $args])->recv } || $@); if(eval { $res->isa('AnyEvent::FTP::Client::Response') }) { $self->res($res) } else { $error = $res; $self->res(undef) } } $message //= "command: $command"; my $ctx = context(); $ctx->ok($error eq '', $message); $ctx->diag($error) if $error; $ctx->release; $self; } sub code_is { my($self, $code, $message) = @_; $message //= "response code is $code"; my $ctx = context(); my $actual = eval { $self->res->code } // 'undefined'; $ctx->ok($actual == $code, $message); $ctx->diag("actual code returned is $actual") unless $actual == $code; $ctx->release; $self; } sub code_like { my($self, $regex, $message) = @_; $message //= "response code matches"; my $ctx = context(); my $actual = eval { $self->res->code } // 'undefined'; $ctx->ok($actual =~ $regex, $message); $ctx->diag("code $actual does not match $regex") unless $actual =~ $regex; $ctx->release; $self; } sub message_like { my($self, $regex, $message) = @_; $message //= "response message matches"; my $ok = 0; my @message = @{ (eval { $self->res->message }) // [] }; foreach my $line (@message) { $ok = 1 if $line =~ $regex; } my $ctx = context(); $ctx->ok($ok, $message); unless($ok) { $ctx->diag("message: "); $ctx->diag(" $_") for @message; $ctx->diag("does not match $regex"); } $ctx->release; $self; } sub message_is { my($self, $string, $message) = @_; $message //= "response message matches"; my $ok = 0; my @message = @{ (eval { $self->res->message }) // [] }; foreach my $line (@message) { $ok = 1 if $line eq $string; } my $ctx = context(); $ctx->ok($ok, $message); unless($ok) { $ctx->diag("message: "); $ctx->diag(" $_") for @message; $ctx->diag("does not match $string"); } $ctx->release; $self; } sub list_ok { my($self, $location, $message) = @_; $message //= defined $location ? "list: $location" : 'list'; my $client = eval { $self->_client }; my $error = $@; $self->content(''); unless($error) { my $list = eval { $client->list($location)->recv }; $error = $@; $self->content(join "\n", @$list, '') unless $error; } my $ctx = context(); $ctx->ok($error eq '', $message); $ctx->diag($error) if $error; $ctx->release; $self; } sub nlst_ok { my($self, $location, $message) = @_; $message //= defined $location ? "nlst: $location" : 'nlst'; my $client = eval { $self->_client }; my $error = $@; $self->content(''); unless($error) { my $list = eval { $client->nlst($location)->recv }; $error = $@; $self->content(join "\n", @$list, '') unless $error; } my $ctx = context(); $ctx->ok($error eq '', $message); $ctx->diag($error) if $error; $ctx->release; $self; } sub _display_content { state $temp; state $counter = 0; my $method = 'diag'; #$method = 'note' if $tb->todo; unless(defined $temp) { $temp = tempdir(CLEANUP => 1); } my $file = $temp->file(sprintf("data.%d", $counter++)); $file->spew($_[0]); my $ctx = context(); if(-T $file) { $ctx->$method(" $_") for split /\n/, $_[0]; } else { if(eval { require Data::HexDump }) { $ctx->$method(" $_") for grep !/^$/, split /\n/, Data::HexDump::HexDump($_[0]); } else { $ctx->$method(" binary content"); } } $ctx->release; $file->remove; } sub content_is { my($self, $string, $message) = @_; $message ||= 'content matches'; my $ok = $self->content eq $string; my $ctx = context(); $ctx->ok($ok, $message); unless($ok) { $ctx->diag("content:"); _display_content($self->content); $ctx->diag("expected:"); _display_content($string); } $ctx->release; $self; } sub global_timeout_ok (;$$) { my($timeout, $message) = @_; $timeout //= 120; $message //= "global timeout of $timeout seconds"; my $ctx = context(); state $timers = []; eval { push @$timers, AnyEvent->timer( after => $timeout, cb => sub { $ctx->diag("GLOBAL TIMEOUT"); exit }, ); }; my $error = $@; my $ok = $error eq ''; $ctx->ok($ok, $message); $ctx->diag($error) if $error; $ctx->release; $ok; } sub import { my $caller = caller; no strict 'refs'; *{join '::', $caller, 'create_ftpserver_ok'} = \&create_ftpserver_ok; *{join '::', $caller, 'global_timeout_ok'} = \&global_timeout_ok; } BEGIN { eval 'use EV' } 1; __END__ =pod =encoding UTF-8 =head1 NAME Test::AnyEventFTPServer - Test (non-blocking) ftp clients against a real FTP server =head1 VERSION version 0.20 =head1 SYNOPSIS use Test2:V0; use Test::AnyEventFTPServer; # exit this script after 30s to avoid hung test global_timeout_ok; # $test_server isa AnyEvent::FTP::Server # and isa Test::AnyEventFTPServer my $test_server = create_ftpserver_ok; $test_server->command_ok('HELP') ->code_is(214) ->message_like(qr{the following commands are recognize}); # $res isa AnyEvent::FTP::Client::Response # from that last HELP command my $res = $test_server->res; # $client isa AnyEvent::FTP::Client my $client = $test_server->connect_ftpclient_ok; # check to make sure that all FTP commands have help $test_server->help_coverage_ok; done_testing; =head1 DESCRIPTION This module makes it easy to test ftp clients against a real L FTP server. The FTP server is non-blocking in and does not C, so if you are testing a FTP client that blocks then you will need to do it in a separate process. L is a client that doesn't block and so is safe to use in testing against the server. =head1 ATTRIBUTES =head2 test_uri my $uri = $test_server->test_uri The full URL (including host, port, username and password) of the test ftp server. This is returned as L. =head2 res my $res = $test_server->res The last L object returned from the server after calling the C method. =head2 content my $content = $test_server->content The last content retrieved from a C, C or C test. =head2 auto_login my $bool = $test_server->auto_login If true (the default) automatically login using the correct credentials. Normally if you are testing file transfers you want to keep this to the default value, if you are testing the authentication of a server context then you want to set this to false. =head1 METHODS =head2 create_ftpserver_ok my $test_server = create_ftpserver_ok; my $test_server = create_ftpserver_ok($default_context); my $test_server = create_ftpserver_ok($default_context, $test_name); Create the FTP server with a random username and password for logging in. You can get the username/password from the C attribute, or connect to the server using L automatically with the C method below. =head2 connect_ftpclient_ok my $client = $test_server->connect_ftpclient_ok; my $client = $test_server->connect_ftpclient_ok($test_name); Connect to the FTP server, return the L object which can be used for testing. =head2 help_coverage_ok $test_server->help_coverage_ok; $test_server->help_coverage_ok($context_class); $test_server->help_coverage_ok($context_class, $test_name); Test that there is a C method for each C method in the given context class (the server's default context class is used if it isn't provided). This can also be used to test help coverage of context roles. =head2 command_ok $test_command->command_ok( $command, $arguments ); $test_command->command_ok( $command, $arguments, $test_name ); Execute the given command with the given arguments on the remote server. Fails only if a valid FTP response is not returned from the server (even error responses are okay). The response is stored in the C attribute. This method returns the test server object, so you can chain this command: $server->command_ok('HELP', 'HELP') # get help on the help command ->code_is(214) # returns status code 214 ->message_like(qr{HELP}); # the help command mentions the help command =head2 code_is $test_server->code_is($code); $test_server->code_is($code, $test_name); Verifies that the status code of the last command executed matches the given code exactly. =head2 code_like $test_server->code_like($regex); $test_server->code_like($regex, $test_name); Verifies that the status code of the last command executed matches the given regular expression.. =head2 message_like $test_server->message_like($regex); $test_server->message_like($regex, $test_name); Verifies that the message portion of the response of the last command executed matches the given regular expression. =head2 message_is $test_server->message_is($string); $test_server->message_is($string, $test_name); Verifies that the message portion of the response of the last command executed matches the given string. If the response message has multiple lines, then only one of the lines needs to match the given string. =head2 list_ok $test_server->list_ok; $test_server->list_ok($location); $test_server->list_ok($location, $test_name) Execute a the C command on the given C<$location> and wait for the results. You can see the result using the C attribute or test it with the C method. =head2 nlst_ok $test_server->nlst_ok; $test_server->nlst_ok( $location ); $test_server->nlst_ok( $location, $test_name ); Execute a the C command on the given C<$location> and wait for the results. You can see the result using the C attribute or test it with the C method. =head2 content_is $test_server->content_is($string); $test_server->content_is($string, $test_name); Test that the given C<$string> matches the content returned by the last C or C method. =head2 global_timeout_ok global_timeout_ok; global_timeout_ok($timeout); global_timeout_ok($timeout, $test_name) Set a global timeout on the entire test script. If the timeout is exceeded the test will exit. Handy if you have test automation and your test automation doesn't handle hung tests. The default timeout is 120 seconds. =head1 AUTHOR Author: Graham Ollis Eplicease@cpan.orgE Contributors: Ryo Okamoto Shlomi Fish José Joaquín Atria =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2017-2022 by Graham Ollis. 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 AnyEvent-FTP-0.20/maint/000755 000000 000000 00000000000 15123245460 014703 5ustar00rootroot000000 000000 AnyEvent-FTP-0.20/maint/gen.pl000644 000000 000000 00000001342 15123245460 016011 0ustar00rootroot000000 000000 use strict; use warnings; my @list = sort map { chomp; s/\.pm$//; s/^lib\///; s/\//::/g; $_ } `find lib -name \*.pm`; open my $fh, '>', 't/01_use.t'; print $fh <<'EOM'; use Test2::V0 -no_srand => 1; sub require_ok ($); EOM foreach my $module (@list) { print $fh "require_ok '$module';\n"; } print $fh <<'EOM'; done_testing; sub require_ok ($) { # special case of when I really do want require_ok. # I just want a test that checks that the modules # will compile okay. I won't be trying to use them. my($mod) = @_; my $ctx = context(); eval qq{ require $mod }; my $error = $@; my $ok = !$error; $ctx->ok($ok, "require $mod"); $ctx->diag("error: $error") if $error ne ''; $ctx->release; } EOM close $fh; AnyEvent-FTP-0.20/maint/travis-install-file-sharedir-dist000755 000000 000000 00000000531 15123245460 023261 0ustar00rootroot000000 000000 #!/bin/bash -x set -euo pipefail IFS=$'\n\t' rm -rf /tmp/File-ShareDir-dist cpanm -n Dist::Zilla git clone --depth 2 https://github.com/plicease/File-ShareDir-Dist.git /tmp/File-ShareDir-dist cd /tmp/File-ShareDir-dist dzil authordeps --missing | cpanm -n dzil listdeps --missing | cpanm -n dzil install --install-command 'cpanm -n -v .' AnyEvent-FTP-0.20/perlcriticrc000644 000000 000000 00000003671 15123245460 016212 0ustar00rootroot000000 000000 severity = 1 only = 1 [Community::ArrayAssignAref] [Community::BarewordFilehandles] [Community::ConditionalDeclarations] [Community::ConditionalImplicitReturn] [Community::DeprecatedFeatures] ;[Community::DiscouragedModules] [Community::DollarAB] [Community::Each] [Community::IndirectObjectNotation] [Community::LexicalForeachIterator] [Community::LoopOnHash] [Community::ModPerl] [Community::OpenArgs] [Community::OverloadOptions] [Community::POSIXImports] [Community::PackageMatchesFilename] [Community::PreferredAlternatives] [Community::StrictWarnings] extra_importers = Test2::V0 [Community::Threads] [Community::Wantarray] [Community::WarningsSwitch] [Community::WhileDiamondDefaultAssignment] [BuiltinFunctions::ProhibitBooleanGrep] ;[BuiltinFunctions::ProhibitStringyEval] [BuiltinFunctions::ProhibitStringySplit] [BuiltinFunctions::ProhibitVoidGrep] [BuiltinFunctions::ProhibitVoidMap] [ClassHierarchies::ProhibitExplicitISA] [ClassHierarchies::ProhibitOneArgBless] [CodeLayout::ProhibitHardTabs] allow_leading_tabs = 0 [CodeLayout::ProhibitTrailingWhitespace] [CodeLayout::RequireConsistentNewlines] [ControlStructures::ProhibitLabelsWithSpecialBlockNames] [ControlStructures::ProhibitMutatingListFunctions] [ControlStructures::ProhibitUnreachableCode] [InputOutput::ProhibitBarewordFileHandles] [InputOutput::ProhibitJoinedReadline] [InputOutput::ProhibitTwoArgOpen] [Miscellanea::ProhibitFormats] [Miscellanea::ProhibitUselessNoCritic] [Modules::ProhibitConditionalUseStatements] ;[Modules::RequireEndWithOne] [Modules::RequireNoMatchVarsWithUseEnglish] [Objects::ProhibitIndirectSyntax] [RegularExpressions::ProhibitUselessTopic] [Subroutines::ProhibitNestedSubs] [ValuesAndExpressions::ProhibitLeadingZeros] [ValuesAndExpressions::ProhibitMixedBooleanOperators] [ValuesAndExpressions::ProhibitSpecialLiteralHeredocTerminator] [ValuesAndExpressions::RequireUpperCaseHeredocTerminator] [Variables::ProhibitPerl4PackageNames] [Variables::ProhibitUnusedVariables] AnyEvent-FTP-0.20/share/000755 000000 000000 00000000000 15123245460 014675 5ustar00rootroot000000 000000 AnyEvent-FTP-0.20/share/ppt/000755 000000 000000 00000000000 15123245460 015500 5ustar00rootroot000000 000000 AnyEvent-FTP-0.20/share/ppt/ls.pl000644 000000 000000 00000036006 15123245460 016460 0ustar00rootroot000000 000000 =begin metadata Name: ls Description: list file/directory information Author: Mark Leighton Fisher, fisherm@tce.com License: perl =end metadata =cut # Perl Power Tool - ls(1) use File::stat; use Getopt::Std; use File::Spec; # ------ partial inline of Stat::lsMode v0.50 code # (see http://www.plover.com/~mjd/perl/lsMode/ # for the complete module) # # # Stat::lsMode # # Copyright 1998 M-J. Dominus # (mjd-perl-lsmode@plover.com) # # You may distribute this module under the same terms as Perl itself. my @perms = qw(--- --x -w- -wx r-- r-x rw- rwx); my @ftype = qw(. p c ? d ? b ? - ? l ? s ? ? ?); $ftype[0] = ''; sub format_mode { my $mode = shift; my %opts = @_; my $setids = ($mode & 07000)>>9; my @permstrs = @perms[($mode&0700)>>6, ($mode&0070)>>3, $mode&0007]; my $ftype = $ftype[($mode & 0170000)>>12]; if ($setids) { if ($setids & 01) { # Sticky bit $permstrs[2] =~ s/([-x])$/$1 eq 'x' ? 't' : 'T'/e; } if ($setids & 04) { # Setuid bit $permstrs[0] =~ s/([-x])$/$1 eq 'x' ? 's' : 'S'/e; } if ($setids & 02) { # Setgid bit $permstrs[1] =~ s/([-x])$/$1 eq 'x' ? 's' : 'S'/e; } } join '', $ftype, @permstrs; } # ------ define variables my $Arg = ""; # file/directory name argument from @ARGV my $ArgCount = 0; # file/directory argument count my $Attributes = ""; # File::stat from STDOUT (isatty() kludge) my %Attributes = (); # File::stat directory entry attributes my %DirEntries = (); # hash of dir entries and stat attributes my $Getgrgid = ""; # getgrgid() for this platform my $Getpwuid = ""; # getpwuid() for this platform my @Dirs = (); # directories in ARGV my @Files = (); # non-directories in ARGV my $First = 1; # first directory entry on command line my $Maxlen = 1; # longest string we've seen my $Now = time; # time we were invoked my %Options = (); # option/flag arguments my $SixMonths = # long listing time if < 6 months, else year 60*60*24*(365/2); my $VERSION = '0.70'; # because we're V7-compatible :) my $WinSize = "\0" x 8; # window size buffer my $TIOCGWINSZ = # get window size via ioctl() 0x40087468; # should be require sys/ioctl.pl, # but that won't exist on all platforms my $WinCols = 0; # window columns of output my $WinRows = 0; # window rows of output my $Xpixel = 0; # window start X my $Ypixel = 0; # window start Y # ------ compensate for lack of getpwuid/getgrgid on some platforms eval { my $dummy = ""; $dummy = (getpwuid(0))[0] }; if ($@) { $Getpwuid = sub { return ($_[0], 0); }; $Getgrgid = sub { return ($_[0], 0); }; } else { $Getpwuid = sub { return getpwuid($_[0]); }; $Getgrgid = sub { return getgrgid($_[0]); }; } # ------ functions # ------ get directory entries sub DirEntries { my $Options = shift; # option arguments hashref local *DH; # directory handle my %Attributes = (); # entry/attributes hash my @Entries = (); # entries in original order my $Name = ""; # entry name if (!opendir(DH, $_[0]) || exists($Options{'d'})) { if (-e $_[0]) { closedir(DH) if (defined(DH)); push(@Entries, $_[0]); $Attributes{$_[0]} = stat($_[0]); push(@Entries, \%Attributes); return @Entries; } print "pls: can't access '$_[0]': $!\n"; return (); } while ($Name = readdir(DH)) { next if (!exists($Options->{'a'}) && $Name =~ m/^\./o); push(@Entries, $Name); $Attributes{$Name} = stat( File::Spec->catfile( $_[0], $Name ) ); } closedir(DH); # ------ return list with %Attributes ref at end push(@Entries, \%Attributes); return @Entries; } # ------ format directory entry sub EntryFormat { my $Options = shift; # ls option arguments my $Attributes = shift; # entry attributes hashref my $Entry = shift; # directory entry name my $Blocks = 0; # block size when otherwise unknown my $BlockSize = # block size in 512-byte units exists($Options->{'k'}) ? 2 : 1; my $DateStr = ""; # time/date string my $Gid = -1; # group ID number my $Mode = ""; # file mode my @Month = ( # file time month abbrev. "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ); my $Time = 0; # file time my $Uid = -1; # user ID number # ------ for localtime() my $sec = 0; my $min = 0; my $hour = 0; my $mday = 0; my $mon = 0; my $year = 0; my $wday = 0; my $yday = 0; my $isdst = 0; if (exists($Options->{'i'})) { if (defined($Attributes->{$Entry})) { # 1 2 3 4 5 6 7 #23456789*123456789*123456789*123456789*123456789*123456789*123456789* printf("%10d ", $Attributes->{$Entry}->ino); } else { print "_________ "; } } if (exists($Options->{'s'})) { if (defined($Attributes->{$Entry})) { $Blocks = $Attributes->{$Entry}->blocks; if ($Blocks eq '') { $Blocks = 0; } printf("%4d ", $Blocks / $BlockSize + (($Blocks % $BlockSize) > 0)); } else { print "____ "; } } if (!exists($Options->{'l'})) { print "$Entry\n"; } else { if (!defined($Attributes->{$Entry})) { print <{$Entry}->mode); print "$Mode "; #printf("%8o ", # $Attributes->{$Entry}->mode); printf("%3d ", $Attributes->{$Entry}->nlink); if (exists($Options->{'n'})) { printf("%-8d ", $Attributes->{$Entry}->uid); } else { $Uid = &$Getpwuid($Attributes->{$Entry}->uid); if (defined($Uid)) { printf("%-8s ", $Uid); } else { printf("%-8d ", $Attributes->{$Entry}->uid); } } if (exists($Options->{'n'})) { printf("%-8d ", $Attributes->{$Entry}->gid); } else { $Gid = &$Getgrgid($Attributes->{$Entry}->gid); if (defined($Gid)) { printf("%-8s ", $Gid); } else { printf("%-8d ", $Attributes->{$Entry}->gid); } } if ($Attributes->{$Entry}->mode & 0140000) { printf("%9d ", $Attributes->{$Entry}->size); } else { printf("%4x,%4x ", (($Attributes->{$Entry}->dev & 0xFFFF000) > 16), $Attributes->{$Entry}->dev & 0xFFFF); } $Time = $Attributes->{$Entry}->mtime; if (exists($Options->{'c'})) { $Time = $Attributes->{$Entry}->ctime; } if (exists($Options->{'u'})) { $Time = $Attributes->{$Entry}->atime; } ($sec,$min,$hour,$mday,$mon,$year, $wday,$yday,$isdst) = localtime($Time); print $Month[$mon]; if ($mday < 10) { print " $mday "; } else { print " $mday "; } if ($Now - $Time <= $SixMonths) { printf("%02d:%02d", $hour, $min); } else { printf(" %04d", $year + 1900); } } print " $Entry\n"; } } # ------ list directory entries, breadth-first sub List { my $Name = shift; # directory name my $Options = shift; # options/flags hashref my $Expand = shift; # do 1 level of dir expansion, # for "ls DIRNAME" my $Attributes = ""; # entry attributes hashref my $BlockSize = # block size in 512-byte units exists($Options->{'k'}) ? 2 : 1; my $Cols = 0; # output columns for this List() my $Entry = ""; # directory entry my @Dirs = (); # directories from -R and DirEntries my $Mask = ""; # sprintf() format/mask my $Mylen = 0; # current entry length my $Path = ""; # path for subdirectories my $Piece = ""; # piece of entry list my @SortedEntries = (); # sorted entry list my $Rows = 0; # output rows for this List() my $Target = 0; # target element index my $TotalBlocks = 0; # total directory size in blocks my $elt = 0; # element index # ------ get directory entries attributes $Attributes = pop(@_); # ------ precompute max entry length and total size foreach (@_) { $TotalBlocks += (!defined($Attributes->{$_}) || ($Attributes->{$_}->blocks eq '')) ? 0: $Attributes->{$_}->blocks; $Mylen = length($_); if ($Mylen > $Maxlen) { $Maxlen = $Mylen; } } $Maxlen += 1; # account for spaces # ------ print directory name if -R if (exists($Options->{'R'})) { print "$Name:\n"; } # ----- print total in blocks if -s or -l if (exists($Options->{'l'}) || exists($Options->{'s'})) { print "total $TotalBlocks\n"; } # ------ sort entry list @SortedEntries = Order(\%Options, $Attributes, @_); # ------ user requested 1 entry/line, long, size, or inode if (defined($Options->{'1'}) || exists($Options->{'l'}) || exists($Options->{'s'}) || exists($Options->{'i'})) { foreach $Entry (@SortedEntries) { EntryFormat(\%Options, $Attributes, $Entry); } # ------ multi-column output } else { # ------ compute rows, columns, width mask $Cols = int($WinCols / $Maxlen) || 1; $Rows = int(($#_+$Cols) / $Cols); $Mask = sprintf("%%-%ds ", $Maxlen); for ($elt = 0; $elt < $Rows * $Cols; $elt++) { $Target = ($elt % $Cols) * $Rows + int(($elt / $Cols)); $Piece = sprintf($Mask, $Target < ($#SortedEntries + 1) ? $SortedEntries[$Target] : ""); # don't blank pad to eol of line $Piece =~ s/\s+$// if (($elt+1) % $Cols == 0); print $Piece; print "\n" if (($elt+1) % $Cols == 0); } print "\n" if (($elt+1) % $Cols == 0); } # ------ print blank line if -R if (exists($Options->{'R'})) { print "\n"; } # ------ list subdirectories of this directory if (!exists($Options{'d'}) && ($Expand || exists($Options->{'R'}))) { foreach $Entry (Order(\%Options, $Attributes, @_)) { next if ($Entry eq "." || $Entry eq ".."); if (defined($Attributes->{$Entry}) && $Attributes->{$Entry}->mode & 0040000) { $Path = File::Spec->canonpath(File::Spec->catdir($Name,$Entry)); @Dirs = DirEntries(\%Options, $Path); List($Path, \%Options, 0, @Dirs); } } } } # ------ sort file list based on %Options sub Order { my $Options = shift; # parsed option/flag arguments my $Attributes = shift; # File::stat attributes hashref my @Entries = @_; # directory entry names # ------ sort by size, largest first if (exists($Options->{'S'})) { if (exists($Options->{'r'})) { @Entries = sort { $Attributes->{$a}->size <=> $Attributes->{$b}->size } @Entries; } else { @Entries = sort { $Attributes->{$b}->size <=> $Attributes->{$a}->size } @Entries; } # ------ sort by time, most recent first } elsif (exists($Options->{'t'}) || exists($Options->{'c'}) || exists($Options->{'u'})) { if (exists($Options->{'r'})) { if (exists($Options->{'u'})) { @Entries = sort { $Attributes->{$a}->atime <=> $Attributes->{$b}->atime } @Entries; } elsif (exists($Options->{'c'})) { @Entries = sort { $Attributes->{$a}->ctime <=> $Attributes->{$b}->ctime } @Entries; } else { @Entries = sort { $Attributes->{$a}->mtime <=> $Attributes->{$b}->mtime } @Entries; } } else { if (exists($Options->{'u'})) { @Entries = sort { $Attributes->{$b}->atime <=> $Attributes->{$a}->atime } @Entries; } elsif (exists($Options->{'c'})) { @Entries = sort { $Attributes->{$b}->ctime <=> $Attributes->{$a}->ctime } @Entries; } else { @Entries = sort { $Attributes->{$b}->mtime <=> $Attributes->{$a}->mtime } @Entries; } } # ------ sort by name } elsif (!exists($Options->{'f'})) { if (exists($Options->{'r'})) { @Entries = sort { $b cmp $a } @Entries; } else { @Entries = sort { $a cmp $b } @Entries; } } # ------ return list sorted by options (or unsorted if -f) return @Entries; } # ------ process arguments getopts('1ACFLRSTWacdfgiklmnopqrstux', \%Options); # ------ get (or guess) window size if (ioctl(STDOUT, $TIOCGWINSZ, $WinSize)) { ($WinRows, $WinCols, $Xpixel, $Ypixel) = unpack('S4', $WinSize); } else { $WinCols = 80; } $Attributes = stat(STDOUT); if ($Attributes->mode & 0140000) { $Options{'1'} = '1'; } # ------ current directory if no arguments if ($#ARGV < 0) { List('.', \%Options, 0, DirEntries(\%Options, ".")); # ------ named files/directories if arguments } else { $ArgCount = -1; foreach $Arg (@ARGV) { if (!exists($Options{'d'}) && -d $Arg) { $ArgCount++; push(@Dirs, $Arg); } else { $ArgCount += 2; push(@Files, $Arg); } } foreach $Arg (@Files) { $Attributes{$Arg} = stat($Arg); } foreach $Arg (Order(\%Options, \%Attributes, @Files)) { $First = 0; List($Arg, \%Options, 0, DirEntries(\%Options, $Arg)); } foreach $Arg (@Dirs) { $Attributes{$Arg} = stat($Arg); } foreach $Arg (Order(\%Options, \%Attributes, @Dirs)) { if (!exists($Options{'R'})) { print "\n" if (!$First); $First = 0; print "$Arg:\n" if ($ArgCount > 0); } List($Arg, \%Options, 0, DirEntries(\%Options, $Arg)); } } __END__ =pod =head1 NAME ls - list file/directory information =head1 SYNOPSIS ls [-1RSacdfiklnrstu] [file ...] =head1 DESCRIPTION This programs lists information about files and directories. If it is invoked without file/directory name arguments, it lists the contents of the current directory. Otherwise, B lists information about the files and information about the contents of the directories (but see B<-d>). Furthermore, without any option arguments B just lists the names of files and directories. All files are listed before all directories. The default sort order is ascending ASCII on filename. =head2 OPTIONS The BSD options '1ACFLRSTWacdfgiklmnopqrstux' are recognized, but only '1RSacdfiklnrstu' are implemented: =over 4 =item -1 List entries 1 per line (default if output is not a tty). =item -R Recursively list the contents of all directories, breadth-first. =item -S Sort descending by size. =item -a List all files (normally files starting with '.' are ignored). =item -c Sort by descending last modification time of inode. =item -d Do not list directory contents. =item -f Do not sort -- list in whatever order files/directories are returned by the directory read function. =item -i List file inode number. (Doesn't mean much on non-inode systems.) =item -k When used with B<-s>, list file/directory size in 1024-byte blocks. =item -l Long format listing of mode -- # of links, owner name, group name, size in bytes, time of last modification, and name. =item -n List numeric uid and gid (default on platforms without getpwuid()). =item -r Reverse sorting order. =item -s List file/directory size in 512-byte blocks. (May not mean much on non-Unix systems.) =item -t Sort by descending last modification time. =item -u Sort by descending last access time. =back =head1 ENVIRONMENT =head1 BUGS The file metadata from stat() is used, which may not necessarily mean much on non-Unix systems. Specifically, the uid, gid, inode, and block numbers may be meaningless (or less than meaningful at least). The B<-l> option does not yet list the major and minor device numbers for special files, but it does list the value of the 'dev' field as 2 hex 16-bit words. Doing this properly would probably require filesystem type probing. =head1 AUTHOR This Perl implementation of I was written by Mark Leighton Fisher of Thomson Consumer Electronics, I. =head1 COPYRIGHT and LICENSE This program is free and open software. You may use, modify, distribute, and sell this program (and any modified variants) in any way you wish, provided you do not restrict others from doing the same. =cut AnyEvent-FTP-0.20/t/000755 000000 000000 00000000000 15123245460 014036 5ustar00rootroot000000 000000 AnyEvent-FTP-0.20/t/00_diag.t000644 000000 000000 00000003205 15123245460 015426 0ustar00rootroot000000 000000 use Test2::V0 -no_srand => 1; use Config; eval { require 'Test/More.pm' }; # This .t file is generated. # make changes instead to dist.ini my %modules; my $post_diag; $modules{$_} = $_ for qw( AnyEvent Capture::Tiny ExtUtils::MakeMaker File::ShareDir::Dist File::ShareDir::Install File::Which File::chdir Moo Path::Class PerlIO::eol Test2::API Test2::V0 URI autodie ); $post_diag = sub { use AnyEvent::FTP::Server::Context::FSRW; diag "ls[] = ", $_ for AnyEvent::FTP::Server::Context::FSRW::_shared_cmd('ls'); BEGIN { eval 'use EV' } diag 'AnyEvent::detect() = ', AnyEvent::detect(); }; my @modules = sort keys %modules; sub spacer () { diag ''; diag ''; diag ''; } pass 'okay'; my $max = 1; $max = $_ > $max ? $_ : $max for map { length $_ } @modules; our $format = "%-${max}s %s"; spacer; my @keys = sort grep /(MOJO|PERL|\A(LC|HARNESS)_|\A(SHELL|LANG)\Z)/i, keys %ENV; if(@keys > 0) { diag "$_=$ENV{$_}" for @keys; if($ENV{PERL5LIB}) { spacer; diag "PERL5LIB path"; diag $_ for split $Config{path_sep}, $ENV{PERL5LIB}; } elsif($ENV{PERLLIB}) { spacer; diag "PERLLIB path"; diag $_ for split $Config{path_sep}, $ENV{PERLLIB}; } spacer; } diag sprintf $format, 'perl', "$] $^O $Config{archname}"; foreach my $module (sort @modules) { my $pm = "$module.pm"; $pm =~ s{::}{/}g; if(eval { require $pm; 1 }) { my $ver = eval { $module->VERSION }; $ver = 'undef' unless defined $ver; diag sprintf $format, $module, $ver; } else { diag sprintf $format, $module, '-'; } } if($post_diag) { spacer; $post_diag->(); } spacer; done_testing; AnyEvent-FTP-0.20/t/01_use.t000644 000000 000000 00000004237 15123245460 015325 0ustar00rootroot000000 000000 use Test2::V0 -no_srand => 1; sub require_ok ($); require_ok 'AnyEvent::FTP'; require_ok 'AnyEvent::FTP::Client'; require_ok 'AnyEvent::FTP::Client::Response'; require_ok 'AnyEvent::FTP::Client::Role::FetchTransfer'; require_ok 'AnyEvent::FTP::Client::Role::ListTransfer'; require_ok 'AnyEvent::FTP::Client::Role::RequestBuffer'; require_ok 'AnyEvent::FTP::Client::Role::ResponseBuffer'; require_ok 'AnyEvent::FTP::Client::Role::StoreTransfer'; require_ok 'AnyEvent::FTP::Client::Site'; require_ok 'AnyEvent::FTP::Client::Site::Base'; require_ok 'AnyEvent::FTP::Client::Site::Microsoft'; require_ok 'AnyEvent::FTP::Client::Site::NetFtpServer'; require_ok 'AnyEvent::FTP::Client::Site::Proftpd'; require_ok 'AnyEvent::FTP::Client::Transfer'; require_ok 'AnyEvent::FTP::Client::Transfer::Active'; require_ok 'AnyEvent::FTP::Client::Transfer::Passive'; require_ok 'AnyEvent::FTP::Request'; require_ok 'AnyEvent::FTP::Response'; require_ok 'AnyEvent::FTP::Role::Event'; require_ok 'AnyEvent::FTP::Server'; require_ok 'AnyEvent::FTP::Server::Connection'; require_ok 'AnyEvent::FTP::Server::Context'; require_ok 'AnyEvent::FTP::Server::Context::FS'; require_ok 'AnyEvent::FTP::Server::Context::FSRO'; require_ok 'AnyEvent::FTP::Server::Context::FSRW'; require_ok 'AnyEvent::FTP::Server::Context::Memory'; require_ok 'AnyEvent::FTP::Server::OS::UNIX'; require_ok 'AnyEvent::FTP::Server::Role::Auth'; require_ok 'AnyEvent::FTP::Server::Role::Context'; require_ok 'AnyEvent::FTP::Server::Role::Help'; require_ok 'AnyEvent::FTP::Server::Role::Old'; require_ok 'AnyEvent::FTP::Server::Role::ResponseEncoder'; require_ok 'AnyEvent::FTP::Server::Role::TransferPrep'; require_ok 'AnyEvent::FTP::Server::Role::Type'; require_ok 'AnyEvent::FTP::Server::UnambiguousResponseEncoder'; require_ok 'Test::AnyEventFTPServer'; done_testing; sub require_ok ($) { # special case of when I really do want require_ok. # I just want a test that checks that the modules # will compile okay. I won't be trying to use them. my($mod) = @_; my $ctx = context(); eval qq{ require $mod }; my $error = $@; my $ok = !$error; $ctx->ok($ok, "require $mod"); $ctx->diag("error: $error") if $error ne ''; $ctx->release; } AnyEvent-FTP-0.20/t/anyevent_ftp.t000644 000000 000000 00000000107 15123245460 016723 0ustar00rootroot000000 000000 use Test2::V0 -no_srand => 1; use AnyEvent::FTP; ok 1; done_testing; AnyEvent-FTP-0.20/t/anyevent_ftp_client.t000644 000000 000000 00000107077 15123245460 020277 0ustar00rootroot000000 000000 use 5.010; use lib 't/lib'; use Test2::V0 -no_srand => 1; use Test2::Tools::ClientTests; use AnyEvent::FTP::Client; use File::Temp qw( tempdir ); use File::chdir; subtest 'syst' => sub { reset_timeout; my $client = eval { AnyEvent::FTP::Client->new }; diag $@ if $@; isa_ok $client, 'AnyEvent::FTP::Client'; prep_client( $client ); our $config; $client->connect($config->{host}, $config->{port})->recv; $client->login($config->{user}, $config->{pass})->recv; my $res = eval { $client->syst->recv }; diag $@ if $@; isa_ok $res, 'AnyEvent::FTP::Response'; is eval { $res->code }, 215, 'code = 215'; diag $@ if $@; $client->quit->recv; }; subtest 'retr' => sub { reset_timeout; skip_all 'requires client and server on localhost' if $ENV{AEF_REMOTE}; our $config; $config->{dir} = tempdir( CLEANUP => 1 ); my $fn = File::Spec->catfile($config->{dir}, 'foo.txt'); do { open my $fh, '>', $fn; say $fh "line 1"; say $fh "line 2"; close $fh; }; foreach my $passive (0,1) { my $client = AnyEvent::FTP::Client->new( passive => $passive ); prep_client( $client ); $client->connect($config->{host}, $config->{port})->recv; $client->login($config->{user}, $config->{pass})->recv; $client->type('I')->recv; $client->cwd($config->{dir})->recv; do { my $dest_fn = File::Spec->catdir(tempdir( CLEANUP => 1 ), 'foo.txt'); my $ret = eval { $client->retr('foo.txt', $dest_fn)->recv; }; diag $@ if $@; isa_ok $ret, 'AnyEvent::FTP::Response'; my @data = split /\015?\012/, do { open my $fh, '<', $dest_fn; local $/; <$fh>; }; is $data[0], 'line 1'; is $data[1], 'line 2'; }; do { my $data = ''; my $xfer = eval { $client->retr('foo.txt') }; isa_ok $xfer, 'AnyEvent::FTP::Client::Transfer'; $xfer->on_open(sub { my $handle = shift; $handle->on_read(sub { $handle->push_read(sub { $data .= $_[0]{rbuf}; $_[0]{rbuf} = ''; }); }); }); my $ret = eval { $xfer->recv }; isa_ok $ret, 'AnyEvent::FTP::Response'; my @data = split /\015?\012/, $data; is $data[0], 'line 1'; is $data[1], 'line 2'; }; do { my $data = ''; my $ret = eval { $client->retr('foo.txt', sub { $data .= shift })->recv; }; diag $@ if $@; isa_ok $ret, 'AnyEvent::FTP::Response'; my @data = split /\015?\012/, $data; is $data[0], 'line 1'; is $data[1], 'line 2'; }; do { my $data = ''; my $ret = eval { $client->retr('foo.txt', \$data)->recv; }; diag $@ if $@; isa_ok $ret, 'AnyEvent::FTP::Response'; my @data = split /\015?\012/, $data; is $data[0], 'line 1'; is $data[1], 'line 2'; }; do { my $data = ''; open my $fh, '>', \$data; my $ret = eval { $client->retr('foo.txt', $fh)->recv; }; diag $@ if $@; close $fh; isa_ok $ret, 'AnyEvent::FTP::Response'; my @data = split /\015?\012/, $data; is $data[0], 'line 1'; is $data[1], 'line 2'; }; $client->quit->recv; } }; subtest 'help' => sub { reset_timeout; my $client = AnyEvent::FTP::Client->new; prep_client( $client ); our $config; $client->connect($config->{host}, $config->{port})->recv; $client->login($config->{user}, $config->{pass})->recv; do { my $res = eval { $client->help->recv }; diag $@ if $@; isa_ok $res, 'AnyEvent::FTP::Response'; my $code = eval { $res->code }; diag $@ if $@; like $code, qr{^21[14]$}, 'code = ' . $code; }; do { my $res = eval { $client->help('help')->recv }; diag $@ if $@; isa_ok $res, 'AnyEvent::FTP::Response'; my $code = eval { $res->code }; diag $@ if $@; like $code, qr{^21[14]$}, 'code = ' . $code; }; SKIP: { our $detect; skip 'pure-FTPd does not return 502 on bogus help', 2 if $detect->{pu}; skip 'vsftp does not return 502 on bogus help', 2 if $detect->{vs}; skip 'Net::FTPServer does not return 502 on bogus help', 2 if $detect->{pl}; skip 'ncftpd does not return 502 on bogus help', 2 if $detect->{nc}; skip 'bftp does not respond to help bogus', 2 if $detect->{xb}; eval { $client->help('bogus')->recv }; my $res = $@; isa_ok $res, 'AnyEvent::FTP::Response'; my $code = eval { $res->code }; diag $@ if $@; like $code, qr{^50[12]$}, 'code = ' . $code; }; $client->quit->recv; }; subtest 'type' => sub { reset_timeout; my $client = eval { AnyEvent::FTP::Client->new }; diag $@ if $@; isa_ok $client, 'AnyEvent::FTP::Client'; our $config; prep_client($client); $client->connect($config->{host}, $config->{port})->recv; $client->login($config->{user}, $config->{pass})->recv; do { my $res = eval { $client->type('I')->recv }; diag $@ if $@; isa_ok $res, 'AnyEvent::FTP::Response'; is eval { $res->code }, 200, 'code = 200'; diag $@ if $@; }; do { my $res = eval { $client->type('A')->recv }; diag $@ if $@; isa_ok $res, 'AnyEvent::FTP::Response'; is eval { $res->code }, 200, 'code = 200'; diag $@ if $@; }; do { eval { $client->type('X')->recv }; my $error = $@; isa_ok $error, 'AnyEvent::FTP::Response'; like eval { $error->code }, qr{^50[104]$}, 'code = ' . eval { $error->code }; diag $@ if $@; }; $client->quit->recv; }; subtest 'allo' => sub { reset_timeout; my $client = AnyEvent::FTP::Client->new; prep_client( $client ); our $config; $client->connect($config->{host}, $config->{port})->recv; $client->login($config->{user}, $config->{pass})->recv; our $detect; skip_all 'wu-ftpd does not support ALLO' if $detect->{wu}; skip_all 'proftpd does not support ALLO' if $detect->{pr}; my $res = eval { $client->allo('foo')->recv }; diag $@ if $@; isa_ok $res, 'AnyEvent::FTP::Response'; like eval { $res->code }, qr{^20[02]$}, 'code = ' . eval { $res->code }; diag $@ if $@; SKIP: { skip 'pure-ftpd does not support ALLO without argument', 2 if $detect->{pu}; skip 'IIS does not support ALLO without argument', 2 if $detect->{ms}; my $res = eval { $client->allo->recv }; diag $@ if $@; isa_ok $res, 'AnyEvent::FTP::Response'; like eval { $res->code }, qr{^20[02]$}, 'code = ' . eval { $res->code }; diag $@ if $@; } $client->quit->recv; }; subtest 'mkd' => sub { reset_timeout; skip_all 'requires client and server on localhost' if $ENV{AEF_REMOTE}; our $config; $config->{dir} = tempdir( CLEANUP => 1 ); my $client = AnyEvent::FTP::Client->new; prep_client( $client ); $client->connect($config->{host}, $config->{port})->recv; $client->login($config->{user}, $config->{pass})->recv; $client->type('I')->recv; $client->cwd($config->{dir})->recv; do { my $ret = eval { $client->mkd('foo')->recv; }; diag $@ if $@; isa_ok $ret, 'AnyEvent::FTP::Response'; my $dir_name = File::Spec->catdir($config->{dir}, 'foo'); ok -d $dir_name, "dir created: $dir_name"; rmdir $dir_name; ok !-d $dir_name, "dir deleted"; }; $client->quit->recv; }; subtest 'nlst' => sub { reset_timeout; skip_all 'requires client and server on localhost' if $ENV{AEF_REMOTE}; our $config; $config->{dir} = tempdir( CLEANUP => 1 ); foreach my $name (qw( foo bar baz )) { my $fn = File::Spec->catfile($config->{dir}, "$name.txt"); open my $fh, '>', $fn; close $fh; } my $dir2 = File::Spec->catdir($config->{dir}, "dir2"); mkdir $dir2; foreach my $name (qw( dr.pepper coke pepsi )) { my $fn = File::Spec->catfile($config->{dir}, 'dir2', "$name.txt"); open my $fh, '>', $fn; close $fh; } foreach my $passive (0,1) { my $client = AnyEvent::FTP::Client->new( passive => $passive ); prep_client( $client ); $client->connect($config->{host}, $config->{port})->recv; $client->login($config->{user}, $config->{pass})->recv; $client->type('I')->recv; $client->cwd($config->{dir})->recv; do { my $list = eval { $client->nlst->recv }; diag $@ if $@; is $list, array { etc() }; $list //= []; @$list = grep !/^dir2$/, @$list; is [ sort @$list ], [ sort qw( foo.txt bar.txt baz.txt ) ], 'nlst 1'; #note 'actual: ' . join(' ', sort @$list); #note 'expected: ' . join(' ', sort qw( foo.txt bar.txt baz.txt )); }; do { my $list = eval { $client->nlst('dir2')->recv }; diag $@ if $@; is $list, array { etc() }; $list //= []; our $detect; # workaround here for Net::FTPServer and pure-ftpd, unlike other wu,vs and pro ftpd does not include the path name is [ sort @$list ], [ sort map { $detect->{pl} || $detect->{pu} || $detect->{xb} ? "$_.txt" : "dir2/$_.txt" } qw( dr.pepper coke pepsi ) ], 'nlst 1'; #note "list: $_" for @$list; }; $client->quit->recv; } }; subtest '00' => sub { reset_timeout; my $client = eval { AnyEvent::FTP::Client->new }; diag $@ if $@; isa_ok $client, 'AnyEvent::FTP::Client'; prep_client( $client ); our $config; $client->on_greeting(sub { my $res = shift; diag "$res"; }); $client->connect($config->{host}, $config->{port})->recv; $client->quit->recv; }; subtest 'dele' => sub { reset_timeout; skip_all 'requires client and server on localhost' if $ENV{AEF_REMOTE}; our $config; $config->{dir} = tempdir( CLEANUP => 1 ); my $client = AnyEvent::FTP::Client->new; prep_client( $client ); $client->connect($config->{host}, $config->{port})->recv; $client->login($config->{user}, $config->{pass})->recv; $client->type('I')->recv; $client->cwd($config->{dir})->recv; do { my $fn = File::Spec->catfile($config->{dir}, 'foo.txt'); do { open my $fh, '>', $fn; close $fh; }; ok -e $fn, "created file"; my $ret = eval { $client->dele('foo.txt')->recv; }; diag $@ if $@; isa_ok $ret, 'AnyEvent::FTP::Response'; ok !-e $fn, "deleted file"; }; do { my $fn = File::Spec->catfile($config->{dir}, 'bar.txt'); ok !-e $fn, "created file"; eval { $client->dele('foo.txt')->recv; }; my $res = $@; isa_ok $res, 'AnyEvent::FTP::Response'; ok !-e $fn, "deleted file"; }; $client->quit->recv; }; subtest 'site_proftpd' => sub { reset_timeout; skip_all 'requires client and server on localhost' if $ENV{AEF_REMOTE}; our $config; $config->{dir} = tempdir( CLEANUP => 1 ); my $client = AnyEvent::FTP::Client->new; prep_client( $client ); eval { $client->connect($config->{host}, $config->{port})->recv; $client->login($config->{user}, $config->{pass})->recv; $client->type('I')->recv; $client->cwd($config->{dir})->recv; our $detect; unless($detect->{pr}) { $client->quit->recv; die "not ProFTPd" unless $detect->{pr}; } }; skip_all 'requires Proftpd to test against' if $@; do { my $dir_name = File::Spec->catdir($config->{dir}, 'foo'); do { my $res = eval { $client->site->proftpd->mkdir('foo')->recv }; diag $@ if $@; isa_ok $res, 'AnyEvent::FTP::Response'; }; ok -d $dir_name, "dir foo created"; do { my $res = eval { $client->site->proftpd->rmdir('foo')->recv }; diag $@ if $@; isa_ok $res, 'AnyEvent::FTP::Response'; }; ok !-d $dir_name, "dir foo deleted"; }; do { do { open(my $fh, '>', File::Spec->catfile($config->{dir}, 'target')); close $fh; }; do { my $res = eval { $client->site->proftpd->symlink('target', 'link')->recv }; diag $@ if $@; isa_ok $res, 'AnyEvent::FTP::Response'; }; like readlink(File::Spec->catfile($config->{dir}, 'link')), qr{target$}, "link => target"; }; $client->quit->recv; }; subtest 'remote' => sub { reset_timeout; local $ENV{AEF_REMOTE} //= tempdir( CLEANUP => 1 ); our $config; our $detect; foreach my $passive (0,1) { my $client = AnyEvent::FTP::Client->new( passive => $passive ); prep_client( $client ); $client->connect($config->{host}, $config->{port})->recv; $client->login($config->{user}, $config->{pass})->recv; $client->type('I')->recv; isa_ok $client->cwd($ENV{AEF_REMOTE})->recv, 'AnyEvent::FTP::Response'; do { my $dir = $client->pwd->recv; is $dir, net_pwd($ENV{AEF_REMOTE}), "dir = " .net_pwd($ENV{AEF_REMOTE}); }; my $dirname = join '', map { chr(ord('a') + int(rand(23))) } (1..10); isa_ok $client->mkd($dirname)->recv, 'AnyEvent::FTP::Response'; isa_ok $client->cwd($dirname)->recv, 'AnyEvent::FTP::Response'; SKIP: { skip 'wu-ftpd throws an exception on empty directory', 2 if $detect->{wu}; my $res = $client->nlst->recv; is $res, array { etc() }; is scalar(@$res), 0, 'list empty'; if(scalar(@$res) > 0) { diag "~~~ nlst ~~~"; diag $_ for @$res; diag "~~~~~~~~~~~~"; } }; isa_ok $client->stor('foo.txt', \"here is some data eh\n")->recv, 'AnyEvent::FTP::Response'; do { my $res = $client->nlst->recv; is $res, array { etc() }; is scalar(@$res), 1, 'list not empty'; is $res->[0], 'foo.txt'; }; do { my $res = $client->list->recv; is $res, array { etc() }; is scalar(grep /foo.txt$/, @$res), 1, 'has foo.txt in listing'; }; do { my $data = ''; isa_ok $client->retr('foo.txt', \$data)->recv, 'AnyEvent::FTP::Response'; is $data, "here is some data eh\n", 'retr ok'; }; isa_ok $client->appe('foo.txt', \"line 2\n")->recv, 'AnyEvent::FTP::Response'; do { my $data = ''; isa_ok $client->retr('foo.txt', \$data)->recv, 'AnyEvent::FTP::Response'; is $data, "here is some data eh\nline 2\n", 'retr ok'; }; isa_ok $client->rename('foo.txt', 'bar.txt')->recv, 'AnyEvent::FTP::Response'; do { my $res = $client->nlst->recv; is $res, array { etc() }; is scalar(@$res), 1, 'list not empty'; is $res->[0], 'bar.txt'; }; do { my $res = $client->list->recv; is $res, array { etc() }; is scalar(grep /bar.txt$/, @$res), 1, 'has bar.txt in listing'; }; do { my $data = "here is some data"; isa_ok $client->retr('bar.txt', \$data, restart => do { use bytes; length $data})->recv, 'AnyEvent::FTP::Response'; is $data, "here is some data eh\nline 2\n", 'rest, retr ok'; }; isa_ok $client->dele('bar.txt')->recv, 'AnyEvent::FTP::Response'; # ... isa_ok $client->cdup->recv, 'AnyEvent::FTP::Response'; isa_ok $client->rmd($dirname)->recv, 'AnyEvent::FTP::Response'; isa_ok $client->quit->recv, 'AnyEvent::FTP::Response'; } }; subtest 'rmd' => sub { reset_timeout; skip_all 'requires client and server on localhost' if $ENV{AEF_REMOTE}; our $config; $config->{dir} = tempdir( CLEANUP => 1 ); my $client = AnyEvent::FTP::Client->new; prep_client( $client ); $client->connect($config->{host}, $config->{port})->recv; $client->login($config->{user}, $config->{pass})->recv; $client->type('I')->recv; $client->cwd($config->{dir})->recv; do { my $dir_name = File::Spec->catdir($config->{dir}, 'foo'); mkdir $dir_name; my $ret = eval { $client->rmd('foo')->recv; }; diag $@ if $@; isa_ok $ret, 'AnyEvent::FTP::Response'; ok !-d $dir_name, "dir removed: $dir_name"; rmdir $dir_name if -d $dir_name; }; $client->quit->recv; }; subtest 'stou' => sub { reset_timeout; skip_all 'requires client and server on localhost' if $ENV{AEF_REMOTE}; our $config; $config->{dir} = tempdir( CLEANUP => 1 ); my $plan = sub { state $first = 0; return unless ++$first == 1; our $detect; skip_all 'wu-ftpd does not support STOU' if $detect->{wu}; skip_all 'bftp does not support STOU' if $detect->{xb}; skip_all 'vsftpd does not support STOU without an argument' if $detect->{vs}; }; foreach my $passive (0,1) { my $client = AnyEvent::FTP::Client->new( passive => $passive ); prep_client( $client ); $client->connect($config->{host}, $config->{port})->recv; $client->login($config->{user}, $config->{pass})->recv; $client->type('I')->recv; $client->cwd($config->{dir})->recv; $plan->(); do { my $data = 'some data'; my $xfer = eval { $client->stou(undef, \$data) }; diag $@ if $@; isa_ok $xfer, 'AnyEvent::FTP::Client::Transfer'; my $ret = eval { $xfer->recv; }; diag $@ if $@; isa_ok $ret, 'AnyEvent::FTP::Response'; my @list = do { opendir my $dh, $config->{dir}; grep !/^\./, readdir $dh; }; is scalar(@list), 1, 'exactly one file'; my $fn = File::Spec->catfile($config->{dir}, $list[0]); is $xfer->remote_name, $list[0], "remote_name = $list[0]"; my $remote = do { open my $fh, '<', $fn; local $/; <$fh>; }; is $remote, $data, 'local/remote match'; unlink $fn; ok !-e $fn, 'remote deleted'; }; $client->quit->recv; } }; subtest 'stat' => sub { reset_timeout; my $client = AnyEvent::FTP::Client->new; prep_client( $client ); our $config; our $detect; $client->connect($config->{host}, $config->{port})->recv; $client->login($config->{user}, $config->{pass})->recv; skip_all 'ncftp return code broken' if $detect->{nc}; do { my $res = eval { $client->stat->recv }; diag $@ if $@; isa_ok $res, 'AnyEvent::FTP::Response'; my $code = eval { $res->code }; diag $@ if $@; like $code, qr{^21[123]$}, 'code = ' . $code; }; do { my $res = eval { $client->stat('/')->recv }; diag $@ if $@; isa_ok $res, 'AnyEvent::FTP::Response'; my $code = eval { $res->code }; diag $@ if $@; like $code, qr{^21[123]$}, 'code = ' . $code; }; SKIP: { skip 'wu-ftpd does not return [45]50 on bogus file', 2 if $detect->{wu}; skip 'pure-FTPd does not return [45]50 on bogus file', 2 if $detect->{pu}; skip 'vsftp does not return [45]50 on bogus file', 2 if $detect->{vs}; skip 'IIS does not return [45]50 on bogus file', 2 if $detect->{ms}; skip 'bftp does not return [45]50 on bogus file', 2 if $detect->{xb}; eval { $client->stat('bogus')->recv }; my $res = $@; isa_ok $res, 'AnyEvent::FTP::Response'; my $code = eval { $res->code }; diag $@ if $@; like $code, qr{^[45]50$}, 'code = ' . $code; }; $client->quit->recv; }; subtest 'rename' => sub { reset_timeout; skip_all 'requires client and server on localhost' if $ENV{AEF_REMOTE}; our $config; $config->{dir} = tempdir( CLEANUP => 1 ); my $client = AnyEvent::FTP::Client->new; prep_client( $client ); $client->connect($config->{host}, $config->{port})->recv; $client->login($config->{user}, $config->{pass})->recv; $client->type('I')->recv; $client->cwd($config->{dir})->recv; do { my $from = File::Spec->catfile($config->{dir}, 'foo.txt'); do { open my $fh, '>', $from; close $fh; }; my $to = File::Spec->catfile($config->{dir}, 'bar.txt'); ok -e $from, "EX: $from"; ok !-e $to, "NO: $to"; my $res1 = eval { $client->rnfr($from)->recv }; diag $@ if $@; isa_ok $res1, 'AnyEvent::FTP::Response'; my $res2 = eval { $client->rnto($to)->recv }; diag $@ if $@; isa_ok $res2, 'AnyEvent::FTP::Response'; ok !-e $from, "NO: $from"; ok -e $to, "EX: $to"; }; do { my $from = File::Spec->catfile($config->{dir}, 'pepper.txt'); do { open my $fh, '>', $from; close $fh; }; my $to = File::Spec->catfile($config->{dir}, 'coke.txt'); ok -e $from, "EX: $from"; ok !-e $to, "NO: $to"; my $res = eval { $client->rename($from, $to)->recv }; diag $@ if $@; isa_ok $res, 'AnyEvent::FTP::Response'; ok !-e $from, "NO: $from"; ok -e $to, "EX: $to"; }; $client->quit->recv; }; subtest 'list' => sub { reset_timeout; skip_all 'requires client and server on localhost' if $ENV{AEF_REMOTE}; our $config; $config->{dir} = tempdir( CLEANUP => 1 ); foreach my $name (qw( foo bar baz )) { my $fn = File::Spec->catfile($config->{dir}, "$name.txt"); open my $fh, '>', $fn; close $fh; } my $dir2 = File::Spec->catdir($config->{dir}, "dir2"); mkdir $dir2; foreach my $name (qw( dr.pepper coke pepsi )) { my $fn = File::Spec->catfile($config->{dir}, 'dir2', "$name.txt"); open my $fh, '>', $fn; close $fh; } foreach my $passive (0,1) { my $client = AnyEvent::FTP::Client->new( passive => $passive ); prep_client( $client ); $client->connect($config->{host}, $config->{port})->recv; $client->login($config->{user}, $config->{pass})->recv; $client->type('I')->recv; $client->cwd($config->{dir})->recv; subtest 'listing with directory' => sub { my $list = eval { $client->list->recv }; diag $@ if $@; is $list, array { etc() }; $list //= []; # wu-ftpd shift @$list if $list->[0] =~ / \d+$/i; # Net::FTPServer shift @$list if $list->[0] =~ /\s\.$/; shift @$list if $list->[0] =~ /\s\.\.$/; is scalar(@$list), 4, 'list length 4'; is scalar(grep /foo.txt$/, @$list), 1, 'has foo.txt'; is scalar(grep /bar.txt$/, @$list), 1, 'has bar.txt'; is scalar(grep /baz.txt$/, @$list), 1, 'has baz.txt'; is scalar(grep /dir2$/, @$list), 1, 'has dir2'; #note "list: $_" for @$list; }; subtest 'listing in sub directory' => sub { my $list = eval { $client->list('dir2')->recv }; diag $@ if $@; is $list, array { etc() }; $list //= []; # wu-ftpd shift @$list if $list->[0] =~ / \d+$/i; # Net::FTPServer shift @$list if $list->[0] =~ /\s\.$/; shift @$list if $list->[0] =~ /\s\.\.$/; is scalar(@$list), 3, 'list length 3'; is scalar(grep /dr.pepper.txt$/, @$list), 1, 'has dr.pepper.txt'; is scalar(grep /coke.txt$/, @$list), 1, 'has coke.txt'; is scalar(grep /pepsi.txt$/, @$list), 1, 'has pepsi.txt'; #note "list: $_" for @$list; }; $client->quit->recv; } }; subtest 'stor' => sub { reset_timeout; skip_all 'requires client and server on localhost' if $ENV{AEF_REMOTE}; our $config; $config->{dir} = tempdir( CLEANUP => 1 ); foreach my $passive (0,1) { subtest "passive = $passive" => sub { my $client = AnyEvent::FTP::Client->new( passive => $passive ); prep_client( $client ); $client->connect($config->{host}, $config->{port})->recv; $client->login($config->{user}, $config->{pass})->recv; $client->type('I')->recv; $client->cwd($config->{dir})->recv; my $fn = File::Spec->catfile($config->{dir}, 'foo.txt'); do { my $data = 'some data'; my $src_fn = do { my $fn = File::Spec->catfile(tempdir(CLEANUP => 1), 'foo.txt'); open my $fh, '>', $fn; print $fh $data; close $fh; $fn; }; my $ret = eval { $client->stor('foo.txt', $src_fn)->recv; }; diag $@ if $@; isa_ok $ret, 'AnyEvent::FTP::Response'; ok -e $fn, 'remote file created'; my $remote = do { open my $fh, '<', $fn; local $/; <$fh>; }; is $remote, $data, 'remote matches'; }; do { my $data = 'some data'; my $xfer = eval { $client->stor('foo.txt') }; isa_ok $xfer, 'AnyEvent::FTP::Client::Transfer'; my $called_open = 0; my $called_close = 0; $xfer->on_open(sub { $called_open = 1; my $handle = shift; $handle->on_drain(sub { $handle->push_write($data); $handle->on_drain(sub { $handle->push_shutdown; }); }); }); $xfer->on_close(sub { $called_close = 1; }); my $res = eval { $xfer->recv }; isa_ok $res, 'AnyEvent::FTP::Response'; ok -e $fn, 'remote file created'; my $remote = do { open my $fh, '<', $fn; local $/; <$fh>; }; is $remote, $data, 'remote matches'; is $called_open, 1, 'open emit'; is $called_close, 1, 'close emit'; }; unlink $fn; ok !-e $fn, 'remote file deleted'; do { my $data = 'some data'; my $xfer = eval { $client->stor('foo.txt', \$data) }; diag $@ if $@; isa_ok $xfer, 'AnyEvent::FTP::Client::Transfer'; my $ret = eval { $xfer->recv; }; diag $@ if $@; isa_ok $ret, 'AnyEvent::FTP::Response'; ok -e $fn, 'remote file created'; my $remote = do { open my $fh, '<', $fn; local $/; <$fh>; }; is $remote, $data, 'remote matches'; is $xfer->remote_name, 'foo.txt', 'remote_name = foo.txt'; }; unlink $fn; ok !-e $fn, 'remote file deleted'; do { my $data = 'some data'; my $cb = do { my $buffer = $data; sub { my $tmp = $buffer; undef $buffer; $tmp; }; }; my $ret = eval { $client->stor('foo.txt', $cb)->recv; }; diag $@ if $@; isa_ok $ret, 'AnyEvent::FTP::Response'; ok -e $fn, 'remote file created'; my $remote = do { open my $fh, '<', $fn; local $/; <$fh>; }; is $remote, $data, 'remote matches'; }; unlink $fn; ok !-e $fn, 'remote file deleted'; do { my $data = 'some data'; my $glob = do { my $dir = tempdir( CLEANUP => 1); my $fn = File::Spec->catfile($dir, 'flub.txt'); open my $out, '>', $fn; binmode $out; print $out $data; close $out; open my $in, '<', $fn; binmode $in; $in; }; my $ret = eval { $client->stor('foo.txt', $glob)->recv; }; diag $@ if $@; isa_ok $ret, 'AnyEvent::FTP::Response'; ok -e $fn, 'remote file created'; my $remote = do { open my $fh, '<', $fn; local $/; <$fh>; }; is $remote, $data, 'remote matches'; }; unlink $fn; ok !-e $fn, 'remote file deleted'; $client->quit->recv; }; } }; subtest 'login' => sub { reset_timeout; my $client = eval { AnyEvent::FTP::Client->new }; diag $@ if $@; isa_ok $client, 'AnyEvent::FTP::Client'; prep_client( $client ); our $config; $client->connect($config->{host}, $config->{port})->recv; my $res = eval { $client->login($config->{user}, $config->{pass})->recv }; diag $@ if $@; isa_ok $res, 'AnyEvent::FTP::Response'; is $res->code, 230, 'code = 230'; is eval { $client->quit->recv->code }, 221, 'code = 221'; diag $@ if $@; $client->connect($config->{host}, $config->{port})->recv; eval { $client->login('bogus', 'bogus')->recv }; my $error = $@; isa_ok $error, 'AnyEvent::FTP::Response'; is $error->code, 530, 'code = 530'; eval { $client->quit->recv }; }; subtest 'uri' => sub { reset_timeout; skip_all 'requires client and server on localhost' if $ENV{AEF_REMOTE}; skip_all 'requires URI' unless eval q{ require URI }; my $client = eval { AnyEvent::FTP::Client->new }; diag $@ if $@; isa_ok $client, 'AnyEvent::FTP::Client'; our $config; our $detect; local $config->{dir} = $CWD; prep_client( $client ); my $uri = URI->new('ftp:'); $uri->host($config->{host}); $uri->port($config->{port}); $uri->user($config->{user}); $uri->password($config->{pass}); $uri->path(do { my $dir = $config->{dir}; if($^O eq 'MSWin32') { (undef,$dir,undef) = File::Spec->splitpath($dir,1); $dir =~ s{\\}{/}g; } $dir; }); isa_ok $uri, 'URI'; subtest 'test with real URI object' => sub { my $res = eval { $client->connect($uri)->recv }; is $@, ''; if($@ ne '') { eval { $client->quit->recv }; return; } isa_ok $res, 'AnyEvent::FTP::Response'; is $res->code, 250, 'code = 250'; is $client->pwd->recv, net_pwd($config->{dir}), "dir = " . net_pwd($config->{dir}); $client->quit->recv; }; subtest 'test with string URI' => sub { my $res = eval { $client->connect($uri->as_string)->recv }; is $@, ''; if($@ ne '') { eval { $client->quit->recv }; return; } isa_ok $res, 'AnyEvent::FTP::Response'; is $res->code, 250, 'code = 250'; is $client->pwd->recv, net_pwd($config->{dir}), "dir = " . net_pwd($config->{dir}); $client->quit->recv; }; $uri->user('bogus'); $uri->password('bogus'); SKIP: { skip 'bftp quit broken', 2 if $detect->{xb}; eval { $client->connect($uri->as_string)->recv }; my $error = $@; isa_ok $error, 'AnyEvent::FTP::Response'; is $error->code, 530, 'code = 530'; $client->quit->recv; }; $uri->user($config->{user}); $uri->password($config->{pass}); $uri->path('/bogus/bogus/bogus'); SKIP: { skip 'bftp quit broken', 2 if $detect->{xb}; eval { $client->connect($uri->as_string)->recv }; my $error = $@; isa_ok $error, 'AnyEvent::FTP::Response'; is $error->code, 550, 'code = 550'; $client->quit->recv; }; }; subtest 'rest' => sub { reset_timeout; skip_all 'requires client and server on localhost' if $ENV{AEF_REMOTE}; our $config; $config->{dir} = tempdir( CLEANUP => 1 ); my $fn = File::Spec->catfile($config->{dir}, 'foo.txt'); do { open my $fh, '>', $fn; print $fh "012345678901234567890"; close $fh; }; foreach my $passive (0,1) { my $client = AnyEvent::FTP::Client->new( passive => $passive ); prep_client( $client ); $client->connect($config->{host}, $config->{port})->recv; $client->login($config->{user}, $config->{pass})->recv; $client->type('I')->recv; $client->cwd($config->{dir})->recv; do { my $data = '0123456789'; my $ret1 = eval { $client->rest(10)->recv; }; diag $@ if $@; isa_ok $ret1, 'AnyEvent::FTP::Response'; my $ret2 = eval { $client->retr('foo.txt', sub { $data .= shift }, restart => length $data)->recv; }; diag $@ if $@; isa_ok $ret2, 'AnyEvent::FTP::Response'; is $data, "012345678901234567890", 'data = "012345678901234567890"'; }; $client->quit->recv; } }; subtest 'connect' => sub { reset_timeout; my $done = AnyEvent->condvar; my $client = eval { AnyEvent::FTP::Client->new }; diag $@ if $@; isa_ok $client, 'AnyEvent::FTP::Client'; $client->on_close(sub { $done->send }); our $config; prep_client( $client ); do { my $condvar = eval { $client->connect($config->{host}, $config->{port}) }; diag $@ if $@; my $res = eval { $condvar->recv }; diag $@ if $@; isa_ok $res, 'AnyEvent::FTP::Response'; is $res->code, 220, 'code = 220'; }; is eval { $client->push_command([USER => $config->{user}])->recv->code }, 331, 'code = 331'; diag $@ if $@; is eval { $client->push_command([PASS => $config->{pass}])->recv->code }, 230, 'code = 230'; diag $@ if $@; my $help_cv = $client->push_command(['HELP']); is eval { $client->push_command(['QUIT']) ->recv->code }, 221, 'code = 221'; diag $@ if $@; $done->recv; $done = AnyEvent->condvar; SKIP: { our $detect; skip 'bftp quit broken', 5 if $detect->{xb}; is eval { $client->connect($config->{host}, $config->{port})->recv->code }, 220, 'code = 220'; diag $@ if $@; is eval { $client->push_command([USER => 'bogus'])->recv->code }, 331, 'code = 331'; diag $@ if $@; eval { $client->push_command([PASS => 'bogus'])->recv }; is $@->code, 530, 'code = 530'; is eval { $client->push_command(['QUIT']) ->recv->code }, 221, 'code = 221 (2)'; diag $@ if $@; is $help_cv->recv->code, 214, 'code = 214'; $done->recv; $done = AnyEvent->condvar; } my $cv1 = $client->push_command([USER => $config->{user}]); my $cv2 = $client->push_command([PASS => $config->{pass}]); my $cv3 = $client->push_command(['QUIT']); is eval { $client->connect($config->{host}, $config->{port})->recv->code }, 220, 'code = 220'; diag $@ if $@; is $cv1->recv->code, 331, 'code = 331'; is $cv2->recv->code, 230, 'code = 230'; is $cv3->recv->code, 221, 'code = 221'; $done->recv; }; subtest 'appe_2' => sub { reset_timeout; skip_all 'requires client and server on localhost' if $ENV{AEF_REMOTE}; our $config; my $remote = $config->{dir} = tempdir( CLEANUP => 1 ); my $local = tempdir( CLEANUP => 1 ); foreach my $passive (0,1) { my $client = AnyEvent::FTP::Client->new( passive => $passive ); prep_client( $client ); $client->connect($config->{host}, $config->{port})->recv; $client->login($config->{user}, $config->{pass})->recv; $client->type('I')->recv; $client->cwd($config->{dir})->recv; do { open my $fh, '>', "$local/data.$passive"; binmode $fh; print $fh "data$_\n" for 1..200; close $fh; }; $client->stor("data.$passive", "$local/data.$passive")->recv; my $size = -s "$local/data.$passive"; is $size && -s "$remote/data.$passive", $size, "size of remote file is $size"; $size = $client->size("data.$passive")->recv; is $size, -s "$local/data.$passive", "size returned from remote file is correct"; my $expected = do { open my $fh, '>>', "$local/data.$passive"; binmode $fh; print $fh "xorxor$_\n" for 1..300; close $fh; open $fh, '<', "$local/data.$passive"; binmode $fh; local $/; my $data = <$fh>; close $fh; $data; }; do { open my $fh, '<', "$local/data.$passive"; binmode $fh; seek $fh, $client->size("data.$passive")->recv, 0; $client->appe("data.$passive", $fh)->recv; close $fh; }; $size = -s "$local/data.$passive"; is $size && -s "$remote/data.$passive", $size, "size of remote file is $size"; $size = $client->size("data.$passive")->recv; is $size, -s "$local/data.$passive", "size returned from remote file is correct"; my $actual = do { open my $fh, '<', "$remote/data.$passive"; binmode $fh; local $/; my $data = <$fh>; close $fh; $data; }; is $actual, $expected, "files match"; $client->quit->recv; } }; subtest 'noop' => sub { reset_timeout; my $client = eval { AnyEvent::FTP::Client->new }; diag $@ if $@; isa_ok $client, 'AnyEvent::FTP::Client'; prep_client( $client ); our $config; $client->connect($config->{host}, $config->{port})->recv; $client->login($config->{user}, $config->{pass})->recv; my $res = eval { $client->noop->recv }; diag $@ if $@; isa_ok $res, 'AnyEvent::FTP::Response'; is eval { $res->code }, 200, 'code = 200'; diag $@ if $@; $client->quit->recv; }; subtest 'appe' => sub { reset_timeout; skip_all 'requires client and server on localhost' if $ENV{AEF_REMOTE}; our $config; $config->{dir} = tempdir( CLEANUP => 1 ); foreach my $passive (0,1) { my $client = AnyEvent::FTP::Client->new( passive => $passive ); prep_client( $client ); $client->connect($config->{host}, $config->{port})->recv; $client->login($config->{user}, $config->{pass})->recv; $client->type('I')->recv; $client->cwd(translate_dir($config->{dir}))->recv; my $fn = File::Spec->catfile($config->{dir}, 'foo.txt'); do { open my $fh, '>', $fn; say $fh "line1"; close $fh; }; do { my $data = 'line2'; my $ret = eval { $client->appe('foo.txt', \$data)->recv; }; diag $@ if $@; isa_ok $ret, 'AnyEvent::FTP::Response'; ok -e $fn, 'remote file exists'; my @remote = split /\015?\012/, do { open my $fh, '<', $fn; local $/; <$fh>; }; is scalar(@remote), 2, 'two lines'; is $remote[0], 'line1', 'line 1 = line1'; is $remote[1], 'line2', 'line 2 = line2'; }; unlink $fn; ok !-e $fn, 'remote file deleted'; $client->quit->recv; } }; done_testing; AnyEvent-FTP-0.20/t/anyevent_ftp_client__cwd.t000644 000000 000000 00000002107 15123245460 021257 0ustar00rootroot000000 000000 use 5.010; use lib 't/lib'; use Test2::V0 -no_srand => 1; use Test2::Tools::ClientTests; use AnyEvent::FTP::Client; plan skip_all => 'requires client and server on localhost' if $ENV{AEF_REMOTE}; plan tests => 8; my $client = eval { AnyEvent::FTP::Client->new }; diag $@ if $@; isa_ok $client, 'AnyEvent::FTP::Client'; prep_client( $client ); our $config; $client->connect($config->{host}, $config->{port})->recv; $client->login($config->{user}, $config->{pass})->recv; do { my $res = eval { $client->cwd($config->{dir})->recv }; isa_ok $res, 'AnyEvent::FTP::Response'; is $res->code, 250, 'code = 250'; }; do { my $res = eval { $client->pwd->recv }; is $res, net_pwd($config->{dir}), "dir = " . net_pwd($config->{dir}); }; do { $client->cwd('t')->recv; isnt $client->pwd->recv, $config->{dir}, "in t dir"; my $res = eval { $client->cdup->recv }; diag $@ if $@; isa_ok $res, 'AnyEvent::FTP::Response'; is $res->code, 250, 'code = 250'; is $client->pwd->recv, net_pwd($config->{dir}), "dir = " . net_pwd($config->{dir}); }; $client->quit->recv; done_testing; AnyEvent-FTP-0.20/t/anyevent_ftp_client_response.t000644 000000 000000 00000000775 15123245460 022212 0ustar00rootroot000000 000000 use Test2::V0 -no_srand => 1; use AnyEvent::FTP::Client::Response; my $message = AnyEvent::FTP::Client::Response->new(227, [ 'Entering Passive Mode (192,168,42,23,156,29)' ]); is $message->code, 227, 'code = 227'; like $message->message->[0], qr/Entering Passive Mode/, 'entering passive mode message'; my($ip, $port) = eval { $message->get_address_and_port }; diag $@ if $@; # p1*256+p2 is $ip, '192.168.42.23', 'ip = 192.168.42.23'; is $port, 156*256+29, 'port = ' . (156*256+29); done_testing; AnyEvent-FTP-0.20/t/anyevent_ftp_client_role_responsebuffer.t000644 000000 000000 00000004103 15123245460 024412 0ustar00rootroot000000 000000 use Test2::V0 -no_srand => 1; eval { package Client; use Moo; ## no critic (Modules::ProhibitConditionalUseStatements) with 'AnyEvent::FTP::Client::Role::ResponseBuffer'; }; diag $@ if $@; my $client = eval { Client->new }; diag $@ if $@; isa_ok $client, 'Client'; my $count1 = 0; $client->on_each_response(sub { $count1++; isa_ok shift, 'AnyEvent::FTP::Response'; }); my $count2 = 0; $client->on_next_response(sub { $count2++; my $res = shift; is $res->code, 220, 'code = 220'; is join("\n", @{ $res->message }), 'ProFTPD 1.3.3a Server (Debian) [::ffff:10.10.16.1]', 'message = ProFTPD 1.3.3a Server (Debian) [::ffff:10.10.16.1]'; }); $client->process_message_line("220 ProFTPD 1.3.3a Server (Debian) [::ffff:10.10.16.1]\015\012"); my $count3 = 0; $client->on_next_response(sub { $count3++; my $res = shift; is $res->code, 214, 'code = 214'; is scalar(@{ $res->message }), 3, 'line count = 3'; }); $client->process_message_line("214-The following commands are recognized (* =>\'s unimplemented):\015\012"); $client->process_message_line("214-CWD XCWD CDUP XCUP SMNT* QUIT PORT PASV\015\012"); $client->process_message_line("214 Direct comments to root\@web01.sydney.wdlabs.com\015\012"); my $count4 = 0; $client->on_next_response(sub { $count4++; my $res = shift; is $res->code, 214, 'code = 214'; is scalar(@{ $res->message }), 6, 'line count = 6'; }); $client->process_message_line("214-The following commands are recognized:\015\012"); $client->process_message_line(" USER TYPE RETR RNFR NLST PWD ALLO EPSV\015\012"); $client->process_message_line(" PASS STRU STOR RNTO CWD CDUP SYST QUIT\015\012"); $client->process_message_line(" SITE PORT STOU DELE MKD NOOP STAT HELP\015\012"); $client->process_message_line(" MODE EPRT APPE LIST RMD ABOR PASV\015\012"); $client->process_message_line("214 End of Help.\015\012"); is $count1, 3, 'total = 3'; is $count2, 1, 'single = 1'; is $count3, 1, 'single = 1'; is $count4, 1, 'single = 1'; done_testing; AnyEvent-FTP-0.20/t/anyevent_ftp_client_site.t000644 000000 000000 00000001033 15123245460 021304 0ustar00rootroot000000 000000 use Test2::V0 -no_srand => 1; use AnyEvent::FTP::Client::Site; my $client = bless {}, 'AnyEvent::FTP::Client'; my $site = eval { AnyEvent::FTP::Client::Site->new($client) }; isa_ok $site, 'AnyEvent::FTP::Client::Site'; isa_ok $site->proftpd, 'AnyEvent::FTP::Client::Site::Proftpd'; isa_ok $site->microsoft, 'AnyEvent::FTP::Client::Site::Microsoft'; isa_ok $site->net_ftp_server, 'AnyEvent::FTP::Client::Site::NetFtpServer'; done_testing; package AnyEvent::FTP::Client; BEGIN { $INC{'AnyEvent/FTP/Client.pm'} = __FILE__ } AnyEvent-FTP-0.20/t/anyevent_ftp_role_event.t000644 000000 000000 00000002277 15123245460 021157 0ustar00rootroot000000 000000 use Test2::V0 -no_srand => 1; eval { package Foo; use Moo; ## no critic (Modules::ProhibitConditionalUseStatements) with 'AnyEvent::FTP::Role::Event'; __PACKAGE__->define_events(qw(bar baz other)); }; is $@, '', 'Create class Foo'; my $obj = Foo->new; isa_ok $obj, 'Foo'; ok $obj->can('on_bar'), "can on_bar"; ok $obj->can('on_baz'), "can on_baz"; ok (!$obj->can('on_bogus'), "can't on_bogus"); my $bar = 0; my $baz = 0; my $both = 0; $obj->on_bar(sub { $bar++ }); $obj->on_baz(sub { $baz++ }); $obj->on_bar(sub { $both++ }); $obj->on_baz(sub { $both++ }); ok $obj->can('emit'), 'can emit'; $obj->emit('bar'); is $bar, 1, 'bar = 1'; is $baz, 0, 'baz = 0'; is $both, 1, 'both = 1'; $obj->emit('baz'); is $bar, 1, 'bar = 1'; is $baz, 1, 'baz = 1'; is $both, 2, 'both = 2'; $obj->emit('bar'); is $bar, 2, 'bar = 2'; is $baz, 1, 'baz = 1'; is $both, 3, 'both = 3'; eval { $obj->emit('other') }; is $@, '', 'emitting an event with no listeners'; my $arg1; my $arg2; $obj->on_bar(sub { ($arg1, $arg2) = @_; }); $obj->emit('bar', 1, 2); is $arg1, 1, 'arg1 = 1'; is $arg2, 2, 'arg2 = 2'; $obj->emit('bar', 3, 4); is $arg1, 3, 'arg1 = 3'; is $arg2, 4, 'arg2 = 4'; done_testing; AnyEvent-FTP-0.20/t/anyevent_ftp_server_connection.t000644 000000 000000 00000002655 15123245460 022542 0ustar00rootroot000000 000000 use 5.010; use Test2::V0 -no_srand => 1; use AnyEvent::FTP::Server::Connection; eval q{ package Context; use Moo; with 'AnyEvent::FTP::Server::Role::Context'; sub push_request { my $sub = delete shift->{cb}; $sub->(@_) if $sub; } }; die $@ if $@; my $cx = eval { Context->new }; diag $@ if $@; isa_ok $cx, 'Context'; my $con = eval { AnyEvent::FTP::Server::Connection->new( context => $cx, ip => '1.2.3.4' ) }; diag $@ if $@; isa_ok $con, 'AnyEvent::FTP::Server::Connection'; my $check_user_foo = sub { my($con, $req) = @_; isa_ok $con, 'AnyEvent::FTP::Server::Connection'; isa_ok $req, 'AnyEvent::FTP::Request'; is eval { $req->command }, 'USER', 'cmd = USER'; diag $@ if $@; is eval { $req->args }, 'foo', 'arg = foo'; diag $@ if $@; }; $cx->{cb} = $check_user_foo; isa_ok eval { $con->process_request("USER foo\015\012") }, 'AnyEvent::FTP::Server::Connection'; diag $@ if $@; $cx->{cb} = $check_user_foo; isa_ok eval { $con->process_request("user foo\015\012") }, 'AnyEvent::FTP::Server::Connection'; diag $@ if $@; $cx->{cb} = sub { my($con, $req) = @_; isa_ok $con, 'AnyEvent::FTP::Server::Connection'; isa_ok $req, 'AnyEvent::FTP::Request'; is eval { $req->command }, 'PWD', 'cmd = PWD'; diag $@ if $@; is eval { $req->args }, '', 'arg = ""'; diag $@ if $@; }; isa_ok eval { $con->process_request("pWd\015\012") }, 'AnyEvent::FTP::Server::Connection'; diag $@ if $@; done_testing; AnyEvent-FTP-0.20/t/anyevent_ftp_server_context_fs.t000644 000000 000000 00000006601 15123245460 022552 0ustar00rootroot000000 000000 use Test2::V0 -no_srand => 1; use AnyEvent::FTP::Server::Context::FS; use File::Spec; use Test::AnyEventFTPServer; use File::Temp qw( tempdir ); use Cwd (); *my_abs_path = $^O eq 'MSWin32' ? sub ($) { $_[0] } : \&Cwd::abs_path ; foreach my $type (qw( FS FSRW )) { my $tmp = my_abs_path(tempdir( CLEANUP => 1 )); my $tmp_unmodified = $tmp; if($^O eq 'MSWin32') { chdir $tmp; note "changing to $tmp"; (undef, $tmp) = File::Spec->splitpath($tmp,1); $tmp =~ s{\\}{/}g; } mkdir "$tmp/a"; mkdir "$tmp/b"; my $t = create_ftpserver_ok($type); my $context; $t->on_connect(sub { $context = shift->context; }); $t->help_coverage_ok; ok -d $context->cwd, "cwd " . $context->cwd . " exists"; $t->command_ok('CWD') ->code_is(550) ->message_like(qr{CWD error}); $t->command_ok('CWD', "$tmp/a") ->code_is(250) ->message_like(qr{CWD command successful}); is $context->cwd, File::Spec->catdir($tmp_unmodified, 'a'), "cwd = $tmp_unmodified/a"; $t->command_ok('CDUP') ->code_is(250) ->message_like(qr{CDUP command successful}); is $context->cwd, "$tmp_unmodified", "cwd = $tmp_unmodified"; $t->command_ok('PWD') ->code_is(257) ->message_like(qr{"\Q$tmp\E" is the current directory}); my $size = do { open my $fh, '>', "$tmp/roger.txt"; print $fh "hello there"; close $fh; -s "$tmp/roger.txt"; }; $t->command_ok('SIZE') ->code_is(550); $t->command_ok('SIZE', "roger.txt") ->code_is(213) ->message_like(qr{^$size$}); $t->command_ok('SIZE', "$tmp/a") ->code_is(550) ->message_like(qr{\: not a regular file}); $t->command_ok("MKD") ->code_is(550) ->message_like(qr{MKD error}); $t->command_ok("MKD", "c") ->code_is(257) ->message_like(qr{Directory created}); ok -d "$tmp/c", "MKD created directory $tmp/c"; $t->command_ok("RMD") ->code_is(550) ->message_like(qr{RMD error}); $t->command_ok("RMD", "d") ->code_is(550) ->message_like(qr{RMD error}); $t->command_ok("RMD", "b") ->code_is(250) ->message_like(qr{Directory removed}); ok ! -d "$tmp/b", "RMD deleted directory $tmp/b"; $t->command_ok("DELE") ->code_is(550) ->message_like(qr{DELE error}); $t->command_ok("DELE", "bogus.txt") ->code_is(550) ->message_like(qr{DELE error}); $t->command_ok("DELE", "roger.txt") ->code_is(250) ->message_like(qr{File removed}); ok ! -e "$tmp/roger.txt", "roger.txt was deleted"; $t->command_ok("RNFR") ->code_is(501) ->message_like(qr{Invalid number of arguments}); $t->command_ok("RNFR", "foo.txt") ->code_is(550) ->message_like(qr{No such file or directory}); $size = do { open my $fh, '>', "$tmp/foo.txt"; print $fh "some not so random data\n"; close $fh; -s "$tmp/foo.txt"; }; $t->command_ok("RNFR", "foo.txt") ->code_is(350) ->message_like(qr{File or directory exists, ready for destination name}); # TODO more RNTO tests (RNFR has good coverage) $t->command_ok("RNTO", "bar.txt") ->code_is(250) ->message_like(qr{Rename successful}); $t->command_ok("STAT") ->code_is(211); $t->command_ok("STAT", "bar.txt") ->code_is(211) ->message_like(qr{file}); $t->command_ok("STAT", "$tmp/a") ->code_is(211) ->message_like(qr{dir}); } if($^O eq 'MSWin32') { note "changing to " . File::Spec->rootdir; chdir(File::Spec->rootdir); } done_testing; AnyEvent-FTP-0.20/t/anyevent_ftp_server_context_fsrw__ascii.t000644 000000 000000 00000005106 15123245460 024431 0ustar00rootroot000000 000000 use Test2::V0 -no_srand => 1; use AnyEvent::FTP::Server::Context::FSRW; use autodie; use File::Spec; use Test::AnyEventFTPServer; use File::Temp qw( tempdir ); my $tmp = tempdir( CLEANUP => 1); note "chdir $tmp"; chdir $tmp; if($^O eq 'MSWin32') { (undef, $tmp) = File::Spec->splitpath($tmp,1); $tmp =~ s{\\}{/}g; } my $t = create_ftpserver_ok('FSRW'); my $ctx; my $client; subtest 'connect and set mode' => sub { plan tests => 6; $t->on_connect(sub { $ctx = shift->context }); $t->command_ok(TYPE => 'A') ->code_is(200) ->message_like(qr{Type set to A}); $t->command_ok(CWD => $tmp) ->code_is(250) ->message_like(qr{CWD command successful});; $client = $t->_client; }; subtest 'store native (default)' => sub { plan tests => 12; my $payload_crlf = "one\015\012two\015\012three\015\012"; is $client->stor('test1.txt', \$payload_crlf)->recv->code, 226, 'store okay'; my $test1; is $client->retr('test1.txt', \$test1)->recv->code, 226, 'retr okay'; is $test1, $payload_crlf, "payload response matches what we sent"; open my $fh, '<', 'test1.txt'; $test1 = do { local $/; <$fh> }; close $fh; is $test1, "one\ntwo\nthree\n", "stored as native"; xd('test1.txt'); $test1 = ''; is $client->appe('test1.txt', \$payload_crlf)->recv->code, 226, 'appe okay'; is $client->retr('test1.txt', \$test1)->recv->code, 226, 'retr okay'; is $test1, "$payload_crlf$payload_crlf", "payload response matches what we sent (append)"; open $fh, '<', 'test1.txt'; $test1 = do { local $/; <$fh> }; close $fh; is $test1, "one\ntwo\nthree\none\ntwo\nthree\n", "stored as native (append)"; xd('test1.txt'); my $xfer = $client->stou(undef, \$payload_crlf); is $xfer->recv->code, 226, 'stou okay fn = ' . $xfer->remote_name; $test1 = ''; is $client->retr($xfer->remote_name, \$test1)->recv->code, 226, 'retr okay'; is $test1, $payload_crlf, "payload response matches what we sent (stou)"; open $fh, '<', $xfer->remote_name; $test1 = do { local $/; <$fh> }; close $fh; is $test1, "one\ntwo\nthree\n", "stored as native"; xd($xfer->remote_name); }; subtest 'store CRLF' => sub { plan skip_all => 'todo'; }; subtest 'store CR' => sub { plan skip_all => 'todo'; }; subtest 'store LF' => sub { plan skip_all => 'todo'; }; note "chdir " . File::Spec->rootdir; chdir(File::Spec->rootdir); done_testing; sub xd { my $fn = shift; if(eval { require Data::HexDump }) { open my $fh, '<', $fn; my $data = <$fh>; close $fh; note "hex dump of $fn"; note $_ for grep !/^$/, split /\n/, Data::HexDump::HexDump($data); } } AnyEvent-FTP-0.20/t/anyevent_ftp_server_context_fsrw__help_coverage.t000644 000000 000000 00000000272 15123245460 026143 0ustar00rootroot000000 000000 use Test2::V0 -no_srand => 1; use Test::AnyEventFTPServer; use AnyEvent::FTP::Server::Context::FSRW; my $server = create_ftpserver_ok('FSRW'); $server->help_coverage_ok; done_testing; AnyEvent-FTP-0.20/t/anyevent_ftp_server_context_fsrw__unauth.t000644 000000 000000 00000000670 15123245460 024646 0ustar00rootroot000000 000000 use Test2::V0 -no_srand => 1; use AnyEvent::FTP::Server::Context::FSRW; use 5.010; use Test::AnyEventFTPServer; my $t = create_ftpserver_ok('FSRW'); $t->auto_login(0); my @cmd = qw( CWD CDUP PWD SIZE MKD RMD DELE RNFR RNTO STAT PASV PORT REST RETR NLST LIST STOR APPE STOU ALLO NOOP SYST TYPE ); foreach my $cmd (@cmd) { $t->command_ok($cmd) ->code_is(530) ->message_like(qr{Please login with USER and PASS}); } done_testing; AnyEvent-FTP-0.20/t/anyevent_ftp_server_context_memory__cdup.t000644 000000 000000 00000001541 15123245460 024622 0ustar00rootroot000000 000000 use Test2::V0 -no_srand => 1; use autodie; use Test::AnyEventFTPServer; use Path::Class::Dir; use AnyEvent::FTP::Server::Context::Memory; AnyEvent::FTP::Server::Context::Memory->store->{top} = { foo => { bar => { stuff => { things => '' }} }, bar => {}, baz => 'stuff', }; my $t = create_ftpserver_ok('Memory'); my $context; $t->on_connect(sub { $context = shift->context }); # force a connect $t->command_ok('NOOP') ->code_is(200); is $context->cwd, "/", "cwd = /"; $t->command_ok('CDUP') ->code_is(250); is $context->cwd, "/", "cwd = /"; $context->cwd(Path::Class::Dir->new_foreign('Unix', '/top/foo/bar/stuff')); $t->command_ok('CDUP') ->code_is(250); is $context->cwd, "/top/foo/bar", "cwd = /top/foo/bar"; $context->cwd(Path::Class::Dir->new_foreign('Unix', '/bogus/directory')); $t->command_ok('CDUP') ->code_is(550); done_testing; AnyEvent-FTP-0.20/t/anyevent_ftp_server_context_memory__cwd.t000644 000000 000000 00000002027 15123245460 024444 0ustar00rootroot000000 000000 use Test2::V0 -no_srand => 1; use autodie; use Test::AnyEventFTPServer; use AnyEvent::FTP::Server::Context::Memory; AnyEvent::FTP::Server::Context::Memory->store->{top} = { foo => { bar => { stuff => { things => '' }} }, bar => {}, baz => 'stuff', }; my $t = create_ftpserver_ok('Memory'); my $context; $t->on_connect(sub { $context = shift->context }); # force a connect $t->command_ok('NOOP') ->code_is(200); is $context->cwd, "/", "cwd = /"; $t->command_ok(CWD => "/top") ->code_is(250); is $context->cwd, "/top", "cwd = /top"; $t->command_ok(CWD => "foo/bar/stuff") ->code_is(250); is $context->cwd, "/top/foo/bar/stuff", "cwd = /top/foo/bar/stuff"; $t->command_ok(CWD => "lameo") ->code_is(550); $t->command_ok(CWD => "/lameo") ->code_is(550); $t->command_ok(CWD => "../..") ->code_is(250); is $context->cwd, "/top/foo", "cwd = /top/foo"; $t->command_ok(CWD => "./../../../../../top/./foo/.//./bar/./stuff") ->code_is(250); is $context->cwd, "/top/foo/bar/stuff", "cwd = /top/foo/bar/stuff"; done_testing; AnyEvent-FTP-0.20/t/anyevent_ftp_server_context_memory__dele.t000644 000000 000000 00000001110 15123245460 024570 0ustar00rootroot000000 000000 use Test2::V0 -no_srand => 1; use autodie; use Test::AnyEventFTPServer; use AnyEvent::FTP::Server::Context::Memory; my $store = AnyEvent::FTP::Server::Context::Memory->store; $store->{foo} = {}; $store->{bar} = "hi there"; my $t = create_ftpserver_ok('Memory'); $t->command_ok(DELE => 'bar') ->code_is(250); ok !exists $store->{bar}, "file deleted"; $t->command_ok(DELE => '/') ->code_is(550); $t->command_ok(DELE => 'bar') ->code_is(550); todo "shouldn't be able to do a DELE on a directory" => sub { $t->command_ok(DELE => 'foo') ->code_is(550); }; done_testing; AnyEvent-FTP-0.20/t/anyevent_ftp_server_context_memory__help_coverage.t000644 000000 000000 00000000222 15123245460 026465 0ustar00rootroot000000 000000 use Test2::V0 -no_srand => 1; use Test::AnyEventFTPServer; my $server = create_ftpserver_ok('Memory'); $server->help_coverage_ok; done_testing; AnyEvent-FTP-0.20/t/anyevent_ftp_server_context_memory__mkd.t000644 000000 000000 00000001174 15123245460 024444 0ustar00rootroot000000 000000 use Test2::V0 -no_srand => 1; use autodie; use Test::AnyEventFTPServer; use AnyEvent::FTP::Server::Context::Memory; my $store = AnyEvent::FTP::Server::Context::Memory->store; my $t = create_ftpserver_ok('Memory'); $t->command_ok(MKD => 'foo') ->code_is(257); is ref($store->{foo}), 'HASH', 'created directory'; $t->command_ok(MKD => 'foo') ->code_is(521); $t->command_ok(MKD => '/foo') ->code_is(521); $t->command_ok(MKD => '../.././foo') ->code_is(521); $t->command_ok(MKD => '/') ->code_is(550); todo "shouldn't be able to MKD on root" => sub { $t->command_ok(MKD => '../../') ->code_is(550); }; done_testing; AnyEvent-FTP-0.20/t/anyevent_ftp_server_context_memory__nlst.t000644 000000 000000 00000001412 15123245460 024644 0ustar00rootroot000000 000000 use Test2::V0 -no_srand => 1; use autodie; use Test::AnyEventFTPServer; use AnyEvent::FTP::Server::Context::Memory; my $store = AnyEvent::FTP::Server::Context::Memory->store; my $t = create_ftpserver_ok('Memory'); $store->{foo} = {}; $store->{"bar.txt"} = "hi there"; $store->{"baz.txt"} = "and such"; $t->nlst_ok ->content_is("bar.txt\nbaz.txt\nfoo\n"); $t->nlst_ok('/') ->content_is("/bar.txt\n/baz.txt\n/foo\n"); $store->{stuff} = { map { $_ => $store->{$_} } keys %$store }; $t->nlst_ok('/stuff') ->content_is("/stuff/bar.txt\n/stuff/baz.txt\n/stuff/foo\n"); $t->nlst_ok('/stuff/bar.txt') ->content_is("/stuff/bar.txt\n"); todo 'wildcards' => sub { $t->nlst_ok('/stuff/*') ->content_is("/stuff/bar.txt\n/stuff/baz.txt\n/stuff/foo\n"); }; done_testing; AnyEvent-FTP-0.20/t/anyevent_ftp_server_context_memory__pwd.t000644 000000 000000 00000001107 15123245460 024457 0ustar00rootroot000000 000000 use Test2::V0 -no_srand => 1; use autodie; use Test::AnyEventFTPServer; use Path::Class::Dir; use AnyEvent::FTP::Server::Context::Memory; my $t = create_ftpserver_ok('Memory'); my $context; $t->on_connect(sub { $context = shift->context }); # force a connect $t->command_ok('NOOP') ->code_is(200); $t->command_ok('PWD') ->code_is(257) ->message_is('"/" is the current directory'); $context->cwd(Path::Class::Dir->new_foreign("Unix", '', qw( foo bar baz ))); $t->command_ok('PWD') ->code_is(257) ->message_is('"/foo/bar/baz" is the current directory'); done_testing; AnyEvent-FTP-0.20/t/anyevent_ftp_server_context_memory__rename.t000644 000000 000000 00000003516 15123245460 025142 0ustar00rootroot000000 000000 use Test2::V0 -no_srand => 1; use autodie; use Test::AnyEventFTPServer; use AnyEvent::FTP::Server::Context::Memory; my $store = AnyEvent::FTP::Server::Context::Memory->store; $store->{dir}->{"foo.txt"} = "hello there"; my $t = create_ftpserver_ok('Memory'); $t->command_ok(RNTO => "dir/bar.txt") ->code_is(503) ->message_is('Bad sequence of commands'); $t->command_ok(RNFR => "dir/bogus.txt") ->code_is(550) ->message_is('No such file or directory'); $t->command_ok(RNFR => "bogus/bogus.txt") ->code_is(550) ->message_is('No such file or directory'); $t->command_ok(RNFR => "dir/foo.txt") ->code_is(350) ->message_is('File or directory exists, ready for destination name'); $t->command_ok(RNTO => "dir/bar.txt") ->code_is(250) ->message_is('Rename successful'); ok !exists $store->{dir}->{"foo.txt"}, "file removed"; is $store->{dir}->{"bar.txt"}, "hello there", "file moved"; $store->{dir}->{"bar.txt"} = "hello there"; $store->{dir}->{"foo.txt"} = "hello there"; $t->command_ok(RNFR => "dir/foo.txt") ->code_is(350) ->message_is('File or directory exists, ready for destination name'); $t->command_ok(RNTO => "dir/bar.txt") ->code_is(550) ->message_is('File already exists'); $t->command_ok(RNFR => "dir/foo.txt") ->code_is(350) ->message_is('File or directory exists, ready for destination name'); $t->command_ok(RNTO => "bogus/bogus.txt") ->code_is(550) ->message_is('Rename failed'); $t->command_ok(RNFR => "/") ->code_is(550) ->message_is('No such file or directory'); $t->command_ok(RNFR => "../") ->code_is(550) ->message_is('No such file or directory'); $t->command_ok(RNFR => "dir/foo.txt") ->code_is(350) ->message_is('File or directory exists, ready for destination name'); todo "shouldn't be able to rename to root" => sub { $t->command_ok(RNTO => "/") ->code_is(550); }; done_testing; AnyEvent-FTP-0.20/t/anyevent_ftp_server_context_memory__rmd.t000644 000000 000000 00000001073 15123245460 024451 0ustar00rootroot000000 000000 use Test2::V0 -no_srand => 1; use autodie; use Test::AnyEventFTPServer; use AnyEvent::FTP::Server::Context::Memory; my $store = AnyEvent::FTP::Server::Context::Memory->store; $store->{foo} = {}; $store->{bar} = "hi there"; my $t = create_ftpserver_ok('Memory'); $t->command_ok(RMD => 'foo') ->code_is(250); ok !exists $store->{foo}, "directory deleted"; $t->command_ok(RMD => '/') ->code_is(550); $t->command_ok(RMD => 'foo') ->code_is(550); todo "shouldn't be able to RMD a file" => sub { $t->command_ok(RMD => 'bar') ->code_is(550); }; done_testing; AnyEvent-FTP-0.20/t/anyevent_ftp_server_context_memory__size.t000644 000000 000000 00000001111 15123245460 024632 0ustar00rootroot000000 000000 use Test2::V0 -no_srand => 1; use autodie; use Test::AnyEventFTPServer; use AnyEvent::FTP::Server::Context::Memory; AnyEvent::FTP::Server::Context::Memory->store->{top} = { 'hello.txt' => "1234567890", dir => { }, }; my $t = create_ftpserver_ok('Memory'); $t->command_ok(SIZE => "/top/hello.txt") ->code_is(213) ->message_is(10); $t->command_ok(SIZE => "/top/bogus.txt") ->code_is(550) ->message_is("/top/bogus.txt: No such file or directory"); $t->command_ok(SIZE => "/top/dir") ->code_is(550) ->message_is("/top/dir: not a regular file"); done_testing; AnyEvent-FTP-0.20/t/anyevent_ftp_server_context_memory__stat.t000644 000000 000000 00000001463 15123245460 024645 0ustar00rootroot000000 000000 use Test2::V0 -no_srand => 1; use autodie; use Test::AnyEventFTPServer; use AnyEvent::FTP::Server::Context::Memory; my $store = AnyEvent::FTP::Server::Context::Memory->store; $store->{dir}->{"foo.txt"} = "hello there"; my $t = create_ftpserver_ok('Memory'); $t->command_ok(STAT => "/bogus.txt") ->code_is(450) ->message_is('No such file or directory'); $t->command_ok(STAT => "/dir/foo.txt") ->code_is(211) ->message_is("It's a file"); $t->command_ok(STAT => "/dir") ->code_is(211) ->message_is("It's a directory"); $t->command_ok(STAT => "/") ->code_is(211) ->message_is("It's a directory"); $t->command_ok(STAT => "..") ->code_is(211) ->message_is("It's a directory"); $t->command_ok(STAT => ".././/bogus.txt") ->code_is(450) ->message_is('No such file or directory'); done_testing; AnyEvent-FTP-0.20/t/anyevent_ftp_server_role_auth.t000644 000000 000000 00000003004 15123245460 022352 0ustar00rootroot000000 000000 use Test2::V0 -no_srand => 1; use AnyEvent::FTP::Server::Role::Auth; use 5.010; use Test::AnyEventFTPServer; eval { package AnyEvent::FTP::Server::Context::TestContext; use Moo; ## no critic (Modules::ProhibitConditionalUseStatements) extends 'AnyEvent::FTP::Server::Context'; with 'AnyEvent::FTP::Server::Role::Auth'; with 'AnyEvent::FTP::Server::Role::Help'; has '+unauthenticated_safe_commands' => ( default => sub { [ qw( USER PASS HELP QUIT FOO ) ] }, ); sub cmd_foo { my($self, $con, $req) = @_; $con->send_response(211 => 'Here to stay'); $self->done; } sub cmd_bar { my($self, $con, $req) = @_; $con->send_response(211 => 'And another thing'); $self->done; } 1; $INC{'AnyEvent/FTP/Server/Context/TestContext.pm'} = __FILE__; }; die $@ if $@; my $t = create_ftpserver_ok('TestContext'); $t->auto_login(0); $t->on_connect(sub { shift->context->bad_authentication_delay(0); }); $t->command_ok('FOO') ->code_is(211); $t->command_ok('BAR') ->code_is(530); $t->command_ok('PASS', 'rubbish') ->code_is(503); $t->command_ok('USER') ->code_is(530); my($user, $pass) = split /:/, $t->test_uri->userinfo; $t->command_ok('USER', $user) ->code_is(331); $t->command_ok('PASS', 'bogus') ->code_is(530); $t->command_ok('USER', $user) ->code_is(331); $t->command_ok('PASS', $pass) ->code_is(230); $t->command_ok('FOO') ->code_is(211); $t->command_ok('BAR') ->code_is(211); $t->help_coverage_ok('AnyEvent::FTP::Server::Role::Auth'); done_testing; AnyEvent-FTP-0.20/t/anyevent_ftp_server_role_help.t000644 000000 000000 00000000643 15123245460 022347 0ustar00rootroot000000 000000 use Test2::V0 -no_srand => 1; use Test::AnyEventFTPServer; global_timeout_ok; foreach my $type (qw( FSRW Memory )) { my $server = create_ftpserver_ok($type); my $client = $server->connect_ftpclient_ok; is $client->help->recv->code, 214, "HELP"; is $client->help('HELP')->recv->code, 214, "HELP HELP"; is eval { $client->help('bogus command')->recv} || $@->code, 502, "HELP bogus command"; } done_testing; AnyEvent-FTP-0.20/t/anyevent_ftp_server_role_old.t000644 000000 000000 00000000671 15123245460 022176 0ustar00rootroot000000 000000 use Test2::V0 -no_srand => 1; use Test::AnyEventFTPServer; global_timeout_ok; foreach my $type (qw( FSRW Memory )) { my $server = create_ftpserver_ok($type); my $client = $server->connect_ftpclient_ok; is $client->allo->recv->code, 202, "ALLO"; is $client->noop->recv->code, 200, "NOOP"; is $client->syst->recv->code, 215, "SYST"; $server->help_coverage_ok('AnyEvent::FTP::Server::Role::Old'); } pass 'good'; done_testing; AnyEvent-FTP-0.20/t/anyevent_ftp_server_role_transferprep.t000644 000000 000000 00000001144 15123245460 024127 0ustar00rootroot000000 000000 use Test2::V0 -no_srand => 1; use AnyEvent::FTP::Server::Role::TransferPrep; use strict; use warnings; use Test::AnyEventFTPServer; global_timeout_ok(30); foreach my $type (qw( FSRW Memory )) { my $t = create_ftpserver_ok($type); $t->help_coverage_ok('AnyEvent::FTP::Server::Role::TransferPrep'); $t->command_ok('REST') ->code_is(501) ->message_like(qr{REST requires}); $t->command_ok('REST', 42) ->code_is(350) ->message_like(qr{Restarting at 42\.}); $t->command_ok('PASV') ->code_is(227) ->message_like(qr{Entering Passive Mode \(127,0,0,1,\d+,\d+\)}); } done_testing; AnyEvent-FTP-0.20/t/anyevent_ftp_server_role_type.t000644 000000 000000 00000002144 15123245460 022376 0ustar00rootroot000000 000000 use Test2::V0 -no_srand => 1; use AnyEvent::FTP::Server::Role::Type; use 5.010; use Test::AnyEventFTPServer; eval { package AnyEvent::FTP::Server::Context::TestContext; use Moo; ## no critic (Modules::ProhibitConditionalUseStatements) extends 'AnyEvent::FTP::Server::Context'; with 'AnyEvent::FTP::Server::Role::Type'; with 'AnyEvent::FTP::Server::Role::Auth'; with 'AnyEvent::FTP::Server::Role::Help'; sub cmd_gt { my($self, $con, $req) = @_; $con->send_response(211 => $self->type); $self->done; } 1; $INC{'AnyEvent/FTP/Server/Context/TestContext.pm'} = __FILE__; }; die $@ if $@; my $t = create_ftpserver_ok('TestContext'); $t->command_ok('GT') ->message_like(qr{A}); $t->command_ok('TYPE') ->code_is(500) ->message_like(qr{Type not understood}); $t->command_ok('GT') ->message_like(qr{A}); $t->command_ok(TYPE => 'I') ->code_is(200) ->message_like(qr{Type set to I}); $t->command_ok('GT') ->message_like(qr{I}); $t->command_ok(TYPE => 'A') ->code_is(200) ->message_like(qr{Type set to A}); $t->command_ok('GT') ->message_like(qr{A}); done_testing; AnyEvent-FTP-0.20/t/anyevent_ftp_server_unambiguousresponseencoder.t000644 000000 000000 00000006021 15123245460 026047 0ustar00rootroot000000 000000 use Test2::V0 -no_srand => 1; use AnyEvent::FTP::Server::UnambiguousResponseEncoder; use AnyEvent::FTP::Client::Role::ResponseBuffer; use AnyEvent::FTP::Response; my $encoder = AnyEvent::FTP::Server::UnambiguousResponseEncoder->new; isa_ok $encoder, 'AnyEvent::FTP::Server::UnambiguousResponseEncoder'; eval q{ package Client; use Moo; with 'AnyEvent::FTP::Client::Role::ResponseBuffer'; }; die $@ if $@; my $client = Client->new; isa_ok $client, 'Client'; do { my $raw = $encoder->encode(220, 'ProFTPD 1.3.3a Server (Debian) [::ffff:127.0.0.1]'); is $raw, "220 ProFTPD 1.3.3a Server (Debian) [::ffff:127.0.0.1]\015\012", 'raw response'; $client->on_next_response(sub { my $res = shift; is $res->code, 220, 'code match'; is join('|', @{ $res->message }), 'ProFTPD 1.3.3a Server (Debian) [::ffff:127.0.0.1]', 'message match'; }); $client->process_message_line($raw); }; do { my $raw = $encoder->encode(220, ['ProFTPD 1.3.3a Server (Debian) [::ffff:127.0.0.1]']); is $raw, "220 ProFTPD 1.3.3a Server (Debian) [::ffff:127.0.0.1]\015\012", 'raw response'; $client->on_next_response(sub { my $res = shift; is $res->code, 220, 'code match'; is join('|', @{ $res->message }), 'ProFTPD 1.3.3a Server (Debian) [::ffff:127.0.0.1]', 'message match'; }); $client->process_message_line($raw); }; do { my $raw = $encoder->encode(AnyEvent::FTP::Response->new(220, ['ProFTPD 1.3.3a Server (Debian) [::ffff:127.0.0.1]'])); is $raw, "220 ProFTPD 1.3.3a Server (Debian) [::ffff:127.0.0.1]\015\012", 'raw response'; $client->on_next_response(sub { my $res = shift; is $res->code, 220, 'code match'; is join('|', @{ $res->message }), 'ProFTPD 1.3.3a Server (Debian) [::ffff:127.0.0.1]', 'message match'; }); $client->process_message_line($raw); }; do { my $raw = $encoder->encode(AnyEvent::FTP::Response->new(220, 'ProFTPD 1.3.3a Server (Debian) [::ffff:127.0.0.1]')); is $raw, "220 ProFTPD 1.3.3a Server (Debian) [::ffff:127.0.0.1]\015\012", 'raw response'; $client->on_next_response(sub { my $res = shift; is $res->code, 220, 'code match'; is join('|', @{ $res->message }), 'ProFTPD 1.3.3a Server (Debian) [::ffff:127.0.0.1]', 'message match'; }); $client->process_message_line($raw); }; do { my $raw = $encoder->encode(220, [qw( one two three )]); is $raw, "220-one\015\012220-two\015\012220 three\015\012", 'raw response'; $client->on_next_response(sub { my $res = shift; is $res->code, 220, 'code match'; is join('|', @{ $res->message }), 'one|two|three', 'message match'; }); $client->process_message_line($_) for split /\015\012/, $raw; }; do { my $raw = $encoder->encode(AnyEvent::FTP::Response->new(220, [qw( one two three )])); is $raw, "220-one\015\012220-two\015\012220 three\015\012", 'raw response'; $client->on_next_response(sub { my $res = shift; is $res->code, 220, 'code match'; is join('|', @{ $res->message }), 'one|two|three', 'message match'; }); $client->process_message_line($_) for split /\015\012/, $raw; }; done_testing; AnyEvent-FTP-0.20/t/lib/000755 000000 000000 00000000000 15123245460 014604 5ustar00rootroot000000 000000 AnyEvent-FTP-0.20/t/lib/Test2/000755 000000 000000 00000000000 15123245460 015605 5ustar00rootroot000000 000000 AnyEvent-FTP-0.20/t/lib/Test2/Tools/000755 000000 000000 00000000000 15123245460 016705 5ustar00rootroot000000 000000 AnyEvent-FTP-0.20/t/lib/Test2/Tools/ClientTests.pm000644 000000 000000 00000010352 15123245460 021505 0ustar00rootroot000000 000000 package Test2::Tools::ClientTests; use strict; use warnings; use 5.010; use FindBin (); use Path::Class qw( dir file ); use Path::Class (); use File::Spec; use File::Glob qw( bsd_glob ); use Cwd (); use Test2::API qw( context ); use base qw( Exporter ); our @EXPORT = qw( $config $detect prep_client translate_dir net_pwd reset_timeout ); $ENV{LC_ALL} = 'C'; our $config; our $detect; our $lock; *my_abs_path = $^O eq 'MSWin32' ? sub ($) { $_[0] } : \&Cwd::abs_path ; $config->{dir} //= dir( -l file(__FILE__)->absolute ? file(readlink file(__FILE__))->absolute->parent : $FindBin::Bin )->parent->stringify; do { my $file = file( __FILE__ )->absolute->parent->file("config.yml")->stringify; $ENV{AEF_CONFIG} = $file if -r $file; }; if(defined $ENV{AEF_PORT} && ! defined $ENV{AEF_CONFIG}) { $ENV{AEF_CONFIG} //= file( bsd_glob '~/etc/localhost.yml' )->stringify; } if(defined $ENV{AEF_CONFIG}) { my $save = $config->{dir}; require YAML; $config = YAML::LoadFile($ENV{AEF_CONFIG}); $config->{dir} = $save if defined $save; $config->{dir} = Path::Class::Dir->new($config->{dir})->resolve; $config->{port} //= $ENV{AEF_PORT} if defined $ENV{AEF_PORT}; $config->{host} //= $ENV{AEF_HOST} // 'localhost'; $config->{port} = getservbyname($config->{port}, "tcp") if defined $config->{port} && $config->{port} !~ /^\d+$/; if(defined $config->{remote}) { $ENV{AEF_REMOTE} = $config->{remote}; # remote server tests may not be parallel safe require NX::Lock; $lock = NX::Lock->new($ENV{AEF_CONFIG}, block => 1); } } else { require AnyEvent::FTP::Server; my $server = AnyEvent::FTP::Server->new( host => 'localhost', port => 0, default_context => 'AnyEvent::FTP::Server::Context::FSRW', ); $config->{host} = 'localhost'; $config->{user} = join '', map { chr(ord('a') + int rand(26)) } (1..10); $config->{pass} = join '', map { chr(ord('a') + int rand(26)) } (1..10); { my $ctx = context(); $ctx->note("using fake credentials ", join ':', $config->{user}, $config->{pass}); $ctx->release; } $server->on_bind(sub { my $port = shift; $config->{port} = $port; my $ctx = context(); $ctx->note("binding aeftpd localhost:$port"); $ctx->release; }); $server->on_connect(sub { my $con = shift; $con->context->authenticator(sub { my($user, $pass) = @_; $user eq $config->{user} && $pass eq $config->{pass} ? 1 : 0; }); $con->context->bad_authentication_delay(0); }); $server->start; $detect->{ae} = 1; } our $anyevent_test_timeout; sub reset_timeout { $anyevent_test_timeout = AnyEvent->timer( after => ($detect->{ae} ? 5 : 15), cb => sub { my $ctx = context(); $ctx->bail("TIMEOUT: giving up"); $ctx->release; } ); } sub prep_client { my($client) = @_; if($ENV{AEF_DEBUG}) { $client->on_send(sub { my($cmd, $arguments) = @_; $arguments //= ''; $arguments = 'XXXX' if $cmd eq 'PASS'; my $ctx = context(); $ctx->note("CLIENT: $cmd $arguments"); $ctx->release; }); $client->on_each_response(sub { my $res = shift; my $ctx = context(); $ctx->note(sprintf "SERVER: [ %d ] %s\n", $res->code, $_) for @{ $res->message }; $ctx->release; }); } $client->on_greeting(sub { my $res = shift; $detect->{wu} = 1 if $res->message->[0] =~ /FTP server \(Version wu/; $detect->{pu} = 1 if $res->message->[0] =~ /Welcome to Pure-FTPd/; $detect->{vs} = 1 if $res->message->[0] =~ /\(vsFTPd /; $detect->{pl} = 1 if $res->message->[0] =~ /FTP server \(Net::FTPServer/; $detect->{pr} = 1 if $res->message->[0] =~ /ProFTPD/; $detect->{ms} = 1 if $res->message->[0] =~ /Microsoft FTP Service/; $detect->{nc} = 1 if $res->message->[0] =~ /NcFTPd Server/; $detect->{xb} = 1 if $res->message->[0] =~ /^bftpd /; }); } sub translate_dir { my $dir = shift; if($^O eq 'cygwin' && $detect->{ms}) { $dir =~ s{^/cygdrive/(.)/}{$1:}; $dir =~ s{^/tmp/}{/cygwin/tmp}; $dir =~ s{/}{\\}g; return $dir; } else { return $dir; } } sub net_pwd { my($pwd) = @_; if($^O eq 'MSWin32') { (undef,$pwd) = File::Spec->splitpath($pwd,1); $pwd =~ s{\\}{/}g; } my_abs_path($pwd); } eval { require EV; EV->import; }; 1; AnyEvent-FTP-0.20/t/test_anyeventftpserver.t000644 000000 000000 00000004153 15123245460 021057 0ustar00rootroot000000 000000 use Test2::V0 -no_srand => 1; use Test::AnyEventFTPServer; use File::chdir; global_timeout_ok; subtest 'basic' => sub { my $server = create_ftpserver_ok; isa_ok $server, 'AnyEvent::FTP::Server'; isa_ok $server->test_uri, 'URI'; my $client = $server->connect_ftpclient_ok; isa_ok $client, 'AnyEvent::FTP::Client'; my $response = $client->help->recv; is $response->code, 214, "help response code = 214"; $response = $client->quit->recv; is $response->code, 221, "quit response code = 221"; $server->help_coverage_ok; $server->command_ok('bogus') ->code_is(500) ->code_like(qr{5..}) ->message_like(qr{not understood}); $server->command_ok('HELP') ->code_is(214) ->code_like(qr{.1.}) ->message_like(qr{The following commands are recognized}); isa_ok $server->res, 'AnyEvent::FTP::Client::Response'; }; subtest 'content_is' => sub { my $server = create_ftpserver_ok('FSRO'); $server->command_ok('CWD' => "$CWD/corpus/nlst"); $server->nlst_ok; $server->content_is("one.txt\nthree.txt\ntwo.txt\n"); is( intercept { $server->content_is("one.txt\nthree.txt\ntwo.txt\n") }, array { event Ok => sub { call pass => T(); call name => 'content matches'; }; end; }, 'pass okay', ); is( intercept { $server->content_is("one.txt\ntwo.txt\nthree.txt\n") }, array { event Ok => sub { call pass => F(); call name => 'content matches'; }; event Diag => sub {}; event Diag => sub { call message => 'content:' }; event Diag => sub { call message => ' one.txt' }; event Diag => sub { call message => ' three.txt' }; event Diag => sub { call message => ' two.txt' }; event Diag => sub { call message => 'expected:' }; event Diag => sub { call message => ' one.txt' }; event Diag => sub { call message => ' two.txt' }; event Diag => sub { call message => ' three.txt' }; end; }, 'pass okay', ); todo 'testing todo' => sub { $server->content_is("one.txt\ntwo.txt\nthree.txt\n"); }; }; done_testing; AnyEvent-FTP-0.20/tools/000755 000000 000000 00000000000 15123245460 014733 5ustar00rootroot000000 000000 AnyEvent-FTP-0.20/tools/issue10.pl000644 000000 000000 00000000206 15123245460 016557 0ustar00rootroot000000 000000 use strict; use warnings; use lib '../lib'; use AnyEvent::FTP::Client; AnyEvent::FTP::Client->new->connect("ftp://localhost:9521/"); AnyEvent-FTP-0.20/tools/test_client.pl000644 000000 000000 00000002315 15123245460 017606 0ustar00rootroot000000 000000 use strict; use warnings; use autodie; use 5.010; use File::Spec; use Path::Class qw( dir ); use File::Spec; use FindBin (); use YAML qw( LoadFile ); use File::Glob qw( bsd_glob ); my @services = do { open my $fh, '<', '/etc/services'; map { [split /\t/]->[0] } grep /^(..)?ftp\s/, <$fh>; }; chdir dir($FindBin::Bin)->parent->stringify; say "[self test]"; system 'prove', '-l', '-j', 3, '-r', 't', ;#'xt'; my @client_tests = map { $_->stringify } grep { $_->basename =~ /^client_.*\.t$/ } dir(File::Spec->curdir)->subdir('t')->children(no_hidden => 1); foreach my $service (@services) { local $ENV{AEF_CONFIG} = File::Spec->catfile(bsd_glob '~/.ftptest/localhost.yml'); local $ENV{AEF_PORT} = $service; say "[$service]"; system 'prove', '-l', '-j', 3, @client_tests; } my @list = do { my $dir = File::Spec->catdir(bsd_glob '~/.ftptest'); my $dh; opendir DIR, $dir; my @list = readdir DIR; closedir DIR; map { File::Spec->catfile(bsd_glob('~/.ftptest'), $_) } grep !/^localhost\.yml$/, grep !/^\./, @list; }; foreach my $config (@list) { local $ENV{AEF_REMOTE} = LoadFile($config)->{remote}; local $ENV{AEF_CONFIG} = $config; say "[$config]"; system 'prove', '-l', '-j', 3, @client_tests; } AnyEvent-FTP-0.20/xt/000755 000000 000000 00000000000 15123245460 014226 5ustar00rootroot000000 000000 AnyEvent-FTP-0.20/xt/author/000755 000000 000000 00000000000 15123245460 015530 5ustar00rootroot000000 000000 AnyEvent-FTP-0.20/xt/author/critic.t000644 000000 000000 00000000516 15123245460 017174 0ustar00rootroot000000 000000 use Test2::Require::Module 'Test2::Tools::PerlCritic'; use Test2::Require::Module 'Perl::Critic'; use Test2::Require::Module 'Perl::Critic::Community'; use Test2::V0; use Perl::Critic; use Test2::Tools::PerlCritic; my $critic = Perl::Critic->new( -profile => 'perlcriticrc', ); perl_critic_ok ['lib','t'], $critic; done_testing; AnyEvent-FTP-0.20/xt/author/eol.t000644 000000 000000 00000000510 15123245460 016470 0ustar00rootroot000000 000000 use strict; use warnings; use Test::More; BEGIN { plan skip_all => 'test requires Test::EOL' unless eval q{ use Test::EOL; 1 }; }; use Test::EOL; use FindBin; use File::Spec; chdir(File::Spec->catdir($FindBin::Bin, File::Spec->updir, File::Spec->updir)); all_perl_files_ok(grep { -e $_ } qw( bin lib t Makefile.PL )); AnyEvent-FTP-0.20/xt/author/no_tabs.t000644 000000 000000 00000000522 15123245460 017341 0ustar00rootroot000000 000000 use strict; use warnings; use Test::More; BEGIN { plan skip_all => 'test requires Test::NoTabs' unless eval q{ use Test::NoTabs; 1 }; }; use Test::NoTabs; use FindBin; use File::Spec; chdir(File::Spec->catdir($FindBin::Bin, File::Spec->updir, File::Spec->updir)); all_perl_files_ok( grep { -e $_ } qw( bin lib t Makefile.PL )); AnyEvent-FTP-0.20/xt/author/pod.t000644 000000 000000 00000000472 15123245460 016502 0ustar00rootroot000000 000000 use strict; use warnings; use Test::More; BEGIN { plan skip_all => 'test requires Test::Pod' unless eval q{ use Test::Pod; 1 }; }; use Test::Pod; use FindBin; use File::Spec; chdir(File::Spec->catdir($FindBin::Bin, File::Spec->updir, File::Spec->updir)); all_pod_files_ok( grep { -e $_ } qw( bin lib )); AnyEvent-FTP-0.20/xt/author/pod_coverage.t000644 000000 000000 00000004001 15123245460 020345 0ustar00rootroot000000 000000 use strict; use warnings; use Test::More; BEGIN { plan skip_all => 'test requires 5.010 or better' unless $] >= 5.010; plan skip_all => 'test requires Test::Pod::Coverage' unless eval q{ use Test::Pod::Coverage; 1 }; plan skip_all => 'test requires YAML' unless eval q{ use YAML; 1; }; plan skip_all => 'test does not always work in cip check' if defined $ENV{CIPSTATIC} && $ENV{CIPSTATIC} eq 'true'; }; use Test::Pod::Coverage; use YAML qw( LoadFile ); use FindBin; use File::Spec; my $config_filename = File::Spec->catfile( $FindBin::Bin, File::Spec->updir, File::Spec->updir, 'author.yml' ); my $config; $config = LoadFile($config_filename) if -r $config_filename; plan skip_all => 'disabled' if $config->{pod_coverage}->{skip}; chdir(File::Spec->catdir($FindBin::Bin, File::Spec->updir, File::Spec->updir)); my @private_classes; my %private_methods; push $config->{pod_coverage}->{private}->@*, 'Alien::.*::Install::Files#Inline'; foreach my $private ($config->{pod_coverage}->{private}->@*) { my($class,$method) = split /#/, $private; if(defined $class && $class ne '') { my $regex = eval 'qr{^' . $class . '$}'; if(defined $method && $method ne '') { push @private_classes, { regex => $regex, method => $method }; } else { push @private_classes, { regex => $regex, all => 1 }; } } elsif(defined $method && $method ne '') { $private_methods{$_} = 1 for split /,/, $method; } } my @classes = all_modules; plan tests => scalar @classes; foreach my $class (@classes) { SKIP: { my($is_private_class) = map { 1 } grep { $class =~ $_->{regex} && $_->{all} } @private_classes; skip "private class: $class", 1 if $is_private_class; my %methods = map {; $_ => 1 } map { split /,/, $_->{method} } grep { $class =~ $_->{regex} } @private_classes; $methods{$_} = 1 for keys %private_methods; my $also_private = eval 'qr{^' . join('|', keys %methods ) . '$}'; pod_coverage_ok $class, { also_private => [$also_private] }; }; } AnyEvent-FTP-0.20/xt/author/pod_spelling_common.t000644 000000 000000 00000001350 15123245460 021743 0ustar00rootroot000000 000000 use strict; use warnings; use Test::More; BEGIN { plan skip_all => 'test requires Test::Pod::Spelling::CommonMistakes' unless eval q{ use Test::Pod::Spelling::CommonMistakes; 1 }; plan skip_all => 'test requires YAML' unless eval q{ use YAML qw( LoadFile ); 1 }; }; use Test::Pod::Spelling::CommonMistakes; use FindBin; use File::Spec; my $config_filename = File::Spec->catfile( $FindBin::Bin, File::Spec->updir, File::Spec->updir, 'author.yml' ); my $config; $config = LoadFile($config_filename) if -r $config_filename; plan skip_all => 'disabled' if $config->{pod_spelling_common}->{skip}; chdir(File::Spec->catdir($FindBin::Bin, File::Spec->updir, File::Spec->updir)); # TODO: test files in bin too. all_pod_files_ok; AnyEvent-FTP-0.20/xt/author/strict.t000644 000000 000000 00000001031 15123245460 017220 0ustar00rootroot000000 000000 use strict; use warnings; use Test::More; BEGIN { plan skip_all => 'test requires Test::Strict' unless eval q{ use Test::Strict; 1 }; }; use Test::Strict; use FindBin; use File::Spec; chdir(File::Spec->catdir($FindBin::Bin, File::Spec->updir, File::Spec->updir)); unshift @Test::Strict::MODULES_ENABLING_STRICT, 'ozo', 'Test2::Bundle::SIPS', 'Test2::V0', 'Test2::Bundle::Extended'; note "enabling strict = $_" for @Test::Strict::MODULES_ENABLING_STRICT; all_perl_files_ok( grep { -e $_ } qw( bin lib t Makefile.PL )); AnyEvent-FTP-0.20/xt/author/version.t000644 000000 000000 00000001473 15123245460 017407 0ustar00rootroot000000 000000 use strict; use warnings; use Test::More; use FindBin (); BEGIN { plan skip_all => "test requires Test::Version 2.00" unless eval q{ use Test::Version 2.00 qw( version_all_ok ), { has_version => 1, filename_match => sub { $_[0] !~ m{/(ConfigData|Install/Files)\.pm$} }, }; 1 }; plan skip_all => 'test requires YAML' unless eval q{ use YAML; 1; }; } use YAML qw( LoadFile ); use FindBin; use File::Spec; my $config_filename = File::Spec->catfile( $FindBin::Bin, File::Spec->updir, File::Spec->updir, 'author.yml' ); my $config; $config = LoadFile($config_filename) if -r $config_filename; if($config->{version}->{dir}) { note "using dir " . $config->{version}->{dir} } version_all_ok($config->{version}->{dir} ? ($config->{version}->{dir}) : ()); done_testing; AnyEvent-FTP-0.20/xt/release/000755 000000 000000 00000000000 15123245460 015646 5ustar00rootroot000000 000000 AnyEvent-FTP-0.20/xt/release/changes.t000644 000000 000000 00000001113 15123245460 017437 0ustar00rootroot000000 000000 use strict; use warnings; use Test::More; BEGIN { plan skip_all => 'test requires Test::CPAN::Changes' unless eval q{ use Test::CPAN::Changes; 1 }; }; use Test::CPAN::Changes; use FindBin; use File::Spec; chdir(File::Spec->catdir($FindBin::Bin, File::Spec->updir, File::Spec->updir)); do { my $old = \&Test::Builder::carp; my $new = sub { my($self, @messages) = @_; return if $messages[0] =~ /^Date ".*" is not in the recommend format/; $old->($self, @messages); }; no warnings 'redefine'; *Test::Builder::carp = $new; }; changes_file_ok; done_testing; AnyEvent-FTP-0.20/xt/release/fixme.t000644 000000 000000 00000000616 15123245460 017146 0ustar00rootroot000000 000000 use strict; use warnings; use Test::More; BEGIN { plan skip_all => 'test requires Test::Fixme' unless eval q{ use Test::Fixme 0.14; 1 }; }; use Test::Fixme 0.07; use FindBin; use File::Spec; chdir(File::Spec->catdir($FindBin::Bin, File::Spec->updir, File::Spec->updir)); run_tests( match => qr/FIXME/, where => [ grep { -e $_ } qw( bin lib t Makefile.PL Build.PL )], warn => 1, );