diff options
Diffstat (limited to 'test')
41 files changed, 12016 insertions, 0 deletions
diff --git a/test/.cvsignore b/test/.cvsignore new file mode 100644 index 0000000..fb288d0 --- /dev/null +++ b/test/.cvsignore @@ -0,0 +1,41 @@ +tests +*-tests +Makefile +request +*.log* +server +regress +compress +*.gz +*.tmp +acl +auth +lock +basic +ssl +xml +stubs +ca +ca-stamp +ssigned.pem +wildcard.* +*.cert +*.csr +fqdn.pem +wrongcn.pem +socket +redirect +session +*.out +core* +props +socket-ssl +resolve +*.bb +*.da +*.bbg +ca*.pem +chain.pem +*.p12 +client.* +output.pem diff --git a/test/COPYING b/test/COPYING new file mode 100644 index 0000000..a43ea21 --- /dev/null +++ b/test/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 675 Mass Ave, Cambridge, MA 02139, USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + 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 the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) 19yy <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/test/ChangeLog b/test/ChangeLog new file mode 100644 index 0000000..a32791c --- /dev/null +++ b/test/ChangeLog @@ -0,0 +1,1329 @@ +Sat Jun 21 12:59:49 2003 Joe Orton <joe@manyfish.co.uk> + + * request.c (versions): Fix and enable test. + +Wed Jun 18 20:09:59 2003 Joe Orton <joe@manyfish.co.uk> + + * request.c (is_alive): Adapt for new socket API. + + * socket.c (do_connect, addr_connect): Likewise. + +Tue May 20 20:14:03 2003 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (cert_fingerprint): Fix for VPATH builds. + +Sat May 10 17:13:05 2003 Joe Orton <joe@manyfish.co.uk> + + * xml.c (matches): Add regression test for prefix matching bug + fixed in 0.18.0. + +Sat Apr 26 19:22:29 2003 Joe Orton <joe@manyfish.co.uk> + + * request.c (any_te_header): New function. + +Wed Apr 23 18:24:19 2003 Joe Orton <joe@manyfish.co.uk> + + * stubs.c (stub_ssl): Test ne_ssl_cert_import, ne_ssl_cert_export, + ne_ssl_cert_write stubs. + +Wed Apr 23 14:05:38 2003 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (read_write): New function. + +Wed Apr 23 00:34:44 2003 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (cache_cert, verify_cache): New functions. + +Wed Apr 23 00:14:14 2003 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (any_ssl_request): Free the cert after passing it to + ne_ssl_trust_cert. + +Tue Apr 22 23:24:33 2003 Joe Orton <joe@manyfish.co.uk> + + * string-tests.c (unbase64): Improve coverage. + +Tue Apr 22 20:25:15 2003 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (import_export, flatten_pem, cert_compare): New functions. + +Tue Apr 22 18:32:43 2003 Joe Orton <joe@manyfish.co.uk> + + * string-tests.c (b64_check, unbase64): New functions. + (base64): Use b64_check. + +Tue Apr 22 15:54:04 2003 Joe Orton <joe@manyfish.co.uk> + + * string-tests.c (base64): Test decoding binary data which + contains bytes with the high bit set. + +Tue Apr 22 14:18:03 2003 Joe Orton <joe@manyfish.co.uk> + + * string-tests.c (base64): Moved here... + + * util-tests.c (base64): ...from here. + +Tue Apr 22 13:17:48 2003 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (just_serve_string, fail_not_ssl): New functions. + +Tue Apr 22 13:09:13 2003 Joe Orton <joe@manyfish.co.uk> + + * stubs.c (stub_ssl): Test ne_ssl_cert_validity stub. + +Tue Apr 22 11:35:10 2003 Joe Orton <joe@manyfish.co.uk> + + * request.c (versions): Run test as XFAIL. + +Tue Apr 22 11:33:43 2003 Joe Orton <joe@manyfish.co.uk> + + * util-tests.c (version_string): New function. + +Tue Apr 22 09:23:27 2003 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (check_validity, cert_validity): New functions. + +Mon Apr 21 19:45:39 2003 Joe Orton <joe@manyfish.co.uk> + + * util-tests.c (digest_md5): Replace ne_md5_buffer. + (md5): Use digest_md5; test 500-byte string. + +Mon Apr 21 18:38:02 2003 Joe Orton <joe@manyfish.co.uk> + + * xml.c (fail_parse): Call ne_xml_parse with length=0 finish + parse. + +Mon Apr 21 17:18:45 2003 Joe Orton <joe@manyfish.co.uk> + + * props.c: Add tests for ne_207.h interface and ne_simple_propfind + from ne_props.h. + + * xml.c: Add tests for new XML interface. + + * Makefile.in: Run props tests before lock since the latter is + implemented using the former. + +Mon Apr 7 22:27:18 2003 Joe Orton <joe@manyfish.co.uk> + + * stubs.c (stub_ssl): Test for ne_ssl_cert_identity stub. + +Mon Apr 7 22:17:56 2003 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (cert_fingerprint): Renamed from fingerprint. + (check_identity, cert_identities): New functions. + +Sun Apr 6 20:18:30 2003 Joe Orton <joe@manyfish.co.uk> + + * stubs.c (stub_ssl): Adjust for new clicert API. + +Sun Apr 6 20:12:48 2003 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (dname_compare): Renamed from comparisons. + (dname_readable): New function. + + * makekeys.sh: Create justmail.cert. + +Sun Apr 6 20:00:18 2003 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (keypw_prompt): Removed function. + (init, load_client_cert, client_cert_provided): Adapt for new + clicert API. + (ccert_unencrypted): New function. + +Fri Apr 4 22:34:12 2003 Joe Orton <joe@manyfish.co.uk> + + * request.c (fail_request_with_error): Refactored from + fail_request; check for a particular error string. + (fail_request): Use fail_request_with_error. + (invalid_response_gives_error): New function. + (fail_long_header): Use it. + (fail_corrupt_chunks): New function. + +Sat Mar 29 14:39:20 2003 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (comparisons): New function. + + * stubs.c (stub_ssl): Test ne_ssl_dname_cmp. + +Sat Mar 29 13:58:37 2003 Joe Orton <joe@manyfish.co.uk> + + * makekeys.sh: Generate noclient.p12. + + * ssl.c (load_client_cert): Test ne_ssl_clicert_name. + + * stubs.c (stub_ssl): Check for ne_ssl_clicert_name stub. + +Sat Mar 29 13:31:35 2003 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (load_client_cert): Test ne_ssl_clicert_owner. + +Fri Mar 28 22:13:55 2003 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (fingerprint): New function. + + * stubs.c (stub_ssl): Check for ne_ssl_cert_digest stub. + +Wed Mar 26 22:52:15 2003 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (fail_missing_CN): New function. + + * makekeys.sh: Generate missingcn.cert. + + * openssl.conf: Allow commonName to be omitted from CSR. + +Wed Mar 26 22:41:48 2003 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (load_server_certs): Renamed from load_ca; test loading + non-existent file. + +Wed Mar 26 20:38:08 2003 Joe Orton <joe@manyfish.co.uk> + + * stubs.c (stub_ssl): Updated for new SSL interface. + +Tue Mar 25 20:32:07 2003 Joe Orton <joe@manyfish.co.uk> + + Update tests for changes to SSL interface: + + * socket.c (init_ssl): Use ne_ssl_context_create, + ne_ssl_cert_read, ne_ssl_ctx_trustcert. + (begin): Use ne_sock_connect_ssl. + + * ssl.c (serve_ssl_chained, trust_default_ca, load_client_cert, + check_dname, check_cert_dnames, check_cert, check_chain, + parse_chain, cc_check_dnames, cc_provided_dnames): New functions. + (serve_ccert): Always trust SERVER_CERT; optionally call + SSL_CTX_set_client_CA_list. + (any_ssl_request, load_ca, fail_truncated_eof): Use + ne_ssl_cert_read and ne_ssl_trust_cert. + (keypw_prompt): Fail if userdata is NULL. + (fail_load_ccerts, load_pkcs12_ccert, load_pem_ccert, check_DNs): + Removed functions. + (parse_cert): Use check_cert. + (client_cert_provided, client_cert_pkcs12): Rewritten for new API. + + * makekeys.sh: Generate calist.pem, unclient.p12. + +Wed Mar 12 22:36:27 2003 Joe Orton <joe@manyfish.co.uk> + + * redirect.c (simple): Fold in tests for 30[237] redirects for + better coverage. + (no_redirect): Another test for _location returning NULL. + +Wed Mar 12 22:29:45 2003 Joe Orton <joe@manyfish.co.uk> + + * redirect.c (process_redir): Factored out from check_redir. + (no_redirect): New function. + +Sun Mar 9 17:46:37 2003 Joe Orton <joe@manyfish.co.uk> + + * lock.c (fail_discover): New function. + +Sat Mar 1 10:53:58 2003 Joe Orton <joe@manyfish.co.uk> + + * uri-tests.c (authinfo): Removed. + (escapes): Test nothing-to-escape and invalid URI cases. + (compares): Gain 100% branch coverage in ne_path_compare. + (default_port): Test unknown scheme case. + (parse): Test authinfo here, and some edge cases. + (unparse): Fill in port if default. + +Sat Mar 1 09:20:42 2003 Joe Orton <joe@manyfish.co.uk> + + * socket.c (multi_init): New function. + +Sat Mar 1 08:04:09 2003 Joe Orton <joe@manyfish.co.uk> + + * string-tests.c (cleaner): New function. + +Wed Feb 26 22:13:14 2003 Joe Orton <joe@manyfish.co.uk> + + * request.c (fail_eof_chunk, fail_eof_badclen): New tests. + +Wed Feb 26 21:54:39 2003 Joe Orton <joe@manyfish.co.uk> + + * util-tests.c (support): New function. + (bad_sl, accept_sl): More status-lines. + +Tue Feb 25 21:06:18 2003 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (do_ssl_response): Fail if response contains + "Proxy-Authorization" header. + (apt_post_send, apt_creds, auth_proxy_tunnel): New functions. + +Thu Nov 28 21:25:01 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (te_over_clength2): New test. + +Sun Nov 17 18:59:04 2002 Joe Orton <joe@manyfish.co.uk> + + * socket.c (addr_make_v4, addr_make_v6, addr_compare): New + functions. + +Fri Oct 11 00:49:01 2002 Joe Orton <joe@manyfish.co.uk> + + * props.c (regress): Moved from regress.c:propfind_segv; add + regression test for ne_props.c segfault fixed in rev 1.83. + + * regress.c: Removed. + +Tue Oct 8 20:06:55 2002 Joe Orton <joe@manyfish.co.uk> + + * xml.c (matches): Add tests that entities in attribute values are + dereferenced by the XML parser. + +Fri Oct 4 17:10:19 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (no_body_bad_clength, no_body_empty_clength): New + tests. + (expect_no_body): Use better paths in the requests. + +Tue Sep 24 21:27:33 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (fail_long_header, versions, hook_create_req): New + functions. + +Tue Sep 17 21:08:17 2002 Joe Orton <joe@manyfish.co.uk> + + * openssl.conf (neonca): Make 'countryName' optional in CA policy. + (reqDN.CNfirst): New section. + + * makekeys.sh: Generate 'cnfirst.cert', which has commonName as + first attribute in subject DN. + + * ssl.c (commonName_first): New function. + +Tue Sep 10 21:11:18 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (fail_double_lookup): New function. + +Sun Aug 25 23:16:33 2002 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (do_ssl_response): Add 'unclean' argument. + (all callers changed). + (serve_response_unclean, empty_truncated_eof, fail_truncated_eof): + New functions. + +Sun Aug 25 19:16:00 2002 Joe Orton <joe@manyfish.co.uk> + + * socket.c (resolve_numeric): Test ne_addr_print too. + +Sun Aug 25 13:39:37 2002 Joe Orton <joe@manyfish.co.uk> + + * resolve.c: New file. + +Sun Aug 25 11:25:12 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (is_alive): Update for new ne_addr_* interface. + +Sun Aug 25 08:31:16 2002 Joe Orton <joe@manyfish.co.uk> + + * socket.c (serve_truncate, ssl_truncate): New functions. + +Sun Aug 25 08:28:17 2002 Joe Orton <joe@manyfish.co.uk> + + * socket.c (do_connect): New function; use new + ne_sock_connect/ne_addr interface. + (begin) [SOCKET_SSL, !SOCKET_SSL]: Use do_connect. + (resolve_numeric): Adjust for new ne_addr interface. + (resolve_ipv6): Disable test. + +Sat Aug 24 08:50:06 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (fail_statusline): New function. + +Fri Aug 23 22:52:38 2002 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (init): FAILHARD if initialization fails. + +Wed Aug 21 13:29:58 2002 Joe Orton <joe@manyfish.co.uk> + + * uri-tests.c (null_uri): Removed test. + (parse): More tests including IPv6 address tests; use ONCMP macro. + (failparse): New function. + (unparse): Add URI with IPv6 address. + +Wed Aug 21 13:28:37 2002 Joe Orton <joe@manyfish.co.uk> + + * socket.c (resolve_ipv6): New function. + +Mon Aug 19 16:59:46 2002 Joe Orton <joe@manyfish.co.uk> + + * socket.c (resolve): Adapt for new ne_addr_resolve interface. + (resolve_numeric): New test. + + * request.c (is_alive): Use new ne_addr_resolve interface. + +Mon Aug 19 16:57:53 2002 Joe Orton <joe@manyfish.co.uk> + + * socket.c (begin): Fix handling of connect failure. + (TO_BEGIN): Handle errors from to_begin properly. + +Sun Aug 18 23:37:34 2002 Joe Orton <joe@manyfish.co.uk> + + * string-tests.c (str_errors): Check return value and behaviour + when error string is truncated, an + +Sun Aug 18 23:31:51 2002 Joe Orton <joe@manyfish.co.uk> + + * util-tests.c (str_errors): Moved to... + + * string-tests.c (str_errors): here. + +Sun Aug 18 23:11:28 2002 Joe Orton <joe@manyfish.co.uk> + + * string-tests.c (strnzcpy): New function. + +Sun Aug 18 08:18:24 2002 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (caseless_match): New function. + + * makekeys.sh: Create caseless.cert. + +Sun Aug 18 08:12:32 2002 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (notdns_altname): New function. + + * makekeys.sh: Create altname4.cert. + + * openssl.conf (altExt4): New section. + +Sun Aug 18 07:42:30 2002 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (multi_commonName): New function. + + * openssl.conf (req): Use distinguished_name section as + specificied by $REQDN. + (reqDN.doubleCN): New section. + + * makekeys.sh: Set $REQDN; create twocn.cert. + +Sun Aug 18 00:47:19 2002 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (accept_signed_cert): New function, factored out from + simple. + (simple): Use accept_signed_cert. + (subject_altname, two_subject_altname, two_subject_altname2): + New function. + + * openssl.conf: Add extension sections altExt, altExt2, altExt3. + + * makekeys.sh: Generate altname.cert, altname2.cert, + altname3.cert. + +Sat Aug 17 18:41:42 2002 Joe Orton <joe@manyfish.co.uk> + + * makekeys.sh (csr_fields): New function; generate output for + `openssl req'. + +Sat Aug 17 18:27:36 2002 Joe Orton <joe@manyfish.co.uk> + + * makekeys.sh: Add CA and REQ variables to simplify OpenSSL + invocation. Pass -config to req rather than relying on installed + default configuration. + + * openssl.conf: Add `req' and `reqDN' sections to allow use with + `openssl req' command. Add CA basic constraint extention to + certificates used. + +Sat Aug 10 10:42:57 2002 Joe Orton <joe@manyfish.co.uk> + + * makekeys.sh: Use openssl binary as ${OPENSSL}. + + * Makefile.in: Pick up OPENSSL from configure, and pass it through + to makekeys.sh. + +Sat Aug 10 10:18:15 2002 Joe Orton <joe@manyfish.co.uk> + + * socket.c (begin): Don't use run-time initialization. + + * request.c (s_progress): Fix warnings on FreeBSD. + +Mon Aug 5 21:08:24 2002 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (ccert_provider, client_cert_provided): New functions. + (fail_load_ccerts): Enable function. + +Sun Aug 4 22:32:43 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (serve_abort, retry_after_abort): New functions. + +Sun Aug 4 13:28:47 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (continued_header): New function. + +Sun Aug 4 12:54:52 2002 Joe Orton <joe@manyfish.co.uk> + + * socket.c [SOCKET_SSL] (ssl_closure): New function; use instead + of read_reset, write_reset for SOCKET_SSL build. + +Sun Aug 4 12:27:34 2002 Joe Orton <joe@manyfish.co.uk> + + Build socket.c twice, once for testing over SSL connections: + + * Makefile.in (socket-ssl.o, socket-ssl): New targets. + (SSL_TESTS): Include socket-ssl target. + + * socket.c [SOCKET_SSL] (init_ssl, wrap_serve): New functions. + [SOCKET_SSL] (begin): Alternate implementation. + +Sat Aug 3 22:20:59 2002 Joe Orton <joe@manyfish.co.uk> + + * session.c (privates): New function. + +Sat Aug 3 22:20:14 2002 Joe Orton <joe@manyfish.co.uk> + + * auth.c (fail_auth_cb, tunnel_regress): New function. + +Sat Aug 3 22:12:48 2002 Joe Orton <joe@manyfish.co.uk> + + * auth.c (forget_regress): New function. + +Sun Jul 28 12:24:02 2002 Joe Orton <joe@manyfish.co.uk> + + * lock.c (lock_timeout, submit_test, lock_shared): Use ne_concat, + not CONCAT? macros. + + * ssl.c (init, fail_expired, fail_notvalid): Likewise. + +Thu Jul 25 00:04:47 2002 Joe Orton <joe@manyfish.co.uk> + + * string-tests.c (buf_concat, buf_concat2, buf_concat3): Renamed + from concat, concat1, concat3). + (concat): New function. + +Sun Jul 14 11:42:03 2002 Joe Orton <joe@manyfish.co.uk> + + * util-tests.c (versioning): New function. + +Thu Jul 11 17:24:29 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (no_headers): New function. + +Wed Jul 10 22:58:01 2002 Joe Orton <joe@manyfish.co.uk> + + * utils.c (any_2xx_request_body): New function. + +Wed Jul 10 22:44:12 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (ptimeout_eof, ptimeout_eof2, close_not_retried, + serve_close2): New functions. + (abort_respbody): Rejoin child earlier for reliable results. + +Sun Jul 7 12:17:11 2002 Joe Orton <joe@manyfish.co.uk> + + * socket.c (expect_eof): Better error reporting. + (good_close): Split from finish(). + (finish): Use good_close. + (expect_write_closed, write_reset, read_reset): Add tests that + an ECONNRESET is treated as a SOCK_CLOSED failure. + +Sun Jul 7 08:38:12 2002 Joe Orton <joe@manyfish.co.uk> + + * utils.c (serve_response): Use discard_body(). + +Sun Jul 7 08:28:56 2002 Joe Orton <joe@manyfish.co.uk> + + * socket.c (serve_expect, full_write, small_writes, large_writes, + echo_server, echo_expect, echo_lines): New functions. + +Sat Jul 6 13:11:33 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (serve_eof, fail_early_eof, fail_eof_continued, + fail_eof_headers): New functions. + +Sat Jul 6 08:58:17 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (serve_100_once, expect_100_once): New functions. + +Fri Jul 5 21:43:58 2002 Joe Orton <joe@manyfish.co.uk> + + * auth.c (username): Use the correct spelling of Aladdin. + (auth_hdr): Simplify debug messages. + (auth_serve): Fail if no Authorization header is given. + (basic): Check for response status. + +Fri Jul 5 21:41:02 2002 Joe Orton <joe@manyfish.co.uk> + + * utils.c (any_2xx_request): New function. + +Sun Jun 30 17:10:59 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (fail_noserver): Factor out from host_not_found. + (fail_lookup): Equivalent to old host_not_found. + (fail_connect, abort_respbody): New function. + +Sun Jun 30 14:32:32 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (fail_chunksize): New function. + +Sun Jun 30 10:39:17 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (test_persist): Factor out from persist; take + response and response body as arguments. + (persist_http11): New function, equivalent to old persist. + (persist_chunked, persist_http10): New functions. + +Sun Jun 30 10:25:07 2002 Joe Orton <joe@manyfish.co.uk> + + * utils.c (serve_response): Factor out from single_serve_string, + many_serve_string. + (single_serve_string, many_serve_string): Use serve_response. + +Sun Jun 30 09:13:55 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (expect_response, persist, persist_timeout, + multi_header): Rely on the fact that the test framework + will reap the server. + (expect_no_body, no_body_304, no_body_204, no_body_HEAD, + no_body_chunks): New functions. + +Tue Jun 25 23:05:42 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (trailing_header): New function. + +Sun Jun 23 23:00:03 2002 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (no_verify): Fix sixth argument to any_ssl_request. + +Sun Jun 23 15:21:06 2002 Joe Orton <joe@manyfish.co.uk> + + * Makefile.in (grind): New target. + + * run.sh: Respect $HARNESS. + +Sun Jun 23 15:20:38 2002 Joe Orton <joe@manyfish.co.uk> + + * props.c: New file. + +Sun Jun 23 09:37:10 2002 Joe Orton <joe@manyfish.co.uk> + + * makekeys.sh: Ignore failure from `hostname -[sdf]' commands, as + appropriate tests are skipped on failure. + +Sun Jun 23 08:33:50 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (host_not_found): Use any_request(); simplify. + (proxy_no_resolve): New function. + +Sun Jun 16 11:40:19 2002 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (do_ssl_response): Succeed if connection is closed + by client after negotiation. + (serve_tunnel, fail_tunnel, proxy_tunnel): New functions. + +Mon Jun 10 21:18:03 2002 Joe Orton <joe@manyfish.co.uk> + + * redirect.c (check_redir): Await server child before returning. + +Sun Jun 9 13:05:25 2002 Joe Orton <joe@manyfish.co.uk> + + * socket.c (DECL): Don't use run-time initialization. + (single_read, single_peek, small_reads, read_and_peek, line_closure, + larger_read, line_toolong): Use DECL, as last declaration. + +Sun Jun 9 13:03:36 2002 Joe Orton <joe@manyfish.co.uk> + + * compress.c (reader, do_fetch): Check that inflated data is of + expected length. + +Sun Jun 9 11:40:54 2002 Joe Orton <joe@manyfish.co.uk> + + * redirect.c (struct redir_args): Add 'path' field. + (any_request): Use path in Request-URI. + (simple, redir_303, non_absolute): Fill in path. + (relative_1, relative_2): New functions. + +Tue Jun 4 16:56:08 2002 Joe Orton <joe@manyfish.co.uk> + + * uri-tests.c (parents): Improve ne_path_parent tests. + +Mon Jun 3 18:22:31 2002 Joe Orton <joe@manyfish.co.uk> + + * cookies.c: New file. + +Sun Jun 2 10:06:42 2002 Joe Orton <joe@manyfish.co.uk> + + * basic.c (dav_capabilities): New function. + +Sat Jun 1 10:39:04 2002 Joe Orton <joe@manyfish.co.uk> + + * socket.c (to_begin, to_end, peek_timeout, read_timeout, + readline_timeout, fullread_timeout): New functions. + +Sat Jun 1 10:38:13 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (read_timeout): Use sleepy_server. + (hung_server): Removed. + +Sat Jun 1 10:32:45 2002 Joe Orton <joe@manyfish.co.uk> + + * utils.c (sleepy_server): New function. + +Thu May 30 20:00:40 2002 Joe Orton <joe@manyfish.co.uk> + + * socket.c (finish): New function, factored out from common code. + (small_reads, read_and_peek, larger_read): Use it. + (line_simple, line_closure, line_empty, line_toolong, line_mingle, + line_chunked): New functions. + +Sun May 26 14:54:52 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (fill_uri, match_hostport, hostports): Moved functions + to session.c. + + * session.c: New file. + +Fri May 24 08:14:21 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (match_hostport, hostports): New functions. + +Tue May 21 21:29:25 2002 Joe Orton <joe@manyfish.co.uk> + + * redirect.c: New file. + +Sun May 19 18:25:48 2002 Joe Orton <joe@manyfish.co.uk> + + * auth.c, lock.c, regress.c, socket.c, ssl.c, utils.c, utils.h: + Update for socket API change; s/sock_/ne_sock_/, + s/SOCK_/NE_SOCK_/. + +Wed May 8 19:41:24 2002 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (do_ssl_response): Take response body as parameter; all + callers changed. + (serve_eof, simple_eof): New functions. + +Wed May 8 17:17:27 2002 Joe Orton <joe@manyfish.co.uk> + + * socket.c: New file. + + * sock-tests.c: Removed file. + + * Makefile.in: Updated accordingly. + +Wed May 8 11:53:35 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (host_not_found): New function. + +Wed May 1 21:41:02 2002 Joe Orton <joe@manyfish.co.uk> + + * uri-tests.c (parse): New function. + (simple, simple_ssl): Adjust for ne_uri_parse port default. + +Tue Apr 23 21:39:09 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (read_timeout): Better diagnostic for test failure + cases. + +Sun Apr 14 12:00:19 2002 Joe Orton <joe@manyfish.co.uk> + + * basic.c (content_type): Updated to reflect default charset + ISO-8859-1 for text/ media types. + +Sun Apr 7 17:35:21 2002 Joe Orton <joe@manyfish.co.uk> + + * run.sh: Set MALLOC_CHECK_ so glibc's heap corruption detection + is enabled. + +Sun Apr 7 17:30:37 2002 Joe Orton <joe@manyfish.co.uk> + + * compress.c (do_fetch): Reset 'failed' flag to zero each time. + +Wed Apr 3 20:16:43 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (NO_BODY): Renamed from NOBODY (all callers changed). + (empty_header, ignore_header_ws, ignore_header_ws2): New tests. + (ignore_header_ws3): Renamed from ignore_header_spaces. + +Tue Apr 2 21:09:33 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (expect_header_value): New function. + (ignore_header_case, ignore_header_spaces, + ignore_header_tabs): New tests. + +Mon Mar 25 21:51:24 2002 Joe Orton <joe@manyfish.co.uk> + + * lock.c (multi_lock_response, lock_shared): New function. + (lock_compare): Factored out from discover_results. + (discover, lock_timeout, submit_test): Adjust for lock API + changes. + +Mon Mar 25 21:36:55 2002 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (fail_ssl_request): Check failure bits against + NE_SSL_FAILMASK. + +Sun Mar 10 22:07:48 2002 Joe Orton <joe@manyfish.co.uk> + + * stubs.c (stub_decompress, sd_reader): New function. + +Sun Mar 10 21:39:29 2002 Joe Orton <joe@manyfish.co.uk> + + * lock.c (activelock): New function, factored out from + lock_response. + (lock_response): Use activelock; adjust argument types. + (make_lock): Adjusted for lock_response arg changes. + (discard_response, serve_discovery, discover_result, discover): + New functions. + +Wed Mar 6 22:22:04 2002 Joe Orton <joe@manyfish.co.uk> + + * lock.c (submit_test): Handle failures gracefully. + +Wed Mar 6 21:23:27 2002 Joe Orton <joe@manyfish.co.uk> + + * lock.c (submit_test): Update to expect an absoluteURI in If: + headers. + +Wed Mar 6 21:17:37 2002 Joe Orton <joe@manyfish.co.uk> + + * uri-tests.c (unparse): New function. + +Tue Mar 5 22:59:37 2002 Joe Orton <joe@manyfish.co.uk> + + * uri-tests.c (cmp): Checks for case-insensitive comparison, and + empty path, "/" equivalence. + +Mon Mar 4 01:07:03 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (blank_response): Add test for potential segfault + in strip_eol (would fail if run under Electric Fence). + +Sun Mar 3 20:50:01 2002 Joe Orton <joe@manyfish.co.uk> + + * lock.c (make_lock, store_single, store_several, got_if_header, + serve_verify_if, do_request, submit_test, if_simple, + if_under_infinite, if_infinite_over, if_child, if_covered_child): + New tests. + + (lock_timeout): Adjusted for API changes. + +Sun Mar 3 15:29:05 2002 Joe Orton <joe@manyfish.co.uk> + + * uri-tests.c (cmp_differ, cmp): New functions. + +Sun Mar 3 11:08:36 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (fill_uri): New function. + +Sun Feb 17 21:31:21 2002 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (fqdn_match): Removed test. + +Sun Feb 17 20:32:16 2002 Joe Orton <joe@manyfish.co.uk> + + * makekeys.sh: Create keypair for client cert. + + * ssl.c (do_ssl_response, any_ssl_request, all callers thereof): + Better error handling. + (serve_ccert, load_pem_ccert, keypw_prompt, load_pkcs12_ccert, + fail_load_ccerts, client_cert_pem, client_cert_pkcs12): New + functions. + +Sun Feb 17 11:54:19 2002 Joe Orton <joe@manyfish.co.uk> + + * basic.c (do_range): Factored out from + get_range/fail_range_length. + (get_range, fail_range_length): Use do_range. + (fail_range_units, fail_range_notrange, fail_range_unsatify): New + tests. + +Sun Feb 17 11:36:00 2002 Joe Orton <joe@manyfish.co.uk> + + * basic.c (get_range, fail_range_length): New functions. + +Sat Feb 16 23:29:40 2002 Joe Orton <joe@manyfish.co.uk> + + * xml.c: New file. + + * Makefile.in (DAV_TESTS): Add xml tests. + +Sat Feb 16 15:26:27 2002 Joe Orton <joe@manyfish.co.uk> + + * compress.c (do_fetch): Rename from fetch(); add 'expect_fail' + paramater. (fetch): Re-implement using do_fetch. + (fail_trailing, fail_bad_csum, fail_truncate): New functions. + + * Makefile.in (trailing.gz, truncated.gz, badcsum.gz): New helper + files. + +Thu Feb 14 19:09:42 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (everywhere): Simplify use of expect_response. + +Thu Feb 14 19:05:48 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (ignore_bad_headers): New function. + +Mon Feb 11 22:06:40 2002 Joe Orton <joe@manyfish.co.uk> + + * makekeys.sh: If the hostname command is clever enough to give + FQDN, hostname, domainname, then create wildcard.cert; cert with a + wildcard commonName. + + * ssl.c (wildcard_match): New function + +Mon Feb 11 21:55:52 2002 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (any_ssl_request): Take session pointer, don't + initialize here. (DEFSESS): New macro. + (everywhere): Use DEFSESS rather than passing pointer-to- + session pointer. + +Mon Feb 11 20:44:44 2002 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (fqdn_match): Test for FQDN matching against non-qualified + FQDN. + (makekeys.sh): Create server cert with FQDN. + +Sun Feb 10 12:36:55 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (chunk_oversize): New function. + +Sat Feb 9 21:12:47 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (reason_phrase): New function. + +Sat Feb 9 16:50:58 2002 Joe Orton <joe@manyfish.co.uk> + + * request.c (read_timeout, hung_server): New functions. + +Thu Feb 7 22:58:31 2002 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (cache_verify, no_verify, count_vfy): New functions. + +Thu Feb 7 19:39:33 2002 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (any_ssl_request): Take server function as argument: all + callers changed. + (fail_ssl_request): Renamed from failreq; uses any_ssl_request. + +Wed Feb 6 20:43:32 2002 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (load_ca): New function. + +Wed Feb 6 20:36:15 2002 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (any_ssl_request): Make ca_cert and verify_fn arguments + optional. + (trustall): Removed function. + (simple): Use the CA cert; no need for a verify function. + (parse_cert): Don't give a CA cert, force use of verify function. + (failreq): Bug fix, don't trust server cert as CA. + (fail_wrongCN, fail_notvalid, fail_expired): Pass server cert + as CA cert server cert is self-signed. + +Tue Feb 5 20:33:42 2002 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (fail_untrusted_ca, fail_self_signed): New tests. + (fail_serve): New function. + (failreq, any_ssl_request): Take ca cert argument. + (check_DNs, trustall, get_failures): Adjust for new verify + callback interface. + +Sat Feb 2 14:18:11 2002 Joe Orton <joe@manyfish.co.uk> + + * ssl.c (do_ssl_response): Factored out from serve_ssl. + (serve_ssl): Use do_ssl_response. + (serve_scache, session_cache): New functions. + +Thu Jan 31 21:09:58 2002 Joe Orton <joe@manyfish.co.uk> + + * Makefile.in (ca-stamp): New target. + + * makekeys.sh: New helper script. + + * ssl.c (parse_cert, fail_wrongCN, fail_expired, fail_notvalid): + New tests. + (any_ssl_request, trustall, check_DNs, failreq): New auxiliaries. + +Thu Jan 31 20:42:38 2002 Joe Orton <joe@manyfish.co.uk> + + * wrongcn.pem, notvalid.pem, expired.pem, server.key: New files. + + * Makefile.in: Remove targets to generate certs. + +Wed Jan 30 21:15:33 2002 Joe Orton <joe@manyfish.co.uk> + + * Makefile.in (wrongcn.pem): New target. + +Wed Jan 30 19:58:18 2002 Joe Orton <joe@manyfish.co.uk> + + * string-tests.c: Updated for ne_buffer API change. + +Sat Jan 26 11:23:34 2002 Joe Orton <joe@manyfish.co.uk> + + * Makefile.in: Pick up appropriate TESTS, HELPERS from configure. + (ssltests*, davtests*): Remove crud. + + * compress.c: Presume zlib support present if built. + +Sun Jan 20 23:29:37 2002 Joe Orton <joe@manyfish.co.uk> + + * ssl.c: New file. + + * Makefile.in (ssltests-no, ssltests-yes, server.pem, server.key): + New targets. + (check): Conditionally run SSL tests. + +Sun Jan 20 13:20:56 2002 Joe Orton <joe@manyfish.co.uk> + + * Makefile.in (davtests-no, davtests-yes): Separate test programs + which require DAV support; only run if DAV is enabled. + + * Makefile.in (test): Pass SRCDIR env var through to run.sh. + + * run.sh: Pass SRCDIR as argv[1] to test programs. + + * compress.c (init): New function. Use 'newsfn' global for + filename of NEWS file. + +Sun Jan 20 13:06:40 2002 Joe Orton <joe@manyfish.co.uk> + + * Makefile.in: Fixes for VPATH build + +Mon Jan 14 01:58:39 2002 Joe Orton <joe@manyfish.co.uk> + + * basic.c (content_type): Add harsher charset handling tests. + +Sun Jan 13 14:01:57 2002 Joe Orton <joe@manyfish.co.uk> + + * lock.c (lock_timeout): Use make_session. + + * acl.c (test_acl): Use make_session. + + * auth.c (basic, retries): Use make_session. + +Sun Jan 13 14:01:13 2002 Joe Orton <joe@manyfish.co.uk> + + * utils.c (make_session): New function. + +Sun Jan 13 14:00:34 2002 Joe Orton <joe@manyfish.co.uk> + + * basic.c (content_type): Rename ctype to ct; check if charset is + unexpectedly set. + +Sun Jan 13 13:58:07 2002 Joe Orton <joe@manyfish.co.uk> + + * basic.c: New file. + + * Makefile.in: Add `basic' test suite. + +Mon Jan 7 22:05:33 2002 Joe Orton <joe@manyfish.co.uk> + + * Makefile.in: Don't pass CFLAGS to CC when linking. + +Mon Jan 7 21:46:03 2002 Joe Orton <joe@manyfish.co.uk> + + * lock.c: New file. + + * Makefile.in: Add 'lock' to TESTS, build lock. + +Mon Jan 7 21:17:21 2002 Joe Orton <joe@manyfish.co.uk> + + * skeleton.c: Add skeleton test suite. + +Tue Jan 1 21:47:09 2002 Joe Orton <joe@manyfish.co.uk> + + * Makefile.in: Use CPPFLAGS correctly. + +Sun Dec 9 14:02:50 2001 Joe Orton <joe@manyfish.co.uk> + + * string-tests.c (ONCMP): New macro. (everywhere): Use it. + (grow): Add ne_buffer_grow test. + +Sun Dec 9 13:12:27 2001 Joe Orton <joe@manyfish.co.uk> + + * string-tests.c (concat2, concat3): New ne_buffer_concat tests. + +Sat Dec 1 18:35:29 2001 Joe Orton <joe@manyfish.co.uk> + + * utils.c (any_request): Don't set the error context. + +Sat Dec 1 12:21:48 2001 Joe Orton <joe@manyfish.co.uk> + + * auth.c (retry_failure, retry_fail_cb, retry_fail_serve): New + functions. + +Tue Nov 27 21:24:22 2001 Joe Orton <joe@manyfish.co.uk> + + * request.c (s_progress, provide_progress, send_progress): New + functions. + +Sun Nov 18 19:11:23 2001 Joe Orton <joe@manyfish.co.uk> + + * auth.c (send_response): New function. (auth_serve): Simplify + using send_response. (retry_serve, retry_cb, retries): New + functions. + +Sat Nov 17 22:32:29 2001 Joe Orton <joe@manyfish.co.uk> + + * auth.c (auth_serve, basic): Simplify, use a persistent + connection and any_request() to work with --disable-dav builds. + +Sat Nov 17 22:30:43 2001 Joe Orton <joe@manyfish.co.uk> + + * utils.c (any_request): New function. + +Sun Oct 28 19:38:05 2001 Joe Orton <joe@manyfish.co.uk> + + * Makefile.in: Use explicit link rules. + +Fri Oct 26 20:08:33 2001 Joe Orton <joe@manyfish.co.uk> + + * request.c (persist_timeout): Test behaviour when connection + closes after between 1 and 10 requests. + +Fri Oct 26 20:04:27 2001 Joe Orton <joe@manyfish.co.uk> + + * utils.c (many_serve_string): New function. + +Sun Oct 7 17:48:53 2001 Joe Orton <joe@manyfish.co.uk> + + * utils.c: New file. + + * request.c (single_serve_string): Moved to utils.c. + + * Makefile.in: Link utils.o into all libtest.a. Move libtest.a + into this directory. + +Sun Oct 7 15:01:47 2001 Joe Orton <joe@manyfish.co.uk> + + * request.c (persist, persist_timeout, serve_twice, is_alive): New + functions. (closed_connection): Avoid race condition. + +Sat Oct 6 14:33:42 2001 Joe Orton <joe@manyfish.co.uk> + + * request.c (prepare_request, finish_request): Renamed from + make_request, destroy_request. (skip_interim_1xx, skip_many_1xx, + skip_1xx_hdrs): New functions. + +Wed Oct 3 00:03:33 2001 Joe Orton <joe@manyfish.co.uk> + + * request.c (fail_request): Optionally include a request body, and + optionally presume the server runs "forever". (all callers + changed). (serve_close, closed_connection): New function. + +Sat Sep 29 14:08:16 2001 Joe Orton <joe@manyfish.co.uk> + + * compress.c (fetch): Update for new decompression API. + +Sat Sep 29 11:21:56 2001 Joe Orton <joe@manyfish.co.uk> + + * compress.c: New file. + + * Makefile.in: Build compress test, and some its helpers. Add + -lneon to LIBS, and pick up NEON_CFLAGS. + +Thu Sep 27 20:31:51 2001 Joe Orton <joe@manyfish.co.uk> + + * utils.h: New file. + + * request.c: Moved ONREQ() into utils.h + +Mon Aug 27 00:34:56 2001 Joe Orton <joe@manyfish.co.uk> + + * regress.c: New file. + +Mon Aug 27 00:33:13 2001 Joe Orton <joe@manyfish.co.uk> + + * request.c (discard_request): Moved into common/child.c. + (make_request, destroy_request): Convenience functions. + (serve_non_http, not_http): New test. + +Sun Jun 24 22:15:46 2001 Joe Orton <joe@manyfish.co.uk> + + * test.[ch], child.[ch]: Moved into 'common' subdir. + + * Makefile.in: Updated likewise. + +Tue Jun 19 22:00:06 2001 Joe Orton <joe@manyfish.co.uk> + + * util-tests.c (parse_dates): Test date parsers. + +Sun Jun 10 17:36:11 2001 Joe Orton <joe@manyfish.co.uk> + + * request.c (infinite_headers, unbounded_headers): New test. + +Sun Jun 10 16:38:53 2001 Joe Orton <joe@manyfish.co.uk> + + * child.c [HAVE_PIPE]: Use a pipe between child and parent to know + when the child is ready to accept connections. Avoids boring + sleep()ing. + +Fri Jun 8 21:19:35 2001 Joe Orton <joe@manyfish.co.uk> + + * tests.c (segv, main): Remove SEGV handler in favour of useful + core dumps. + +Mon Jun 4 01:15:52 2001 Joe Orton <joe@manyfish.co.uk> + + * child.c (server_socket): Set socket family correctly. + +Thu May 31 08:58:41 2001 Joe Orton <joe@manyfish.co.uk> + + * util-tests.c (md5_alignment): New test for MD5 alignment issue + on Sparc. + +Thu May 31 00:40:43 2001 Joe Orton <joe@manyfish.co.uk> + + * child.c (minisleep): Just sleep for a second anyway. + +Thu May 31 00:19:16 2001 Joe Orton <joe@manyfish.co.uk> + + * child.c (server_socket): Casts for bind and setsockopt arguments. + +Thu May 31 00:02:21 2001 Joe Orton <joe@manyfish.co.uk> + + * request.c (send_bodies): Test callback-provided request bodies. + +Wed May 30 22:37:08 2001 Joe Orton <joe@manyfish.co.uk> + + * tests.c (child_segv): New function. (in_child): Install + different SEGV handler. (segv): Sleep so the re-raised SEGV + signal gets handled and we dump core. + +Wed May 30 19:24:32 2001 Joe Orton <joe@manyfish.co.uk> + + * request.c (send_bodies): New test for sending request bodies. + +Wed May 16 21:19:49 2001 Joe Orton <joe@manyfish.co.uk> + + * request.c (expect_response): Renamed, fold together + single_request and do_get_request. (all callers changed) + +Wed May 16 20:59:19 2001 Joe Orton <joe@manyfish.co.uk> + + * request.c (construct_get, run_request): New functions. + (fold_headers, fold_many_headers, multi_header): New tests. + +Sat May 12 17:37:36 2001 Joe Orton <joe@manyfish.co.uk> + + * server.c: Renamed from http-tests.c. + +Sat May 12 17:35:05 2001 Joe Orton <joe@manyfish.co.uk> + + * child.c (minisleep): New function. (spawn_server, reap_server): + New functions. (server_child): Call in_child. + +Sat May 12 17:33:57 2001 Joe Orton <joe@manyfish.co.uk> + + * tests.c (main): Open two log files for debugging messages. + (in_child): Switch to debug using child log. + +Sat May 12 11:18:18 2001 Joe Orton <joe@manyfish.co.uk> + + * tests.c (main): Call sock_init. (segv): Re-raise SEGV signal + after printing message. + +Mon May 7 10:38:50 2001 Joe Orton <joe@manyfish.co.uk> + + * request.c (chunk_syntax_1, chunk_syntax_2, chunk_syntax_3, + chunk_syntax_4, chunk_syntax_5): Split down from chunk_syntax. + +Mon May 7 10:37:38 2001 Joe Orton <joe@manyfish.co.uk> + + * util-tests.c (base64): Update for ne_base64() changes. Add + tests for binary data. + +Sun May 6 23:55:36 2001 Joe Orton <joe@manyfish.co.uk> + + * tests.h (ON): Use global buffer 'on_err_buf'. Make 'name' + variable public. + +Sun May 6 23:53:06 2001 Joe Orton <joe@manyfish.co.uk> + + * request.c (single_serve_string): General version of + single_serve_*. (single_request): Pass in expected response body. + (single_get_*): Use new single_request/single_serve_string. + (chunk_syntax): Add some tests for chunk syntax. + +Sun May 6 22:29:36 2001 Joe Orton <joe@manyfish.co.uk> + + * child.c, child.h: New files, split down from request.c. + +Sun May 6 21:53:28 2001 Joe Orton <joe@manyfish.co.uk> + + * request.c (spawn_server): Sleep for a while to let the server + get going. (do_request): Use passed parameters when creating + request. + +Sun May 6 21:34:27 2001 Joe Orton <joe@manyfish.co.uk> + + * request.c (spawn_server): Use callback to handle the server side + of connection. (single_request): New function. (single_get_eof, + single_get_clength, single_get_chunked): New functions. + (reap_server): New function. + +Sun May 6 20:02:32 2001 Joe Orton <joe@manyfish.co.uk> + + * request.c: New file. + +Wed May 2 12:08:53 2001 Joe Orton <joe@manyfish.co.uk> + + * string-tests.c (token1, token2, nulls, empty, quoted, badquotes, + shave, combo): New tests for ne_token and ne_shave. + +Wed May 2 12:04:52 2001 Joe Orton <joe@manyfish.co.uk> + + * string-tests.c: Updated for sbuffer -> ne_buffer changes. + +Wed May 2 01:08:45 2001 Joe Orton <joe@manyfish.co.uk> + + * Makefile.in (check): Alias for test goal. + +Wed May 2 01:08:36 2001 Joe Orton <joe@manyfish.co.uk> + + * tests.c (segv): Disable SEGV handler once handling it. + +Sun Apr 29 14:57:59 2001 Joe Orton <joe@manyfish.co.uk> + + * uri-tests.c (slash): Check behaviour of passing zero-length URI. + +Sun Apr 29 13:43:59 2001 Joe Orton <joe@manyfish.co.uk> + + * Makefile.in (clean): New target. (libtest.a): Depend on libneon + to force rebuilds when necessary. (all): Build but don't test. + +Sun Apr 29 13:41:13 2001 Joe Orton <joe@manyfish.co.uk> + + * util-tests.c: Add status line with leading garbage. + +Sun Apr 29 13:39:53 2001 Joe Orton <joe@manyfish.co.uk> + + * util-tests.c (status_lines): Add some tests for invalid status + lines too. + +Sun Apr 29 13:38:31 2001 Joe Orton <joe@manyfish.co.uk> + + * tests.c (main): Use basename(argv[0]) as suite name. Fail if no + tests are in the functions vector. + +Sun Apr 29 11:06:45 2001 Joe Orton <joe@manyfish.co.uk> + + * tests.c (segv): New function. (main): Add SIGSEGV handler. + +Fri Apr 27 00:00:12 2001 Joe Orton <joe@manyfish.co.uk> + + * util-tests.c (base64): New test. + +Thu Apr 26 22:39:44 2001 Joe Orton <joe@manyfish.co.uk> + + * uri-tests.c (just_hostname, just_path, null_uri): New tests. + +Thu Apr 26 22:03:58 2001 Joe Orton <joe@manyfish.co.uk> + + * util-tests.c (md5): Test of MD5 functions. + +Mon Apr 23 23:08:02 2001 Joe Orton <joe@manyfish.co.uk> + + * http-tests.c (simple_head): Add HEAD test. + +Mon Apr 23 22:49:52 2001 Joe Orton <joe@manyfish.co.uk> + + * http-tests.c (simple_get): Check for EOF after reading response + body of HTTP/1.0 GET request. + + (null_resource): New function, test for 404 on null resource. + + diff --git a/test/Makefile.in b/test/Makefile.in new file mode 100644 index 0000000..eb0da59 --- /dev/null +++ b/test/Makefile.in @@ -0,0 +1,171 @@ +# Makefile for neon test suite. + +SHELL = @SHELL@ +CPPFLAGS = @CPPFLAGS@ -I. -I$(top_srcdir)/src -I$(top_srcdir)/test/common +CFLAGS = @CFLAGS@ @NEON_CFLAGS@ +LDFLAGS = -L. @LDFLAGS@ -L$(top_srcdir)/src -L../src/.libs +DEFS = @DEFS@ + +top_builddir = .. +top_srcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +AR = ar + +RANLIB = @RANLIB@ +LIBS = -ltest -lneon @NEON_LIBS@ +CC = @CC@ +OPENSSL = @OPENSSL@ + +HELPERS = @HELPERS@ +BASIC_TESTS = uri-tests util-tests string-tests session socket \ + request auth basic stubs redirect +ZLIB_TESTS = compress +ZLIB_HELPERS = file1.gz file2.gz trailing.gz badcsum.gz truncated.gz +DAV_TESTS = xml acl props lock +SSL_TESTS = socket-ssl ssl +SSL_HELPERS = ca-stamp +TESTS = @TESTS@ +HDRS = common/tests.h common/child.h utils.h $(top_builddir)/config.h +VALGRIND = valgrind --gdb-attach=yes --leak-check=yes + +LIBTEST = libtest.a + +# By default, compile but don't run the tests. +all: $(TESTS) + +clean: + rm -f $(TESTS) $(HELPERS) *.o common/*.o libtest.a *.log + rm -rf ca + rm -f ca-stamp client.key *.csr ssigned.pem wrongcn.pem \ + server.cert client.cert client.p12 + +check: $(TESTS) $(HELPERS) + @SRCDIR=$(srcdir) $(SHELL) $(srcdir)/run.sh $(TESTS) + +grind: $(TESTS) $(HELPERS) + @SRCDIR=$(srcdir) HARNESS="$(VALGRIND)" $(SHELL) $(srcdir)/run.sh $(TESTS) + +file1.gz: ../NEWS + gzip -c --no-name $(top_srcdir)/NEWS > $@ + +file2.gz: ../NEWS + gzip -c --name $(top_srcdir)/NEWS > $@ + +# gzip file with trailing bytes. +trailing.gz: ../NEWS + gzip -c --no-name $(top_srcdir)/NEWS > $@ + echo "hello, world" >> $@ + +truncated.gz: file1.gz + dd if=file1.gz of=$@ bs=2048 count=2 + +badcsum.gz: file1.gz + dd of=$@ if=file1.gz bs=1 count=`perl -e 'printf "%d", (stat("file1.gz"))[7] - 8;'` + echo 'broken!' >> $@ + +# Dummy target to create the CA keys etc. makekeys stderr is redirected +# since it changes for every invocation; not helpful for regression +# testing. +ca-stamp: $(srcdir)/makekeys.sh $(srcdir)/openssl.conf + rm -rf ca + OPENSSL=$(OPENSSL) \ + $(SHELL) $(srcdir)/makekeys.sh $(srcdir) 2>makekeys.out + @echo timestamp > ca-stamp + +Makefile: $(srcdir)/Makefile.in + cd .. && ./config.status test/Makefile + +LIBOBJS = common/tests.o common/child.o utils.o + +$(LIBTEST): $(LIBOBJS) $(HDRS) ../src/@NEON_TARGET@ + $(AR) cru $@ $(LIBOBJS) + $(RANLIB) $@ + +.c.o: + $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ + +uri-tests: uri-tests.o $(LIBTEST) + $(CC) $(LDFLAGS) $(CFLAGS) -o $@ uri-tests.o $(LIBS) + +request: request.o $(LIBTEST) + $(CC) $(LDFLAGS) -o $@ request.o $(LIBS) + +string-tests: string-tests.o $(LIBTEST) + $(CC) $(LDFLAGS) -o $@ string-tests.o $(LIBS) + +socket: socket.o $(LIBTEST) + $(CC) $(LDFLAGS) -o $@ socket.o $(LIBS) + +compress: compress.o $(LIBTEST) + $(CC) $(LDFLAGS) -o $@ compress.o $(LIBS) + +acl: acl.o $(LIBTEST) + $(CC) $(LDFLAGS) -o $@ acl.o $(LIBS) + +utils: utils.o $(LIBTEST) + $(CC) $(LDFLAGS) -o $@ utils.o $(LIBS) + +util-tests: util-tests.o $(LIBTEST) + $(CC) $(LDFLAGS) -o $@ util-tests.o $(LIBS) + +auth: auth.o $(LIBTEST) + $(CC) $(LDFLAGS) -o $@ auth.o $(LIBS) + +lock: lock.o $(LIBTEST) + $(CC) $(LDFLAGS) -o $@ lock.o $(LIBS) + +basic: basic.o $(LIBTEST) + $(CC) $(LDFLAGS) -o $@ basic.o $(LIBS) + +# Recompile socket.c with SOCKET_SSL defined +socket-ssl.o: $(srcdir)/socket.c $(HDRS) + $(CC) -DSOCKET_SSL $(CPPFLAGS) $(CFLAGS) -c $(srcdir)/socket.c -o $@ + +socket-ssl: socket-ssl.o $(LIBTEST) + $(CC) $(LDFLAGS) -o $@ socket-ssl.o $(LIBS) + +ssl: ssl.o $(LIBTEST) + $(CC) $(LDFLAGS) -o $@ ssl.o $(LIBS) + +xml: xml.o $(LIBTEST) + $(CC) $(LDFLAGS) -o $@ xml.o $(LIBS) + +stubs: stubs.o $(LIBTEST) + $(CC) $(LDFLAGS) -o $@ stubs.o $(LIBS) + +redirect: redirect.o $(LIBTEST) + $(CC) $(LDFLAGS) -o $@ redirect.o $(LIBS) + +session: session.o $(LIBTEST) + $(CC) $(LDFLAGS) -o $@ session.o $(LIBS) + +cookies: cookies.o $(LIBTEST) + $(CC) $(LDFLAGS) -o $@ cookies.o $(LIBS) + +props: props.o $(LIBTEST) + $(CC) $(LDFLAGS) -o $@ props.o $(LIBS) + +resolve: resolve.o $(LIBTEST) + $(CC) $(LDFLAGS) -o $@ resolve.o $(LIBS) + +#FOO: FOO.o $(LIBTEST) +# $(CC) $(LDFLAGS) -o $@ FOO.o $(LIBS) + +auth.o: auth.c $(HDRS) +uri-tests.o: uri-tests.c $(HDRS) +util-tests.o: util-tests.c $(HDRS) +string-tests.o: string-tests.c $(HDRS) +socket.o: socket.c $(HDRS) +server.o: server.c $(HDRS) +request.o: request.c $(HDRS) +regress.o: regress.c $(HDRS) +compress.o: compress.c $(HDRS) +acl.o: acl.c $(HDRS) +utils.o: utils.c $(HDRS) +stubs.o: stubs.c $(HDRS) +props.o: props.c $(HDRS) +session.o: session.c $(HDRS) +redirect.o: redirect.c $(HDRS) +basic.o: basic.c $(HDRS) diff --git a/test/README b/test/README new file mode 100644 index 0000000..d8f6528 --- /dev/null +++ b/test/README @@ -0,0 +1,39 @@ + +Stupidly Simple Test Suite for neon +----------------------------------- + +The aim of the test suite is two-fold: + + 1. ensure compliance to the relevant RFCs in network behaviour. + + 2. ensure that the promises made by the public API are met + by the current implementation. + +The file `STATUS' makes an attempt at listing RFC requirements and how +the test suite tests whether neon meets them or not (it's not finished +yet). + +The test suite is licensed under the GPL. + +Important Note About Test Failures +---------------------------------- + +Note that a test failure either means a bug in the test or a bug in +the code itself. On platforms without pipe(), there is a race +condition in the code which forks a server process: if you get random +failures on a slow or loaded box, increase the sleep time in +common/child.c:minisleep(). + +Extra Stuff +----------- + +server-tests requires that you have a running HTTP server on localhost +port 80, and you have copied htdocs/* to server-htdocs-root/test/* + +Credits +------- + +This test suite is inspired by the Subversion project, discussion on +the subversion mailing list, and seeing chromatic's talks on XP. The +presentation is inspired by the standard Perl test suite. Imitation +is the greatest form of flattery, right? diff --git a/test/STATUS b/test/STATUS new file mode 100644 index 0000000..a5483ef --- /dev/null +++ b/test/STATUS @@ -0,0 +1,74 @@ + -*- text -*- + +This document attempts to list RFC requirements and determine whether +neon meets them, or where they do not apply, etc. + + Yes: test written, succeeds + No: test written, but currently fails + ???: no test written + ---: feature not supported + App: this is an application issue not a neon issue + + RFC2616 + ======= + +3.1: MUST treat major/minor as separate digits Yes +3.1: MUST ignore leading zeros Yes +3.1: MUST only send HTTP/1.1 when appropriate ??? + +3.2.2: MUST use abs_path of "/" in Request-URI App +3.2.3: comparisons of host names MUST be case-insensitive Yes + comparisons of scheme names MUST be ... Yes + comparison of empty abs_path equivalent to "/" No/--- + +3.3.1: MUST accept three date formats App/Yes [2] + MUST only generate RFC1123-style dates App + +3.3.1: MUST use GMT for http-dates ??? + MUST assume GMT when parsing asctime dates ??? + +3.4.1: MUST respect charset label provided Yes/App + +3.5*: content codings App + +3.6: MUST requirements for multiple transfer-codings --- [4] + +3.6.1: parsing of chunked transfer coding Yes + MUST be able to handle "chunked" transfer-coding Yes + MUST ignore unknown chunk-extension extensions Yes + +3.7: parsing of Content-Type headers Yes + +3.7: MUST NOT have LWS between type/subtype in C-T hdr App + SHOULD only send parameters to "new HTTP apps" (>1.0?) App + +3.7.1: MUST represent HTTP message in canonical form App + MUST accept CRLF/CR/LF as line-breaks in text/* media App + MUST NOT use only CR or LF in HTTP control structures ??? + MUST specify charset if not ISO-8859-1 App + +3.7.2: multipart types --- + +3.8: SHOULD have short product token Yes/App [5] + SHOULD use product-version for version identifier Yes/App + only product-version differs between versions Yes/App + +3.9: Content Negotiation ---/App + +3.10: Language Tags ---/App + +3.11: Entity Tags ---/App + + + + + +[2]: date parser is provided which handles all three formats, but no +handling of the Date header is present within neon. + +[3]: not sure if neon should be handling of this internally. + +[4]: neon only supports using just chunked Transfer-Coding or none. + +[5]: these reflect that applications may add their own product tokens + alongside neon's. diff --git a/test/acl.c b/test/acl.c new file mode 100644 index 0000000..14008b1 --- /dev/null +++ b/test/acl.c @@ -0,0 +1,115 @@ +/* + Dummy ACL tests + Copyright (C) 2001-2003, Joe Orton <joe@manyfish.co.uk> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "ne_acl.h" + +#include "tests.h" +#include "child.h" +#include "utils.h" + +#ifdef NEON_NODAV + +static int skip(void) +{ + t_context("built without WebDAV support"); + return SKIP; +} + +ne_test tests[] = { T(skip), T(NULL) }; + +#else + +/**** DUMMY TESTS: just makes sure the stuff doesn't dump core. */ + +static int test_acl(const char *uri, ne_acl_entry *es, int nume) +{ + ne_session *sess; + + CALL(make_session(&sess, single_serve_string, + "HTTP/1.1 200 OK\r\n" + "Connection: close\r\n\r\n")); + + ON(ne_acl_set(sess, uri, es, nume)); + + CALL(await_server()); + ne_session_destroy(sess); + + return OK; +} + +static int grant_all(void) +{ + ne_acl_entry e = {0}; + + e.apply = ne_acl_all; + e.type = ne_acl_grant; + + CALL(test_acl("/foo", &e, 1)); + + return OK; +} + +static int deny_all(void) +{ + ne_acl_entry e = {0}; + + e.apply = ne_acl_all; + e.type = ne_acl_deny; + + CALL(test_acl("/foo", &e, 1)); + + return OK; +} + +static int deny_one(void) +{ + ne_acl_entry e = {0}; + + e.apply = ne_acl_href; + e.type = ne_acl_deny; + e.principal = "http://webdav.org/users/joe"; + + CALL(test_acl("/foo", &e, 1)); + + return OK; +} + +static int deny_byprop(void) +{ + ne_acl_entry e = {0}; + + e.apply = ne_acl_property; + e.type = ne_acl_deny; + e.principal = "owner"; + + CALL(test_acl("/foo", &e, 1)); + + return OK; +} + +ne_test tests[] = { + T(grant_all), + T(deny_all), + T(deny_one), + T(deny_byprop), + T(NULL) +}; + +#endif /* NEON_NODAV */ diff --git a/test/auth.c b/test/auth.c new file mode 100644 index 0000000..30fe88e --- /dev/null +++ b/test/auth.c @@ -0,0 +1,315 @@ +/* + Authentication tests + Copyright (C) 2001-2003, Joe Orton <joe@manyfish.co.uk> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "config.h" + +#include <sys/types.h> + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "ne_request.h" +#include "ne_auth.h" +#include "ne_basic.h" + +#include "tests.h" +#include "child.h" +#include "utils.h" + +static const char username[] = "Aladdin", password[] = "open sesame"; +static int auth_failed; + +static const char www_wally[] = "WWW-Authenticate: Basic realm=WallyWorld"; + +static int auth_cb(void *userdata, const char *realm, int tries, + char *un, char *pw) +{ + strcpy(un, username); + strcpy(pw, password); + return tries; +} + +static void auth_hdr(char *value) +{ +#define B "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" + auth_failed = strcmp(value, B); + NE_DEBUG(NE_DBG_HTTP, "Got auth header: [%s]\nWanted header: [%s]\n" + "Result: %d\n", value, B, auth_failed); +#undef B +} + +/* Sends a response with given response-code. If hdr is not NULL, + * sends that header string too (appending an EOL). If eoc is + * non-zero, request must be last sent down a connection; otherwise, + * clength 0 is sent to maintain a persistent connection. */ +static int send_response(ne_socket *sock, const char *hdr, int code, int eoc) +{ + char buffer[BUFSIZ]; + + sprintf(buffer, "HTTP/1.1 %d Blah Blah" EOL, code); + + if (hdr) { + strcat(buffer, hdr); + strcat(buffer, EOL); + } + + if (eoc) { + strcat(buffer, "Connection: close" EOL EOL); + } else { + strcat(buffer, "Content-Length: 0" EOL EOL); + } + + return SEND_STRING(sock, buffer); +} + +/* Server function which sends two responses: first requires auth, + * second doesn't. */ +static int auth_serve(ne_socket *sock, void *userdata) +{ + auth_failed = 1; + + /* Register globals for discard_request. */ + got_header = auth_hdr; + want_header = "Authorization"; + + discard_request(sock); + send_response(sock, www_wally, 401, 0); + + discard_request(sock); + send_response(sock, NULL, auth_failed?500:200, 1); + + return 0; +} + +static int basic(void) +{ + ne_session *sess; + + CALL(make_session(&sess, auth_serve, NULL)); + ne_set_server_auth(sess, auth_cb, NULL); + + CALL(any_2xx_request(sess, "/norman")); + + ne_session_destroy(sess); + CALL(await_server()); + return OK; +} + +static int retry_serve(ne_socket *sock, void *ud) +{ + discard_request(sock); + send_response(sock, www_wally, 401, 0); + + discard_request(sock); + send_response(sock, www_wally, 401, 0); + + discard_request(sock); + send_response(sock, NULL, 200, 0); + + discard_request(sock); + send_response(sock, www_wally, 401, 0); + + discard_request(sock); + send_response(sock, NULL, 200, 0); + + discard_request(sock); + send_response(sock, NULL, 200, 0); + + discard_request(sock); + send_response(sock, NULL, 200, 0); + + discard_request(sock); + send_response(sock, www_wally, 401, 0); + + discard_request(sock); + send_response(sock, NULL, 200, 0); + + discard_request(sock); + send_response(sock, www_wally, 401, 0); + + discard_request(sock); + send_response(sock, www_wally, 401, 0); + + discard_request(sock); + send_response(sock, www_wally, 401, 0); + + discard_request(sock); + send_response(sock, NULL, 200, 0); + + return OK; +} + +static int retry_cb(void *userdata, const char *realm, int tries, + char *un, char *pw) +{ + int *count = userdata; + + /* dummy creds; server ignores them anyway. */ + strcpy(un, "a"); + strcpy(pw, "b"); + + switch (*count) { + case 0: + case 1: + if (tries == *count) { + *count += 1; + return 0; + } else { + t_context("On request #%d, got attempt #%d", *count, tries); + *count = -1; + return 1; + } + break; + case 2: + case 3: + /* server fails a subsequent request, check that tries has + * reset to zero. */ + if (tries == 0) { + *count += 1; + return 0; + } else { + t_context("On retry after failure #%d, tries was %d", + *count, tries); + *count = -1; + return 1; + } + break; + case 4: + case 5: + if (tries > 1) { + t_context("Attempt counter reached #%d", tries); + *count = -1; + return 1; + } + return tries; + default: + t_context("Count reached %d!?", *count); + *count = -1; + } + return 1; +} + +/* Test that auth retries are working correctly. */ +static int retries(void) +{ + ne_session *sess; + int count = 0; + + CALL(make_session(&sess, retry_serve, NULL)); + + ne_set_server_auth(sess, retry_cb, &count); + + /* This request will be 401'ed twice, then succeed. */ + ONREQ(any_request(sess, "/foo")); + + /* auth_cb will have set up context. */ + CALL(count != 2); + + /* this request will be 401'ed once, then succeed. */ + ONREQ(any_request(sess, "/foo")); + + /* auth_cb will have set up context. */ + CALL(count != 3); + + /* some 20x requests. */ + ONREQ(any_request(sess, "/foo")); + ONREQ(any_request(sess, "/foo")); + + /* this request will be 401'ed once, then succeed. */ + ONREQ(any_request(sess, "/foo")); + + /* auth_cb will have set up context. */ + CALL(count != 4); + + /* First request is 401'ed by the server at both attempts. */ + ONV(any_request(sess, "/foo") != NE_AUTH, + ("auth succeeded, should have failed: %s", ne_get_error(sess))); + + count++; + + /* Second request is 401'ed first time, then will succeed if + * retried. 0.18.0 didn't reset the attempt counter though so + * this didn't work. */ + ONV(any_request(sess, "/foo") == NE_AUTH, + ("auth failed on second try, should have succeeded: %s", ne_get_error(sess))); + + ne_session_destroy(sess); + + CALL(await_server()); + + return OK; +} + +/* crashes with neon <0.22 */ +static int forget_regress(void) +{ + ne_session *sess = ne_session_create("http", "localhost", 7777); + ne_forget_auth(sess); + ne_session_destroy(sess); + return OK; +} + +static int fail_auth_cb(void *ud, const char *realm, int attempt, + char *un, char *pw) +{ + return 1; +} + +/* this may trigger a segfault in neon 0.21.x and earlier. */ +static int tunnel_regress(void) +{ + ne_session *sess = ne_session_create("https", "localhost", 443); + ne_session_proxy(sess, "localhost", 7777); + ne_set_server_auth(sess, fail_auth_cb, NULL); + CALL(spawn_server(7777, single_serve_string, + "HTTP/1.1 401 Auth failed.\r\n" + "WWW-Authenticate: Basic realm=asda\r\n" + "Content-Length: 0\r\n\r\n")); + any_request(sess, "/foo"); + ne_session_destroy(sess); + CALL(await_server()); + return OK; +} + +/* test digest auth 2068-style. */ + +/* test digest auth 2617-style. */ + +/* negotiation: within a single header, multiple headers. + * check digest has precedence */ + +/* test auth-int, auth-int FAILURE. chunk trailers/non-trailer */ + +/* test logout */ + +/* proxy auth, proxy AND origin */ + +ne_test tests[] = { + T(lookup_localhost), + T(basic), + T(retries), + T(forget_regress), + T(tunnel_regress), + T(NULL) +}; diff --git a/test/basic.c b/test/basic.c new file mode 100644 index 0000000..15f5c8a --- /dev/null +++ b/test/basic.c @@ -0,0 +1,228 @@ +/* + Tests for high-level HTTP interface (ne_basic.h) + Copyright (C) 2002-2003, Joe Orton <joe@manyfish.co.uk> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "config.h" + +#include <sys/types.h> + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include <fcntl.h> + +#include "ne_basic.h" + +#include "tests.h" +#include "child.h" +#include "utils.h" + +static int content_type(void) +{ + int n; + static const struct { + const char *value, *type, *subtype, *charset; + } ctypes[] = { + { "foo/bar", "foo", "bar", NULL }, + { "foo/bar ", "foo", "bar", NULL }, + { "application/xml", "application", "xml", NULL }, + /* text/ subtypes default to charset ISO-8859-1. */ + { "text/lemon", "text", "lemon", "ISO-8859-1" }, +#undef TXU +#define TXU "text", "xml", "utf-8" + /* 2616 doesn't *say* that charset can be quoted, but bets are + * that some servers do it anyway. */ + { "text/xml; charset=utf-8", TXU }, + { "text/xml; charset=utf-8; foo=bar", TXU }, + { "text/xml;charset=utf-8", TXU }, + { "text/xml ;charset=utf-8", TXU }, + { "text/xml;charset=utf-8;foo=bar", TXU }, + { "text/xml; foo=bar; charset=utf-8", TXU }, + { "text/xml; foo=bar; charset=utf-8; bar=foo", TXU }, + { "text/xml; charset=\"utf-8\"", TXU }, + { "text/xml; charset='utf-8'", TXU }, + { "text/xml; foo=bar; charset=\"utf-8\"; bar=foo", TXU }, +#undef TXU + /* badly quoted charset should come out as NULL */ + { "foo/lemon; charset=\"utf-8", "foo", "lemon", NULL }, + { NULL } + }; + + for (n = 0; ctypes[n].value != NULL; n++) { + ne_content_type ct = {0}; + + ne_content_type_handler(&ct, ctypes[n].value); + + ONV(strcmp(ct.type, ctypes[n].type), + ("for `%s': type was `%s'", ctypes[n].value, ct.type)); + + ONV(strcmp(ct.subtype, ctypes[n].subtype), + ("for `%s': subtype was `%s'", ctypes[n].value, ct.subtype)); + + ONV(ctypes[n].charset && ct.charset == NULL, + ("for `%s': charset unset", ctypes[n].value)); + + ONV(ctypes[n].charset == NULL && ct.charset != NULL, + ("for `%s': unexpected charset `%s'", ctypes[n].value, + ct.charset)); + + ONV(ctypes[n].charset && ct.charset && + strcmp(ctypes[n].charset, ct.charset), + ("for `%s': charset was `%s'", ctypes[n].value, ct.charset)); + + NE_FREE(ct.value); + } + + return OK; +} + +/* Do ranged GET for range 'start' to 'end'; with 'resp' as response. + * If 'fail' is non-NULL, expect ne_get_range to fail, and fail the + * test with given message if it doesn't. */ +static int do_range(off_t start, off_t end, const char *fail, + char *resp) +{ + ne_session *sess; + ne_content_range range = {0}; + int fd, ret; + + CALL(make_session(&sess, single_serve_string, resp)); + + range.start = start; + range.end = end; + + fd = open("/dev/null", O_WRONLY); + + ret = ne_get_range(sess, "/foo", &range, fd); + + close(fd); + CALL(await_server()); + + if (fail) { +#if 0 + t_warning("error was %s", ne_get_error(sess)); +#endif + ONN(fail, ret == NE_OK); + } else { + ONREQ(ret); + } + + ne_session_destroy(sess); + return OK; +} + +static int get_range(void) +{ + return do_range(1, 10, NULL, + "HTTP/1.1 206 Widgets\r\n" "Connection: close\r\n" + "Content-Range: bytes 1-10\r\n" + "Content-Length: 10\r\n\r\nabcdefghij"); +} + +static int fail_range_length(void) +{ + return do_range(1, 10, "range response length mismatch should fail", + "HTTP/1.1 206 Widgets\r\n" "Connection: close\r\n" + "Content-Range: bytes 1-2\r\n" + "Content-Length: 2\r\n\r\nab"); +} + +static int fail_range_units(void) +{ + return do_range(1, 2, "range response units check should fail", + "HTTP/1.1 206 Widgets\r\n" "Connection: close\r\n" + "Content-Range: fish 1-2\r\n" + "Content-Length: 2\r\n\r\nab"); +} + +static int fail_range_notrange(void) +{ + return do_range(1, 2, "non-ranged response should fail", + "HTTP/1.1 200 Widgets\r\n" "Connection: close\r\n" + "Content-Range: bytes 1-2\r\n" + "Content-Length: 2\r\n\r\nab"); +} + +static int fail_range_unsatify(void) +{ + return do_range(1, 2, "unsatisfiable range should fail", + "HTTP/1.1 416 No Go\r\n" "Connection: close\r\n" + "Content-Length: 2\r\n\r\nab"); +} + +static int dav_capabilities(void) +{ + static const struct { + const char *hdrs; + unsigned int class1, class2, exec; + } caps[] = { + { "DAV: 1,2\r\n", 1, 1, 0 }, + { "DAV: 1 2\r\n", 0, 0, 0 }, + /* these aren't strictly legal DAV: headers: */ + { "DAV: 2,1\r\n", 1, 1, 0 }, + { "DAV: 1, 2 \r\n", 1, 1, 0 }, + { "DAV: 1\r\nDAV:2\r\n", 1, 1, 0 }, + { NULL, 0, 0, 0 } + }; + char resp[BUFSIZ]; + int n; + + for (n = 0; caps[n].hdrs != NULL; n++) { + ne_server_capabilities c = {0}; + ne_session *sess; + + ne_snprintf(resp, BUFSIZ, "HTTP/1.0 200 OK\r\n" + "Connection: close\r\n" + "%s" "\r\n", caps[n].hdrs); + + CALL(make_session(&sess, single_serve_string, resp)); + + ONREQ(ne_options(sess, "/foo", &c)); + + ONV(c.dav_class1 != caps[n].class1, + ("class1 was %d not %d", c.dav_class1, caps[n].class1)); + ONV(c.dav_class2 != caps[n].class2, + ("class2 was %d not %d", c.dav_class2, caps[n].class2)); + ONV(c.dav_executable != caps[n].exec, + ("class2 was %d not %d", c.dav_executable, caps[n].exec)); + + CALL(await_server()); + + ne_session_destroy(sess); + } + + return OK; +} + +ne_test tests[] = { + T(lookup_localhost), + T(content_type), + T(get_range), + T(fail_range_length), + T(fail_range_units), + T(fail_range_notrange), + T(fail_range_unsatify), + T(dav_capabilities), + T(NULL) +}; + diff --git a/test/common/ChangeLog b/test/common/ChangeLog new file mode 100644 index 0000000..11d9840 --- /dev/null +++ b/test/common/ChangeLog @@ -0,0 +1,222 @@ +Wed Jun 18 20:10:45 2003 Joe Orton <joe@manyfish.co.uk> + + * child.c (server_child, spawn_server_repeat): Adapt for new + socket API. + +Sun Mar 9 17:52:11 2003 Joe Orton <joe@manyfish.co.uk> + + * test.h (T_EXPECT_FAIL): New constant. + (T_XFAIL): New test function wrapper. + + * tests.c (main): Handle expected failures. + +Sat Mar 1 21:04:35 2003 Joe Orton <joe@manyfish.co.uk> + + Extend the ne_test structure with a 'flags' field which can + optionally request leak checking at run-time. + + * tests.h (ne_test): Add 'flags' field. + (T_CHECK_LEAKS): New flag. + (T): Use T_CHECK_LEAKS flag by default. + (T_LEAKY): Like T, but with no flags set. + + * tests.c (main) [NEON_MEMLEAK]: If leak checking is requested, + if a test passes, but leaks memory, fail the test. + +Wed Feb 26 21:52:15 2003 Joe Orton <joe@manyfish.co.uk> + + * tests.c (main): Test the "disable debugging" mode of + ne_debug_init, NE_DBG_FLUSH, and a ne_debug() with no output. + +Fri Aug 23 22:54:35 2002 Joe Orton <joe@manyfish.co.uk> + + * tests.c (main): Call ne_sock_init after ne_debug_init, so that + debugging messages are caught from ne_sock_init. Print a warning + message if ne_sock_init fails. + +Wed Aug 21 13:29:20 2002 Joe Orton <joe@manyfish.co.uk> + + * tests.h (ONCMP): New macro. + +Mon Aug 19 16:53:20 2002 Joe Orton <joe@manyfish.co.uk> + + * child.c (lookup_localhost): Just use inet_addr to resolve + 127.0.0.1. + +Sun Aug 18 13:50:30 2002 Joe Orton <joe@manyfish.co.uk> + + * tests.c (TEST_DEBUG): Add NE_DBG_SSL. + +Sat Aug 10 10:19:18 2002 Joe Orton <joe@manyfish.co.uk> + + * child.c (server_send): Fix declaration. + (discard_body): Use NE_FMT_SSIZE_T for print ssize_t's. + +Sat Jul 6 08:42:37 2002 Joe Orton <joe@manyfish.co.uk> + + * child.c (discard_body): New function. + +Sun Jun 30 10:26:33 2002 Joe Orton <joe@manyfish.co.uk> + + * child.c (server_send): New function. + (discard_request): Fail with appropriate error. + +Sun Jun 30 09:03:51 2002 Joe Orton <joe@manyfish.co.uk> + + * tests.c (main): Reap server after each test has run. + +Sun Jun 30 09:00:43 2002 Joe Orton <joe@manyfish.co.uk> + + * child.c (reap_server): Set `child' to 0 so child can't be reaped + twice. + +Sun Jun 23 12:09:09 2002 Joe Orton <joe@manyfish.co.uk> + + * child.c (serve_file): Use large buffer when sending in chunked + mode to support large chunk sizes. + +Sun Jun 23 09:35:09 2002 Joe Orton <joe@manyfish.co.uk> + + * child.c (serve_file): Use NE_FMT_OFF_T and NE_FMT_SSIZE_T. + +Thu May 30 21:57:39 2002 Joe Orton <joe@manyfish.co.uk> + + * child.c (minisleep): Export function. + +Sun May 19 18:23:19 2002 Joe Orton <joe@manyfish.co.uk> + + * child.c, tests.c: s/sock_/ne_sock_/, s/SOCK_/NE_SOCK_/ for + socket layer API change. + + * child.h (SEND_STRING): New macro. + +Sun May 19 08:57:21 2002 Joe Orton <joe@manyfish.co.uk> + + * child.c (lookup_hostname): Conditionally use hstrerror(). + +Mon Feb 25 20:54:56 2002 Joe Orton <joe@manyfish.co.uk> + + * tests.c (t_context): Use ne_vsnprintf. + +Mon Feb 11 21:52:23 2002 Joe Orton <joe@manyfish.co.uk> + + * child.c (lookup_hostname): New function. + (server_child, do_listen): Pass around struct in_addr argument. + (spawn_server_addr): Renamed from spawn_server, take bind_local + flag to use localhost or "real" hostname to bind to. + (spawn_server): New function, use spawn_server + +Mon Feb 11 20:51:27 2002 Joe Orton <joe@manyfish.co.uk> + + * child.c (minisleep) [HAVE_USLEEP]: Use nice short usleep() + rather than boring long sleep(). + +Sat Feb 2 14:15:25 2002 Joe Orton <joe@manyfish.co.uk> + + * child.c (spawn_server_repeat): Exit child process (with failure) + if the server callback fails. + +Fri Jan 4 22:06:17 2002 Joe Orton <joe@manyfish.co.uk> + + * tests.h: Add SKIPREST result value. + + * tests.c (TEST_DEBUG): Add NE_DBG_LOCKS. (main): Support + SKIPREST. + +Tue Jan 1 20:36:58 2002 Joe Orton <joe@manyfish.co.uk> + + * tests.h: Make test_suite symbol have external linkage. + +Sat Nov 10 22:28:55 2001 Joe Orton <joe@manyfish.co.uk> + + * tests.c, test.h: Export name of test suite. + +Sun Nov 4 13:56:42 2001 Joe Orton <joe@manyfish.co.uk> + + * child.c (discard_request): Support retrieving arbitrary header + values from request via want_request, got_request globals. + +Wed Oct 24 21:41:39 2001 Joe Orton <joe@manyfish.co.uk> + + * tests.h (ONV): New macro. (ON, ONN): Redefine in terms of ONV. + +Wed Oct 24 20:44:59 2001 Joe Orton <joe@manyfish.co.uk> + + * tests.c (main, t_warning, segv): Use colours when stdout is a + terminal device. + +Wed Oct 24 20:36:38 2001 Joe Orton <joe@manyfish.co.uk> + + * tests.c (t_context, t_warning): Renamed from i_am, warning. + (t_context): Take printf varargs. + + * tests.h (ONN, ON): Update, simplify. + + * child.c: Update. + +Tue Oct 23 22:15:17 2001 Joe Orton <joe@manyfish.co.uk> + + * tests.c (main): Vertically align results after warnings. + +Tue Oct 2 23:36:44 2001 Joe Orton <joe@manyfish.co.uk> + + * child.c (do_listen): Refactored from server_socket, only does + bind/listen. (spawn_server): Moved awaken/accept calls here. + (spawn_server_repeat, dead_server): New functions. + +Sun Sep 30 10:14:35 2001 Joe Orton <joe@manyfish.co.uk> + + * tests.h: Use a function/name structure for tests, add 'T' macro + for easily writing initializers. Rename 'name' global variable to + 'test_context' to avoid parameter name collisions. + + * child.c (spawn_server): Update accordingly. + + * tests.c (i_am): Update accordingly. (main): Update; prettify + output using new 'name' from test structure. Cope better when all + tests in a suite are skipped. + +Sat Sep 29 11:04:40 2001 Joe Orton <joe@manyfish.co.uk> + + * child.c (serve_file): If 'chunks' is set in argument object, + then deliver the file as a series of chunks. + +Thu Sep 27 20:28:45 2001 Joe Orton <joe@manyfish.co.uk> + + * child.c (serve_file): New function. + +Thu Sep 27 20:28:41 2001 Joe Orton <joe@manyfish.co.uk> + + * child.c (discard_request): Reset clength. + +Mon Aug 27 00:31:09 2001 Joe Orton <joe@manyfish.co.uk> + + * tests.c (test_num): Expose test counter. (segv): Handle + segfault nicely. + +Mon Aug 27 00:30:20 2001 Joe Orton <joe@manyfish.co.uk> + + * child.c (discard_request): New function, from request.c in + neon/test. + +Wed Aug 8 22:09:21 2001 Joe Orton <joe@manyfish.co.uk> + + * tests.c, test.h: Only define test_argc/argv once. + +Mon Jul 16 16:30:28 2001 Joe Orton <joe@manyfish.co.uk> + + * tests.c (warning): Take printf-style arguments list. + +Mon Jul 16 16:16:08 2001 Joe Orton <joe@manyfish.co.uk> + + * tests.c (main): Cope with skipped tests properly. + +Mon Jul 16 16:00:59 2001 Joe Orton <joe@manyfish.co.uk> + + * tests.c (warning): New function. (main): Cope with warnings. + +Sun Jul 8 16:09:33 2001 Joe Orton <joe@manyfish.co.uk> + + * tests.c (main): Export argc/argv as test_argc, test_argv. + + diff --git a/test/common/README b/test/common/README new file mode 100644 index 0000000..a910a23 --- /dev/null +++ b/test/common/README @@ -0,0 +1,4 @@ + +Simple test framework for neon; licensed under the GNU GPL. + +Copyright (C) 2001-2002 Joe Orton diff --git a/test/common/child.c b/test/common/child.c new file mode 100644 index 0000000..84f580e --- /dev/null +++ b/test/common/child.c @@ -0,0 +1,454 @@ +/* + Framework for testing with a server process + Copyright (C) 2001-2002, Joe Orton <joe@manyfish.co.uk> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "config.h" + +#include <sys/types.h> + +#include <sys/wait.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <sys/stat.h> + +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif + +#include <fcntl.h> +#include <netdb.h> +#include <signal.h> + +#include "ne_socket.h" +#include "ne_utils.h" +#include "ne_string.h" + +#include "tests.h" +#include "child.h" + +static pid_t child = 0; + +int clength; + +static struct in_addr lh_addr = {0}, hn_addr = {0}; + +const char *want_header = NULL; +got_header_fn got_header = NULL; +char *local_hostname = NULL; + +/* If we have pipe(), then use a pipe between the parent and child to + * know when the child is ready to accept incoming connections. + * Otherwise use boring sleep()s trying to avoid the race condition + * between listen() and connect() in the two processes. */ +#ifdef HAVE_PIPE +#define USE_PIPE 1 +#endif + +int lookup_localhost(void) +{ + /* this will break if a system is set up so that `localhost' does + * not resolve to 127.0.0.1, but... */ + lh_addr.s_addr = inet_addr("127.0.0.1"); + return OK; +} + +int lookup_hostname(void) +{ + char buf[BUFSIZ]; + struct hostent *ent; + + local_hostname = NULL; + ONV(gethostname(buf, BUFSIZ) < 0, + ("gethostname failed: %s", strerror(errno))); + + ent = gethostbyname(buf); +#ifdef HAVE_HSTRERROR + ONV(ent == NULL, + ("could not resolve `%s': %s", buf, hstrerror(h_errno))); +#else + ONV(ent == NULL, ("could not resolve `%s'", buf)); +#endif + + local_hostname = ne_strdup(ent->h_name); + + return OK; +} + +static int do_listen(struct in_addr addr, int port) +{ + int ls = socket(AF_INET, SOCK_STREAM, 0); + struct sockaddr_in saddr = {0}; + int val = 1; + + setsockopt(ls, SOL_SOCKET, SO_REUSEADDR, (void *)&val, sizeof(int)); + + saddr.sin_addr = addr; + saddr.sin_port = htons(port); + saddr.sin_family = AF_INET; + + if (bind(ls, (struct sockaddr *)&saddr, sizeof(saddr))) { + printf("bind failed: %s\n", strerror(errno)); + return -1; + } + if (listen(ls, 5)) { + printf("listen failed: %s\n", strerror(errno)); + return -1; + } + + return ls; +} + +void minisleep(void) +{ +#ifdef HAVE_USLEEP + usleep(500); +#else + sleep(1); +#endif +} + +/* This runs as the child process. */ +static int server_child(int readyfd, struct in_addr addr, int port, + server_fn callback, void *userdata) +{ + ne_socket *s = ne_sock_create(); + int ret, listener; + + in_child(); + + listener = do_listen(addr, port); + if (listener < 0) + return FAIL; + +#ifdef USE_PIPE + /* Tell the parent we're ready for the request. */ + write(readyfd, "a", 1); +#endif + + ONN("accept failed", ne_sock_accept(s, listener)); + + ret = callback(s, userdata); + + ne_sock_close(s); + + return ret; +} + +int spawn_server(int port, server_fn fn, void *ud) +{ + return spawn_server_addr(1, port, fn, ud); +} + +int spawn_server_addr(int bind_local, int port, server_fn fn, void *ud) +{ + int fds[2]; + struct in_addr addr; + + addr = bind_local?lh_addr:hn_addr; + +#ifdef USE_PIPE + if (pipe(fds)) { + perror("spawn_server: pipe"); + return FAIL; + } +#else + /* avoid using uninitialized variable. */ + fds[0] = fds[1] = 0; +#endif + + child = fork(); + + ONN("fork server", child == -1); + + if (child == 0) { + /* this is the child. */ + int ret; + + ret = server_child(fds[1], addr, port, fn, ud); + +#ifdef USE_PIPE + close(fds[0]); + close(fds[1]); +#endif + + /* print the error out otherwise it gets lost. */ + if (ret) { + printf("server child failed: %s\n", test_context); + } + + /* and quit the child. */ + exit(ret); + } else { + char ch; + +#ifdef USE_PIPE + if (read(fds[0], &ch, 1) < 0) + perror("parent read"); + + close(fds[0]); + close(fds[1]); +#else + minisleep(); +#endif + + return OK; + } +} + +int spawn_server_repeat(int port, server_fn fn, void *userdata, int n) +{ + int fds[2]; + +#ifdef USE_PIPE + if (pipe(fds)) { + perror("spawn_server: pipe"); + return FAIL; + } +#else + /* avoid using uninitialized variable. */ + fds[0] = fds[1] = 0; +#endif + + child = fork(); + + if (child == 0) { + /* this is the child. */ + int listener, count = 0; + + in_child(); + + listener = do_listen(lh_addr, port); + +#ifdef USE_PIPE + write(fds[1], "Z", 1); +#endif + + close(fds[1]); + close(fds[0]); + + /* Loop serving requests. */ + while (++count < n) { + ne_socket *sock = ne_sock_create(); + int ret; + + NE_DEBUG(NE_DBG_HTTP, "child awaiting connection #%d.\n", count); + ONN("accept failed", ne_sock_accept(sock, listener)); + ret = fn(sock, userdata); + ne_sock_close(sock); + NE_DEBUG(NE_DBG_HTTP, "child served request, %d.\n", ret); + if (ret) { + printf("server child failed: %s\n", test_context); + exit(-1); + } + /* don't send back notification to parent more than + * once. */ + } + + NE_DEBUG(NE_DBG_HTTP, "child aborted.\n"); + close(listener); + + exit(-1); + + } else { + char ch; + /* this is the parent. wait for the child to get ready */ +#ifdef USE_PIPE + if (read(fds[0], &ch, 1) < 0) + perror("parent read"); + + close(fds[0]); + close(fds[1]); +#else + minisleep(); +#endif + } + + return OK; +} + +int dead_server(void) +{ + int status; + + if (waitpid(child, &status, WNOHANG)) { + /* child quit already! */ + return FAIL; + } + + NE_DEBUG(NE_DBG_HTTP, "child has not quit.\n"); + + return OK; +} + + +int await_server(void) +{ + int status; + + (void) wait(&status); + + /* so that we aren't reaped by mistake. */ + child = 0; + + ONN("error from server process", WEXITSTATUS(status)); + + return OK; +} + +int reap_server(void) +{ + int status; + + if (child != 0) { + (void) kill(child, SIGTERM); + minisleep(); + (void) wait(&status); + child = 0; + } + + return OK; +} + +ssize_t server_send(ne_socket *sock, const char *str, size_t len) +{ + NE_DEBUG(NE_DBG_HTTP, "Sending: %.*s\n", (int)len, str); + return ne_sock_fullwrite(sock, str, len); +} + +int discard_request(ne_socket *sock) +{ + char buffer[1024]; + size_t offset = want_header?strlen(want_header):0; + + clength = 0; + + NE_DEBUG(NE_DBG_HTTP, "Discarding request...\n"); + do { + ONV(ne_sock_readline(sock, buffer, 1024) < 0, + ("error reading line: %s", ne_sock_error(sock))); + NE_DEBUG(NE_DBG_HTTP, "[req] %s", buffer); + if (strncasecmp(buffer, "content-length:", 15) == 0) { + clength = atoi(buffer + 16); + } + if (got_header != NULL && want_header != NULL && + strncasecmp(buffer, want_header, offset) == 0 && + buffer[offset] == ':') { + char *value = buffer + offset + 1; + if (*value == ' ') value++; + got_header(ne_shave(value, "\r\n")); + } + } while (strcmp(buffer, "\r\n") != 0); + + return OK; +} + +int discard_body(ne_socket *sock) +{ + while (clength > 0) { + char buf[BUFSIZ]; + size_t bytes = clength; + ssize_t ret; + if (bytes > sizeof(buf)) bytes = sizeof(buf); + NE_DEBUG(NE_DBG_HTTP, "Discarding %" NE_FMT_SIZE_T " bytes.\n", + bytes); + ret = ne_sock_read(sock, buf, bytes); + ONV(ret < 0, ("socket read failed (%" NE_FMT_SSIZE_T "): %s", + ret, ne_sock_error(sock))); + clength -= ret; + NE_DEBUG(NE_DBG_HTTP, "Got %" NE_FMT_SSIZE_T " bytes.\n", ret); + } + NE_DEBUG(NE_DBG_HTTP, "Discard successful.\n"); + return OK; +} + +int serve_file(ne_socket *sock, void *ud) +{ + char buffer[BUFSIZ]; + struct stat st; + struct serve_file_args *args = ud; + ssize_t ret; + int fd; + + CALL(discard_request(sock)); + + ne_sock_fullread(sock, buffer, clength); + + fd = open(args->fname, O_RDONLY); + if (fd < 0) { + SEND_STRING(sock, + "HTTP/1.0 404 File Not Found\r\n" + "Content-Length: 0\r\n\r\n"); + return 0; + } + + ONN("fstat fd", fstat(fd, &st)); + + SEND_STRING(sock, "HTTP/1.0 200 OK\r\n"); + if (args->chunks) { + sprintf(buffer, "Transfer-Encoding: chunked\r\n"); + } else { + sprintf(buffer, "Content-Length: %" NE_FMT_OFF_T "\r\n", + st.st_size); + } + + if (args->headers) { + strcat(buffer, args->headers); + } + + strcat(buffer, "\r\n"); + + SEND_STRING(sock, buffer); + + NE_DEBUG(NE_DBG_HTTP, "Serving %s (%" NE_FMT_OFF_T " bytes).\n", + args->fname, st.st_size); + + if (args->chunks) { + char buf[1024]; + + while ((ret = read(fd, &buf, args->chunks)) > 0) { + /* this is a small integer, cast it explicitly to avoid + * warnings with printing an ssize_t. */ + sprintf(buffer, "%x\r\n", (unsigned int)ret); + SEND_STRING(sock, buffer); + ONN("writing body", ne_sock_fullwrite(sock, buf, ret)); + SEND_STRING(sock, "\r\n"); + } + + SEND_STRING(sock, "0\r\n\r\n"); + + } else { + while ((ret = read(fd, buffer, BUFSIZ)) > 0) { + ONN("writing body", ne_sock_fullwrite(sock, buffer, ret)); + } + } + + ONN("error reading from file", ret < 0); + + (void) close(fd); + + return OK; +} diff --git a/test/common/child.h b/test/common/child.h new file mode 100644 index 0000000..9c506b9 --- /dev/null +++ b/test/common/child.h @@ -0,0 +1,113 @@ +/* + Framework for testing with a server process + Copyright (C) 2001-2002, Joe Orton <joe@manyfish.co.uk> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef CHILD_H +#define CHILD_H 1 + +#include "config.h" + +#ifdef HAVE_STRING_H +#include <string.h> /* for strlen() */ +#endif + +#include "ne_socket.h" + +/* Test which does DNS lookup on "localhost": this must be the first + * named test. */ +int lookup_localhost(void); + +/* Test which looks up real local hostname. */ +int lookup_hostname(void); + +/* set to local hostname if lookup_hostname succeeds. */ +extern char *local_hostname; + +/* Callback for spawn_server. */ +typedef int (*server_fn)(ne_socket *sock, void *userdata); + +/* Spawns server child process: + * - forks child process. + * - child process listens on localhost at given port. + * - when you connect to it, 'fn' is run... + * fn is passed the client/server socket as first argument, + * and userdata as second. + * - the socket is closed when 'fn' returns, so don't close in in 'fn'. + */ +int spawn_server(int port, server_fn fn, void *userdata); + +/* Like spawn_server; if bind_local is non-zero, binds server to + * localhost, otherwise, binds server to real local hostname. (must + * have called lookup_localhost or lookup_hostname as approprate + * beforehand). */ +int spawn_server_addr(int bind_local, int port, server_fn fn, void *userdata); + +/* Like spawn server except will continue accepting connections and + * processing requests, up to 'n' times. If 'n' is reached, then the + * child process exits with a failure status. */ +int spawn_server_repeat(int port, server_fn fn, void *userdata, int n); + +/* Blocks until child process exits, and gives return code of 'fn'. */ +int await_server(void); + +/* Kills child process. */ +int reap_server(void); + +/* Returns non-zero if server process has already died. */ +int dead_server(void); + +/* If discard_request comes across a header called 'want_header', it + * will call got_header passing the header field value. */ +extern const char *want_header; +typedef void (*got_header_fn)(char *value); +extern got_header_fn got_header; + +/* Send string to child; ne_sock_fullwrite with debugging. */ +ssize_t server_send(ne_socket *sock, const char *data, size_t len); + +/* Utility macro: send given string down socket. */ +#define SEND_STRING(sock, str) server_send((sock), (str), strlen((str))) + +/* Utility function: discard request. Sets context on error. */ +int discard_request(ne_socket *sock); + +/* Utility function: discard request body. Sets context on error. */ +int discard_body(ne_socket *sock); + +struct serve_file_args { + const char *fname; + const char *headers; + int chunks; +}; + +/* Utility function: callback for spawn_server: pass pointer to + * serve_file_args as userdata, and args->fname is served as a 200 + * request. If args->headers is non-NULL, it must be a set of + * CRLF-terminated lines which is added in to the response headers. + * If args->chunks is non-zero, the file is delivered using chunks of + * that size. */ +int serve_file(ne_socket *sock, void *ud); + +/* set to value of C-L header by discard_request. */ +extern int clength; + +/* Sleep for a short time. */ +void minisleep(void); + +#endif /* CHILD_H */ diff --git a/test/common/run.sh b/test/common/run.sh new file mode 100755 index 0000000..01ed5a9 --- /dev/null +++ b/test/common/run.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +rm -f debug.log +rm -f child.log + +# for shared builds. +LD_LIBRARY_PATH=../src/.libs:$LD_LIBRARY_PATH +export LD_LIBRARY_PATH + +for f in $*; do + if ./$f; then + : + else + echo FAILURE + exit 1 + fi +done + +exit 0 diff --git a/test/common/tests.c b/test/common/tests.c new file mode 100644 index 0000000..2ff6b5f --- /dev/null +++ b/test/common/tests.c @@ -0,0 +1,318 @@ +/* + Stupidly simple test framework + Copyright (C) 2001-2002, Joe Orton <joe@manyfish.co.uk> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "config.h" + +#include <sys/types.h> + +#include <sys/signal.h> + +#include <stdio.h> +#ifdef HAVE_SIGNAL_H +#include <signal.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif + +#include "ne_utils.h" +#include "ne_socket.h" + +#include "tests.h" +#include "child.h" + +char test_context[BUFSIZ]; +int have_context = 0; + +static FILE *child_debug, *debug; + +char **test_argv; +int test_argc; + +const char *test_suite; +int test_num; + +/* statistics for all tests so far */ +static int passes = 0, fails = 0, skipped = 0, warnings = 0; + +/* per-test globals: */ +static int warned, aborted = 0; +static const char *test_name; /* current test name */ + +static int use_colour = 0; + +/* resource for ANSI escape codes: + * http://www.isthe.com/chongo/tech/comp/ansi_escapes.html */ +#define COL(x) do { if (use_colour) printf("\033[" x "m"); } while (0) + +#define NOCOL COL("00") + +void t_context(const char *context, ...) +{ + va_list ap; + va_start(ap, context); + ne_vsnprintf(test_context, BUFSIZ, context, ap); + va_end(ap); + have_context = 1; +} + +void t_warning(const char *str, ...) +{ + va_list ap; + COL("43;01"); printf("WARNING:"); NOCOL; + putchar(' '); + va_start(ap, str); + vprintf(str, ap); + va_end(ap); + warnings++; + warned++; + putchar('\n'); +} + +#define TEST_DEBUG \ +(NE_DBG_HTTP | NE_DBG_SOCKET | NE_DBG_HTTPBODY | NE_DBG_HTTPAUTH | \ + NE_DBG_LOCKS | NE_DBG_XMLPARSE | NE_DBG_XML | NE_DBG_SSL) + +#define W(m) write(0, m, strlen(m)) + +static void child_segv(int signo) +{ + signal(SIGSEGV, SIG_DFL); + W("(possible segfault in child, not dumping core)\n"); + exit(-1); +} + +static void segv(int signo) +{ + signal(SIGSEGV, SIG_DFL); + if (use_colour) { + W("\033[41;37;01m"); + } + W("FAILED - segmentation fault."); + if (use_colour) { + W("\033[00m"); + } + W("\n"); + fflush(debug); + reap_server(); + kill(getpid(), SIGSEGV); +} + +void in_child(void) +{ + ne_debug_init(child_debug, TEST_DEBUG); + NE_DEBUG(TEST_DEBUG, "**** Child forked for test %s ****\n", test_name); + signal(SIGSEGV, child_segv); +} + +int main(int argc, char *argv[]) +{ + int n; + static const char dots[] = "......................"; + + /* get basename(argv[0]) */ + test_suite = strrchr(argv[0], '/'); + if (test_suite == NULL) { + test_suite = argv[0]; + } else { + test_suite++; + } + +#if defined(HAVE_ISATTY) && defined(STDOUT_FILENO) + if (isatty(STDOUT_FILENO)) { + use_colour = 1; + } +#endif + + test_argc = argc; + test_argv = argv; + + debug = fopen("debug.log", "a"); + if (debug == NULL) { + fprintf(stderr, "%s: Could not open debug.log: %s\n", test_suite, + strerror(errno)); + return -1; + } + child_debug = fopen("child.log", "a"); + if (child_debug == NULL) { + fprintf(stderr, "%s: Could not open child.log: %s\n", test_suite, + strerror(errno)); + fclose(debug); + return -1; + } + + if (tests[0].fn == NULL) { + printf("-> no tests found in `%s'\n", test_suite); + return -1; + } + + /* install special SEGV handler. */ + signal(SIGSEGV, segv); + + /* test the "no-debugging" mode of ne_debug. */ + ne_debug_init(NULL, 0); + NE_DEBUG(TEST_DEBUG, "This message should go to /dev/null"); + + /* enable debugging for real. */ + ne_debug_init(debug, TEST_DEBUG); + NE_DEBUG(TEST_DEBUG | NE_DBG_FLUSH, "Version string: %s\n", + ne_version_string()); + + /* another silly test. */ + NE_DEBUG(0, "This message should also go to /dev/null"); + + if (ne_sock_init()) { + COL("43;01"); printf("WARNING:"); NOCOL; + printf(" Socket library initalization failed.\n"); + } + + printf("-> running `%s':\n", test_suite); + + for (n = 0; !aborted && tests[n].fn != NULL; n++) { + int result; +#ifdef NEON_MEMLEAK + size_t allocated = ne_alloc_used; +#endif + + test_name = tests[n].name; + printf("%2d. %s%.*s ", n, test_name, + (int) (strlen(dots) - strlen(test_name)), dots); + have_context = 0; + test_num = n; + warned = 0; + fflush(stdout); + NE_DEBUG(TEST_DEBUG, "******* Running test %d: %s ********\n", + n, test_name); + + /* run the test. */ + result = tests[n].fn(); + +#ifdef NEON_MEMLEAK + /* issue warnings for memory leaks, if requested */ + if ((tests[n].flags & T_CHECK_LEAKS) && result == OK && + ne_alloc_used > allocated) { + t_context("memory leak of %" NE_FMT_SIZE_T " bytes", + ne_alloc_used - allocated); + ne_alloc_dump(debug); + result = FAIL; + } +#endif + + if (tests[n].flags & T_EXPECT_FAIL) { + if (result == OK) { + t_context("test passed but expected failure"); + result = FAIL; + } else if (result == FAIL) + result = OK; + } + + /* align the result column if we've had warnings. */ + if (warned) { + printf(" %s ", dots); + } + + switch (result) { + case OK: + COL("32"); printf("pass"); NOCOL; + if (warned) { + printf(" (with %d warning%s)", warned, (warned > 1)?"s":""); + } + putchar('\n'); + passes++; + break; + case FAILHARD: + aborted = 1; + /* fall-through */ + case FAIL: + COL("41;37;01"); printf("FAIL"); NOCOL; + if (have_context) { + printf(" (%s)", test_context); + } + putchar('\n'); + fails++; + break; + case SKIPREST: + aborted = 1; + /* fall-through */ + case SKIP: + COL("44"); printf("SKIPPED"); NOCOL; + if (have_context) { + printf(" (%s)", test_context); + } + putchar('\n'); + skipped++; + break; + default: + COL("41;37;01"); printf("OOPS"); NOCOL; + printf(" unexpected test result `%d'\n", result); + break; + } + + reap_server(); + } + + /* discount skipped tests */ + if (skipped) { + printf("-> %d %s.\n", skipped, + skipped==1?"test was skipped":"tests were skipped"); + n -= skipped; + if (passes + fails != n) { + printf("-> ARGH! Number of test results does not match " + "number of tests.\n" + "-> ARGH! Test Results are INRELIABLE.\n"); + } + } + /* print the summary. */ + if (skipped && n == 0) { + printf("<- all tests skipped for `%s'.\n", test_suite); + } else { + printf("<- summary for `%s': " + "of %d tests run: %d passed, %d failed. %.1f%%\n", + test_suite, n, passes, fails, 100*(float)passes/n); + if (warnings) { + printf("-> %d warning%s issued.\n", warnings, + warnings==1?" was":"s were"); + } + } + + if (fclose(debug)) { + fprintf(stderr, "Error closing debug.log: %s\n", strerror(errno)); + fails = 1; + } + + if (fclose(child_debug)) { + fprintf(stderr, "Error closing child.log: %s\n", strerror(errno)); + fails = 1; + } + + ne_sock_exit(); + + return fails; +} + diff --git a/test/common/tests.h b/test/common/tests.h new file mode 100644 index 0000000..72e8275 --- /dev/null +++ b/test/common/tests.h @@ -0,0 +1,125 @@ +/* + Stupidly simple test framework + Copyright (C) 2001-2002, Joe Orton <joe@manyfish.co.uk> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA + +*/ + +#ifndef TESTS_H +#define TESTS_H 1 + +#ifdef HAVE_STRING_H +#include <string.h> +#endif + +#include <stdio.h> + +#define OK 0 +#define FAIL 1 +#define FAILHARD 2 /* fail and skip all succeeding tests in this suite. */ +#define SKIP 3 /* test was skipped because precondition was not met */ +#define SKIPREST 4 /* skipped, and skip all succeeding tests in suite */ + +/* A test function. Must return any of OK, FAIL, FAILHARD, SKIP, or + * SKIPREST. May call t_warning() any number of times. If not + * returning OK, optionally call t_context to provide an error + * message. */ +typedef int (*test_func)(void); + +typedef struct { + test_func fn; /* the function to test. */ + const char *name; /* the name of the test. */ + int flags; +} ne_test; + +/* possible values for flags: */ +#define T_CHECK_LEAKS (1) /* check for memory leaks */ +#define T_EXPECT_FAIL (2) /* expect failure */ + +/* array of tests to run: must be defined by each test suite. */ +extern ne_test tests[]; + +/* define a test function which has the same name as the function, + * and does check for memory leaks. */ +#define T(fn) { fn, #fn, T_CHECK_LEAKS } +/* define a test function which is expected to return FAIL. */ +#define T_XFAIL(fn) { fn, #fn, T_EXPECT_FAIL | T_CHECK_LEAKS } +/* define a test function which isn't checked for memory leaks. */ +#define T_LEAKY(fn) { fn, #fn, 0 } + +/* current test number */ +extern int test_num; + +/* name of test suite */ +extern const char *test_suite; + +/* Provide result context message. */ +void t_context(const char *ctx, ...) +#ifdef __GNUC__ + __attribute__ ((format (printf, 1, 2))) +#endif /* __GNUC__ */ + ; + +extern char test_context[]; + +/* the command-line arguments passed in to the test suite: */ +extern char **test_argv; +extern int test_argc; + +/* child process should call this. */ +void in_child(void); + +/* issue a warning. */ +void t_warning(const char *str, ...) +#ifdef __GNUC__ + __attribute__ ((format (printf, 1, 2))) +#endif /* __GNUC__ */ +; + +/* Macros for easily writing is-not-zero comparison tests; the ON* + * macros fail the function if a comparison is not zero. + * + * ONV(x,vs) takes a comparison X, and a printf varargs list for + * the failure message. + * e.g. ONV(strcmp(bar, "foo"), ("bar was %s not 'foo'", bar)) + * + * ON(x) takes a comparison X, and uses the line number for the failure + * message. e.g. ONV(strcmp(bar, "foo")) + * + * ONN(n, x) takes a comparison X, and a flat string failure message. + * e.g. ONN("foo was wrong", strcmp(bar, "foo")) */ + +#define ONV(x,vs) do { if ((x)) { t_context vs; return FAIL; } } while (0) +#define ON(x) ONV((x), ("line %d", __LINE__ )) +#define ONN(n,x) ONV((x), (n)) + +/* ONCMP(exp, act, name): 'exp' is the expected string, 'act' is the + * actual string for some field 'name'. Succeeds if strcmp(exp,act) + * == 0 or both are NULL. */ +#define ONCMP(exp, act, ctx, name) do { \ +ONV(exp && !act, ("%s: " name " was NULL, expected non-NULL", ctx)); \ +ONV(!exp && act, ("%s: " name " was non-NULL, expected NULL", ctx)); \ +ONV(exp && strcmp(exp, act), ("%s: " name " was %s not %s", ctx, exp, act)); \ +} while (0) + +/* return immediately with result of test 'x' if it fails. */ +#define CALL(x) do { int t_ret = (x); if (t_ret != OK) return t_ret; } while (0) + +/* PRECOND: skip current test if condition 'x' is not true. */ +#define PRECOND(x) do { if (!(x)) { return SKIP; } } while (0) + +#endif /* TESTS_H */ diff --git a/test/compress.c b/test/compress.c new file mode 100644 index 0000000..720ebe0 --- /dev/null +++ b/test/compress.c @@ -0,0 +1,216 @@ +/* + tests for compressed response handling. + Copyright (C) 2001-2003, Joe Orton <joe@manyfish.co.uk> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "config.h" + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include <fcntl.h> + +#include "ne_compress.h" + +#include "tests.h" +#include "child.h" +#include "utils.h" + +static int failed; + +static char *newsfn = "../NEWS"; + +struct body { + const char *str; + size_t len; +}; + +static int init(void) +{ + if (test_argc > 1) { + newsfn = ne_concat(test_argv[1], "/../NEWS", NULL); + } + return lookup_localhost(); +} + +static void reader(void *ud, const char *block, size_t len) +{ + struct body *b = ud; + + if (failed || len > b->len || memcmp(b->str, block, len) != 0) { + failed = 1; + } else { + b->str += len; + b->len -= len; + } +} + +static int file2buf(int fd, ne_buffer *buf) +{ + char buffer[BUFSIZ]; + ssize_t n; + + while ((n = read(fd, buffer, BUFSIZ)) > 0) { + ne_buffer_append(buf, buffer, n); + } + + return 0; +} + +static int do_fetch(const char *realfn, const char *gzipfn, + int chunked, int expect_fail) +{ + ne_session *sess; + ne_request *req; + int fd; + ne_buffer *buf = ne_buffer_create(); + struct serve_file_args sfargs; + ne_decompress *dc; + struct body body; + + fd = open(realfn, O_RDONLY); + ONN("failed to open file", fd < 0); + file2buf(fd, buf); + (void) close(fd); + + body.str = buf->data; + body.len = buf->used - 1; + + failed = 0; + + if (gzipfn) { + sfargs.fname = gzipfn; + sfargs.headers = "Content-Encoding: gzip\r\n"; + } else { + sfargs.fname = realfn; + sfargs.headers = NULL; + } + sfargs.chunks = chunked; + + CALL(make_session(&sess, serve_file, &sfargs)); + + req = ne_request_create(sess, "GET", "/"); + dc = ne_decompress_reader(req, ne_accept_2xx, reader, &body); + + ONREQ(ne_request_dispatch(req)); + + ONN("file not served", ne_get_status(req)->code != 200); + + ONN("decompress succeeded", expect_fail && !ne_decompress_destroy(dc)); + ONV(!expect_fail && ne_decompress_destroy(dc), + ("decompress failed: %s", ne_get_error(sess))); + + ne_request_destroy(req); + ne_session_destroy(sess); + ne_buffer_destroy(buf); + + CALL(await_server()); + + ONN("inflated response compare", failed); + if (!expect_fail) + ONN("inflated response truncated", body.len != 0); + + return OK; +} + +static int fetch(const char *realfn, const char *gzipfn, int chunked) +{ + return do_fetch(realfn, gzipfn, chunked, 0); +} + +/* Test the no-compression case. */ +static int not_compressed(void) +{ + return fetch(newsfn, NULL, 0); +} + +static int simple(void) +{ + return fetch(newsfn, "file1.gz", 0); +} + +/* file1.gz has an embedded filename. */ +static int withname(void) +{ + return fetch(newsfn, "file2.gz", 0); +} + +/* deliver various different sizes of chunks: tests the various + * decoding cases. */ +static int chunked_1b_wn(void) +{ + return fetch(newsfn, "file2.gz", 1); +} + +static int chunked_1b(void) +{ + return fetch(newsfn, "file1.gz", 1); +} + +static int chunked_12b(void) +{ + return fetch(newsfn, "file2.gz", 12); +} + +static int chunked_20b(void) +{ + return fetch(newsfn, "file2.gz", 20); +} + +static int chunked_10b(void) +{ + return fetch(newsfn, "file1.gz", 10); +} + +static int chunked_10b_wn(void) +{ + return fetch(newsfn, "file2.gz", 10); +} + +static int fail_trailing(void) +{ + return do_fetch(newsfn, "trailing.gz", 0, 1); +} + +static int fail_truncate(void) +{ + return do_fetch(newsfn, "truncated.gz", 0, 1); +} + +static int fail_bad_csum(void) +{ + return do_fetch(newsfn, "badcsum.gz", 0, 1); +} + +ne_test tests[] = { + T_LEAKY(init), + T(not_compressed), + T(simple), + T(withname), + T(fail_trailing), + T(fail_bad_csum), + T(fail_truncate), + T(chunked_1b), + T(chunked_1b_wn), + T(chunked_12b), + T(chunked_20b), + T(chunked_10b), + T(chunked_10b_wn), + T(NULL) +}; diff --git a/test/cookies.c b/test/cookies.c new file mode 100644 index 0000000..706ca7e --- /dev/null +++ b/test/cookies.c @@ -0,0 +1,140 @@ +/* + Test for cookies interface (ne_cookies.h) + Copyright (C) 2002-2003, Joe Orton <joe@manyfish.co.uk> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "config.h" + +#include <sys/types.h> + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "ne_request.h" +#include "ne_socket.h" +#include "ne_cookies.h" + +#include "tests.h" +#include "child.h" +#include "utils.h" + +static int serve_cookie(ne_socket *sock, void *ud) +{ + const char *hdr = ud; + char buf[BUFSIZ]; + + CALL(discard_request(sock)); + + ne_snprintf(buf, BUFSIZ, "HTTP/1.0 200 Okey Dokey\r\n" + "Connection: close\r\n" "%s\r\n\r\n", hdr); + + SEND_STRING(sock, buf); + + return OK; +} + +static int fetch_cookie(const char *hdr, const char *path, + ne_cookie_cache *jar) +{ + ne_session *sess; + + CALL(make_session(&sess, serve_cookie, (void *)hdr)); + + ne_cookie_register(sess, jar); + + CALL(any_request(sess, path)); + + ne_session_destroy(sess); + CALL(await_server()); + + return OK; +} + +static int parsing(void) +{ + static const struct { + const char *hdr, *name, *value; + } cookies[] = { + { "Set-Cookie: alpha=bar", "alpha", "bar" }, + { "Set-Cookie2: alpha=bar", "alpha", "bar" }, + { "Set-Cookie: beta = bar", "beta", "bar" }, + { "Set-Cookie: delta = bar; norman=fish", "delta", "bar" }, + { NULL, NULL, NULL } + }; + int n; + + for (n = 0; cookies[n].hdr != NULL; n++) { + ne_cookie_cache jar = {0}; + ne_cookie *ck; + + CALL(fetch_cookie(cookies[n].hdr, "/foo", &jar)); + + ck = jar.cookies; + ONV(ck == NULL, ("%d: cookie jar was empty!", n)); + + ONV(strcmp(ck->name, cookies[n].name) || + strcmp(ck->value, cookies[n].value), + ("%d: was [%s]=[%s]!", n, ck->name, ck->value)); + } + + return OK; +} + +static int rejects(void) +{ + static const struct { + const char *hdr, *path; + } resps[] = { + /* names prefixed with $ are illegal */ + { "Set-Cookie2: $foo=bar, Version=1", "/foo" }, +#define FOOBAR "Set-Cookie2: foo=bar, Version=1" + /* Path is not prefix of Request-URI */ + { FOOBAR ", Path=/bar", "/foo" }, + /* Domain must have embedded dots. */ + { FOOBAR ", Domain=fish", "/foo" }, + /* Domain must domain-match request-host */ + { FOOBAR ", Domain=other.host.com", "/foo" }, + /* Port not named in Port list */ + { FOOBAR ", Port=\"12\"", "/foo" }, + { NULL, NULL } + }; + int n; + + for (n = 0; resps[n].hdr != NULL; n++) { + ne_cookie_cache jar = {0}; + + CALL(fetch_cookie(resps[n].hdr, resps[n].path, &jar)); + + ONV(jar.cookies != NULL, + ("cookie was returned for `%s'", resps[n].hdr)); + } + + return OK; +} + +ne_test tests[] = { + T(lookup_localhost), + T(parsing), + T(rejects), + T(NULL) +}; + diff --git a/test/expired.pem b/test/expired.pem new file mode 100644 index 0000000..0062cc8 --- /dev/null +++ b/test/expired.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDODCCAuKgAwIBAgIBADANBgkqhkiG9w0BAQQFADCBoTELMAkGA1UEBhMCR0Ix +FzAVBgNVBAgTDkNhbWJyaWRnZXNoaXJlMRIwEAYDVQQHEwlDYW1icmlkZ2UxGjAY +BgNVBAoTEU5lb24gSGFja2VycyBMdGQuMRUwEwYDVQQLEwxOZW9uIFFBIERlcHQx +EjAQBgNVBAMTCWxvY2FsaG9zdDEeMBwGCSqGSIb3DQEJARYPbmVvbkB3ZWJkYXYu +b3JnMB4XDTAyMDEyMTIwMzkwNFoXDTAyMDEzMTIwMzkwNFowgaExCzAJBgNVBAYT +AkdCMRcwFQYDVQQIEw5DYW1icmlkZ2VzaGlyZTESMBAGA1UEBxMJQ2FtYnJpZGdl +MRowGAYDVQQKExFOZW9uIEhhY2tlcnMgTHRkLjEVMBMGA1UECxMMTmVvbiBRQSBE +ZXB0MRIwEAYDVQQDEwlsb2NhbGhvc3QxHjAcBgkqhkiG9w0BCQEWD25lb25Ad2Vi +ZGF2Lm9yZzBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQDzRU5sZ8+CWQPvPkqJw9Kl +oEgT2FqzZR9RT/qbJuRBmRphiRr0g7JOh5Mr7LXaKShedFLhGidutyKKwIZJnRht +AgMBAAGjggEBMIH+MB0GA1UdDgQWBBRFA3ktzHSuD9uB6mJOWoElmOtknzCBzgYD +VR0jBIHGMIHDgBRFA3ktzHSuD9uB6mJOWoElmOtkn6GBp6SBpDCBoTELMAkGA1UE +BhMCR0IxFzAVBgNVBAgTDkNhbWJyaWRnZXNoaXJlMRIwEAYDVQQHEwlDYW1icmlk +Z2UxGjAYBgNVBAoTEU5lb24gSGFja2VycyBMdGQuMRUwEwYDVQQLEwxOZW9uIFFB +IERlcHQxEjAQBgNVBAMTCWxvY2FsaG9zdDEeMBwGCSqGSIb3DQEJARYPbmVvbkB3 +ZWJkYXYub3JnggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADQQBDSFbe +9EjP+IyZ4vhJSk66gLSN8CnafoGm5JHpNOHy5gWLh7j0a/dxWRd4gpoBYBB6Y9rO +YV6Eq3njdj0gu+NN +-----END CERTIFICATE----- diff --git a/test/htdocs/plain b/test/htdocs/plain new file mode 100644 index 0000000..bce1946 --- /dev/null +++ b/test/htdocs/plain @@ -0,0 +1 @@ +Test file. diff --git a/test/lock.c b/test/lock.c new file mode 100644 index 0000000..5fb80d4 --- /dev/null +++ b/test/lock.c @@ -0,0 +1,540 @@ +/* + lock tests + Copyright (C) 2002-2003, Joe Orton <joe@manyfish.co.uk> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "config.h" + +#include <sys/types.h> + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "ne_request.h" +#include "ne_locks.h" +#include "ne_socket.h" +#include "ne_basic.h" + +#include "tests.h" +#include "child.h" +#include "utils.h" + +/* returns an activelock XML element. */ +static char *activelock(enum ne_lock_scope scope, + int depth, + const char *owner, + long timeout, + const char *token_href) +{ + static char buf[BUFSIZ]; + + ne_snprintf(buf, BUFSIZ, + "<D:activelock>\n" + "<D:locktype><D:write/></D:locktype>\n" + "<D:lockscope><D:%s/></D:lockscope>\n" + "<D:depth>%d</D:depth>\n" + "<D:owner>%s</D:owner>\n" + "<D:timeout>Second-%ld</D:timeout>\n" + "<D:locktoken><D:href>%s</D:href></D:locktoken>\n" + "</D:activelock>", + scope==ne_lockscope_exclusive?"exclusive":"shared", + depth, owner, timeout, token_href); + + return buf; +} + +/* return body of LOCK response for given lock. */ +static char *lock_response(enum ne_lock_scope scope, + int depth, + const char *owner, + long timeout, + const char *token_href) +{ + static char buf[BUFSIZ]; + sprintf(buf, + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + "<D:prop xmlns:D=\"DAV:\">" + "<D:lockdiscovery>%s</D:lockdiscovery></D:prop>\n", + activelock(scope, depth, owner, timeout, token_href)); + return buf; +} + +/* return body of LOCK response where response gives multiple + * activelocks (i.e. shared locks). */ +static char *multi_lock_response(struct ne_lock **locks) +{ + ne_buffer *buf = ne_buffer_create(); + int n; + + ne_buffer_zappend(buf, + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + "<D:prop xmlns:D=\"DAV:\">" + "<D:lockdiscovery>"); + + for (n = 0; locks[n] != NULL; n++) { + char *lk = activelock(locks[n]->scope, locks[n]->depth, + locks[n]->owner, locks[n]->timeout, + locks[n]->token); + ne_buffer_zappend(buf, lk); + } + + ne_buffer_zappend(buf, "</D:lockdiscovery></D:prop>"); + return ne_buffer_finish(buf); +} + +static char *discover_response(const char *href, const struct ne_lock *lk) +{ + static char buf[BUFSIZ]; + ne_snprintf(buf, BUFSIZ, + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + "<D:multistatus xmlns:D='DAV:'>\n" + "<D:response><D:href>%s</D:href><D:propstat>\n" + "<D:prop><D:lockdiscovery>%s</D:lockdiscovery></D:prop>\n" + "<D:status>HTTP/1.1 200 OK</D:status></D:propstat>\n" + "</D:response></D:multistatus>\n", + href, activelock(lk->scope, lk->depth, lk->owner, + 7200, lk->token)); + return buf; +} + +static struct ne_lock *make_lock(const char *path, const char *token, + enum ne_lock_scope scope, int depth) +{ + struct ne_lock *lock = ne_calloc(sizeof *lock); + + lock->token = ne_strdup(token); + lock->scope = scope; + lock->depth = depth; + lock->uri.host = ne_strdup("localhost"); + lock->uri.scheme = ne_strdup("http"); + lock->uri.path = ne_strdup(path); + lock->uri.port = 7777; + + return lock; +} + +/* Tests for lock store handling. */ +static int store_single(void) +{ + ne_lock_store *store = ne_lockstore_create(); + struct ne_lock *lk = make_lock("/foo", "blah", ne_lockscope_exclusive, 0); + struct ne_lock *lk2; + + ONN("create failed", store == NULL); + + ONN("new lock store not empty", ne_lockstore_first(store) != NULL); + + ne_lockstore_add(store, lk); + + ONN("lock not found in store", ne_lockstore_first(store) != lk); + + ONN(">1 locks in store?", ne_lockstore_next(store) != NULL); + + lk2 = ne_lockstore_findbyuri(store, &lk->uri); + + ONN("lock not found by URI", lk2 == NULL); + ONN("other lock found by URI", lk2 != lk); + + ne_lockstore_remove(store, lk); + + ONN("store not empty after removing lock", + ne_lockstore_first(store) != NULL); + + ONN("lock still found after removing lock", + ne_lockstore_findbyuri(store, &lk->uri) != NULL); + + ne_lockstore_destroy(store); + ne_lock_destroy(lk); + + return OK; +} + +static int store_several(void) +{ + ne_lock_store *store = ne_lockstore_create(); + struct ne_lock *lk = make_lock("/foo", "blah", ne_lockscope_exclusive, 0); + struct ne_lock *lk2 = make_lock("/bar", "blee", ne_lockscope_exclusive, 0); + struct ne_lock *lf, *lf2; + + ONN("create failed", store == NULL); + + ne_lockstore_add(store, lk); + ne_lockstore_add(store, lk2); + + lf = ne_lockstore_first(store); + ONN("lock store empty", lf == NULL); + lf2 = ne_lockstore_next(store); + ONN("lock store >2 locks", ne_lockstore_next(store) != NULL); + + /* guarantee that _first, _next returned either of the + * combinations: (lf, lf2) or (lf2, lf) */ + ONN("found wrong locks", ((lf != lk && lf != lk2) || + (lf2 != lk && lf2 != lk2) || + (lf == lf2))); + + ONN("first find failed", + ne_lockstore_findbyuri(store, &lk->uri) != lk); + ONN("second find failed", + ne_lockstore_findbyuri(store, &lk2->uri) != lk2); + + ne_lockstore_remove(store, lk); + ne_lock_destroy(lk); + + ONN("remove left stray lock?", ne_lockstore_first(store) != lk2); + + ONN("remove left >1 lock?", ne_lockstore_next(store) != NULL); + + ne_lockstore_remove(store, lk2); + ne_lock_destroy(lk2); + + ONN("store not empty after removing all locks", + ne_lockstore_first(store) != NULL); + + ne_lockstore_destroy(store); + + return OK; +} + +/* regression test for <= 0.18.2, where timeout field was not parsed correctly. */ +static int lock_timeout(void) +{ + ne_session *sess; + char *resp, *rbody = lock_response(ne_lockscope_exclusive, 0, "me", + 6500, "opaquelocktoken:foo"); + struct ne_lock *lock = ne_lock_create(); + + resp = ne_concat("HTTP/1.1 200 OK\r\n" "Server: neon-test-server\r\n" + "Lock-Token: <opaquelocktoken:foo>" EOL + "Connection: close\r\n\r\n", rbody, NULL); + + CALL(make_session(&sess, single_serve_string, resp)); + ne_free(resp); + + ne_fill_server_uri(sess, &lock->uri); + lock->uri.path = ne_strdup("/foo"); + lock->timeout = 5; + + ONREQ(ne_lock(sess, lock)); + + ONN("lock timeout ignored in response", + lock->timeout != 6500); + + ne_session_destroy(sess); + ne_lock_destroy(lock); + + CALL(await_server()); + + return OK; +} + +static int verify_if; +static const char *verify_if_expect; + +static void got_if_header(char *value) +{ + verify_if = !strcmp(verify_if_expect, value); + NE_DEBUG(NE_DBG_HTTP, "Verified If header, %d: got [%s] expected [%s]\n", + verify_if, value, verify_if_expect); +} + +/* Server callback which checks that an If: header is recevied. */ +static int serve_verify_if(ne_socket *sock, void *userdata) +{ + /* tell us about If headers in the request. */ + want_header = "If"; + got_header = got_if_header; + verify_if_expect = userdata; + + verify_if = 0; + + CALL(discard_request(sock)); + + if (verify_if) { + ON(SEND_STRING(sock, "HTTP/1.1 200 OK" EOL)); + } else { + ON(SEND_STRING(sock, "HTTP/1.1 403 Wrong If Header" EOL)); + } + + ON(SEND_STRING(sock, "Connection: close" EOL EOL)); + + return OK; +} + +/* Make a request which will require a lock. */ +static int do_request(ne_session *sess, const char *path, int depth, + int modparent) +{ + ne_request *req = ne_request_create(sess, "RANDOM", path); + + if (depth > 0) { + ne_add_depth_header(req, depth); + } + + if (depth != -1) + ne_lock_using_resource(req, path, depth); + if (modparent) + ne_lock_using_parent(req, path); + + ONREQ(ne_request_dispatch(req)); + + ONV(ne_get_status(req)->code != 200, + ("request failed: %s", ne_get_error(sess))); + + ne_request_destroy(req); + + return OK; +} + +/* Tests If: header submission, for a lock of depth 'lockdepth' at + * 'lockpath', with a request to 'reqpath' which Depth header of + * 'reqdepth'. If modparent is non-zero; the request is flagged to + * modify the parent resource too. */ +static int submit_test(const char *lockpath, int lockdepth, + const char *reqpath, int reqdepth, + int modparent) +{ + ne_lock_store *store = ne_lockstore_create(); + ne_session *sess; + struct ne_lock *lk = ne_lock_create(); + char *expect_if; + int ret; + + expect_if = ne_concat("<http://localhost:7777", lockpath, + "> (<somelocktoken>)", NULL); + CALL(make_session(&sess, serve_verify_if, expect_if)); + ne_free(expect_if); + + ne_fill_server_uri(sess, &lk->uri); + lk->uri.path = ne_strdup(lockpath); + lk->token = ne_strdup("somelocktoken"); + lk->depth = lockdepth; + + /* register the lock store, and add our lock for "/foo" to it. */ + ne_lockstore_register(store, sess); + ne_lockstore_add(store, lk); + + ret = do_request(sess, reqpath, reqdepth, modparent); + CALL(await_server()); + + ne_lockstore_destroy(store); + ne_session_destroy(sess); + + return ret; +} + +static int if_simple(void) +{ + return submit_test("/foo", 0, "/foo", 0, 0); +} + +static int if_under_infinite(void) +{ + return submit_test("/foo", NE_DEPTH_INFINITE, "/foo/bar", 0, 0); +} + +static int if_infinite_over(void) +{ + return submit_test("/foo/bar", 0, "/foo/", NE_DEPTH_INFINITE, 0); +} + +static int if_child(void) +{ + return submit_test("/foo/", 0, "/foo/bar", 0, 1); +} + +/* this is a special test, where the PARENT resource of "/foo/bar" is + * modified, but NOT "/foo/bar" itself. An UNLOCK request on a + * lock-null resource can do this; see ne_unlock() for the comment. + * Regression test for neon <= 0.19.3, which didn't handle this + * correctly. */ +static int if_covered_child(void) +{ + return submit_test("/", NE_DEPTH_INFINITE, "/foo/bar", -1, 1); +} + +static int serve_discovery(ne_socket *sock, void *userdata) +{ + char buf[BUFSIZ], *resp = userdata; + + ON(discard_request(sock)); + ONN("no PROPFIND body", clength == 0); + ON(ne_sock_read(sock, buf, clength) < 0); + ON(SEND_STRING(sock, "HTTP/1.0 207 OK" EOL + "Connection: close" EOL EOL)); + ON(SEND_STRING(sock, resp)); + return OK; +} + +struct result_args { + struct ne_lock *lock; + int result; +}; + +static int lock_compare(const char *ctx, + const struct ne_lock *a, const struct ne_lock *b) +{ + ONV(!a->uri.host || !a->uri.scheme || !a->uri.path, + ("URI structure incomplete in %s", ctx)); + ONV(ne_uri_cmp(&a->uri, &b->uri) != 0, + ("URI comparison failed for %s: %s not %s", ctx, + ne_uri_unparse(&a->uri), ne_uri_unparse(&b->uri))); + ONV(a->depth != b->depth, + ("%s depth was %d not %d", ctx, a->depth, b->depth)); + ONV(a->scope != b->scope, + ("%s scope was %d not %d", ctx, a->scope, b->scope)); + ONV(a->type != b->type, + ("%s type was %d not %d", ctx, a->type, b->type)); + return OK; +} + +static void discover_result(void *userdata, const struct ne_lock *lk, + const char *path, const ne_status *st) +{ + struct result_args *args = userdata; + args->result = lock_compare("discovered lock", lk, args->lock); +} + +static int discover(void) +{ + ne_session *sess = ne_session_create("http", "localhost", 7777); + char *response; + int ret; + struct result_args args; + + args.lock = ne_lock_create(); + + args.lock->owner = ne_strdup("someowner"); + args.lock->token = ne_strdup("sometoken"); + + /* default */ + args.result = FAIL; + t_context("results callback never invoked"); + + ne_fill_server_uri(sess, &args.lock->uri); + args.lock->uri.path = ne_strdup("/lockme"); + + response = discover_response("/lockme", args.lock); + CALL(spawn_server(7777, serve_discovery, response)); + + ret = ne_lock_discover(sess, "/lockme", discover_result, &args); + CALL(await_server()); + ONREQ(ret); + + ne_lock_destroy(args.lock); + ne_session_destroy(sess); + + return args.result; +} + +/* Check that the token for the response header */ +static int lock_shared(void) +{ + ne_session *sess; + char *resp, *rbody; + struct ne_lock *lock, *resplocks[3]; + +#define FILLK(l, s) do { \ +(l)->token = strdup("opaquelocktoken:" s); \ +(l)->owner = strdup("owner " s); \ +(l)->uri.path = strdup("/" s); (l)->uri.host = strdup("localhost"); \ +(l)->uri.scheme = strdup("http"); (l)->uri.port = 7777; } while (0) + + resplocks[0] = ne_lock_create(); + resplocks[1] = ne_lock_create(); + resplocks[2] = NULL; + FILLK(resplocks[0], "alpha"); + FILLK(resplocks[1], "beta"); + resplocks[0]->timeout = 100; + resplocks[1]->timeout = 200; + + rbody = multi_lock_response(resplocks); + + resp = ne_concat("HTTP/1.1 200 OK\r\n" "Server: neon-test-server\r\n" + "Lock-Token: <opaquelocktoken:beta>" EOL + "Connection: close\r\n\r\n", rbody, NULL); + ne_free(rbody); + + CALL(make_session(&sess, single_serve_string, resp)); + ne_free(resp); + + lock = ne_lock_create(); + ne_fill_server_uri(sess, &lock->uri); + lock->uri.path = ne_strdup("/beta"); + + ONREQ(ne_lock(sess, lock)); + + CALL(await_server()); + + CALL(lock_compare("returned lock", resplocks[1], lock)); + + ne_session_destroy(sess); + ne_lock_destroy(lock); + ne_lock_destroy(resplocks[0]); + ne_lock_destroy(resplocks[1]); + + return OK; +} + +static void dummy_discover(void *userdata, const struct ne_lock *lock, + const char *uri, const ne_status *status) +{ +} + +/* This failed with neon 0.23.x and earlier when memory leak detection + * is enabled. */ +static int fail_discover(void) +{ + ne_session *sess; + int ret; + + CALL(make_session(&sess, single_serve_string, + "HTTP/1.0 207 OK\r\n" "Connection: close\r\n" "\r\n" + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + "<D:multistatus xmlns:D='DAV:'>\n" + "<D:response><D:href>/foo/bar</D:href><D:propstat>\n" + "</parse this, my friend>\n")); + + ret = ne_lock_discover(sess, "/foo", dummy_discover, NULL); + CALL(await_server()); + + ONN("discovery okay for response with invalid XML!?", ret != NE_ERROR); + + ne_session_destroy(sess); + return OK; +} + +ne_test tests[] = { + T(lookup_localhost), + T(store_single), + T(store_several), + T(if_simple), + T(if_under_infinite), + T(if_infinite_over), + T(if_child), + T(if_covered_child), + T(lock_timeout), + T(lock_shared), + T(discover), + T(fail_discover), + T(NULL) +}; + diff --git a/test/makekeys.sh b/test/makekeys.sh new file mode 100755 index 0000000..59a1e51 --- /dev/null +++ b/test/makekeys.sh @@ -0,0 +1,151 @@ +#!/bin/sh +# Helper script to create CA and server certificates. + +srcdir=${1-.} + +OPENSSL=${OPENSSL-openssl} +CONF=${srcdir}/openssl.conf +REQ="${OPENSSL} req -config ${CONF}" +CA="${OPENSSL} ca -config ${CONF} -batch" +# MKCERT makes a self-signed cert +MKCERT="${REQ} -x509 -new -days 900" + +REQDN=reqDN +export REQDN + +set -ex + +mkdir ca +touch ca/index.txt +echo 01 > ca/serial + +${OPENSSL} genrsa -rand ${srcdir}/../configure > ca/key.pem +${OPENSSL} genrsa -rand ${srcdir}/../configure > client.key + +${MKCERT} -key ca/key.pem -out ca/cert.pem <<EOF +US +California +Oakland +Neosign +Random Dept +nowhere.example.com +neon@webdav.org +EOF + +# Function to generate appropriate output for `openssl req'. +csr_fields() { +CN=${2-"localhost"} +OU=${1-"Neon QA Dept"} +Org=${3-"Neon Hackers Ltd"} +Locality=${4-"Cambridge"} +State=${5-"Cambridgeshire"} +cat <<EOF +GB +${State} +${Locality} +${Org} +${OU} +${CN} +neon@webdav.org +. +. +EOF +} + +csr_fields | ${REQ} -new -key ${srcdir}/server.key -out server.csr + +csr_fields "Upper Case Dept" lOcALhost | \ +${REQ} -new -key ${srcdir}/server.key -out caseless.csr + +csr_fields "Use AltName Dept" nowhere.example.com | \ +${REQ} -new -key ${srcdir}/server.key -out altname.csr + +csr_fields "Two AltName Dept" nowhere.example.com | \ +${REQ} -new -key ${srcdir}/server.key -out altname2.csr + +csr_fields "Third AltName Dept" nowhere.example.com | \ +${REQ} -new -key ${srcdir}/server.key -out altname3.csr + +csr_fields "Fourth AltName Dept" localhost | \ +${REQ} -new -key ${srcdir}/server.key -out altname4.csr + +csr_fields "Self-Signed" | \ +${MKCERT} -key ${srcdir}/server.key -out ssigned.pem + +csr_fields "Bad Hostname Department" nohost.example.com | \ +${MKCERT} -key ${srcdir}/server.key -out wrongcn.pem + +### produce a set of CA certs + +csr_fields "First Random CA" "first.example.com" "CAs Ltd." Lincoln Lincolnshire | \ +${MKCERT} -key ${srcdir}/server.key -out ca1.pem + +csr_fields "Second Random CA" "second.example.com" "CAs Ltd." Falmouth Cornwall | \ +${MKCERT} -key ${srcdir}/server.key -out ca2.pem + +csr_fields "Third Random CA" "third.example.com" "CAs Ltd." Ipswich Suffolk | \ +${MKCERT} -key ${srcdir}/server.key -out ca3.pem + +csr_fields "Fourth Random CA" "fourth.example.com" "CAs Ltd." Norwich Norfolk | \ +${MKCERT} -key ${srcdir}/server.key -out ca4.pem + +cat ca[1234].pem > calist.pem + +# Only works with a Linuxy hostname command: continue without it, +# as appropriate tests are skipped if these fail. +hostname=`hostname -s 2>/dev/null` || true +domain=`hostname -d 2>/dev/null` || true +fqdn=`hostname -f 2>/dev/null` || true +if [ "x${hostname}.${domain}" = "x${fqdn}" ]; then + csr_fields "Wildcard Cert Dept" "*.${domain}" | \ + ${REQ} -new -key ${srcdir}/server.key -out wildcard.csr + ${CA} -days 900 -in wildcard.csr -out wildcard.cert +fi + +csr_fields "Neon Client Cert" ignored.example.com | \ +${REQ} -new -key client.key -out client.csr + +### requests using special DN. + +REQDN=reqDN.doubleCN +csr_fields "Double CN Dept" "nohost.example.com +localhost" | ${REQ} -new -key ${srcdir}/server.key -out twocn.csr + +REQDN=reqDN.CNfirst +echo localhost | ${REQ} -new -key ${srcdir}/server.key -out cnfirst.csr + +REQDN=reqDN.missingCN +echo GB | ${REQ} -new -key ${srcdir}/server.key -out missingcn.csr + +REQDN=reqDN.justEmail +echo blah@example.com | ${REQ} -new -key ${srcdir}/server.key -out justmail.csr + +### don't put ${REQ} invocations after here + +for f in server client twocn caseless cnfirst missingcn justmail; do + ${CA} -days 900 -in ${f}.csr -out ${f}.cert +done + +${CA} -extensions altExt -days 900 -in altname.csr -out altname.cert +${CA} -extensions altExt2 -days 900 -in altname2.csr -out altname2.cert +${CA} -extensions altExt3 -days 900 -in altname3.csr -out altname3.cert +${CA} -extensions altExt4 -days 900 -in altname4.csr -out altname4.cert + +# generate a PKCS12 cert from the client cert: -passOUT because it's the +# passphrase on the OUTPUT cert, confusing... +echo foobar | ${OPENSSL} pkcs12 -export -passout stdin \ + -name "Just A Neon Client Cert" \ + -in client.cert -inkey client.key -out client.p12 + +# generate a PKCS12 cert with no password +echo | ${OPENSSL} pkcs12 -export -passout stdin \ + -name "An Unencrypted Neon Client Cert" \ + -in client.cert -inkey client.key -out unclient.p12 + +# generate a PKCS12 cert with no friendly name +echo | ${OPENSSL} pkcs12 -export -passout stdin \ + -in client.cert -inkey client.key -out noclient.p12 + +### a file containing a complete chain + +cat ca/cert.pem server.cert > chain.pem diff --git a/test/notvalid.pem b/test/notvalid.pem new file mode 100644 index 0000000..42008b3 --- /dev/null +++ b/test/notvalid.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDODCCAuKgAwIBAgIBADANBgkqhkiG9w0BAQQFADCBoTELMAkGA1UEBhMCR0Ix +FzAVBgNVBAgTDkNhbWJyaWRnZXNoaXJlMRIwEAYDVQQHEwlDYW1icmlkZ2UxGjAY +BgNVBAoTEU5lb24gSGFja2VycyBMdGQuMRUwEwYDVQQLEwxOZW9uIFFBIERlcHQx +EjAQBgNVBAMTCWxvY2FsaG9zdDEeMBwGCSqGSIb3DQEJARYPbmVvbkB3ZWJkYXYu +b3JnMB4XDTIzMTIyNzIwNDAyOVoXDTIzMTIyODIwNDAyOVowgaExCzAJBgNVBAYT +AkdCMRcwFQYDVQQIEw5DYW1icmlkZ2VzaGlyZTESMBAGA1UEBxMJQ2FtYnJpZGdl +MRowGAYDVQQKExFOZW9uIEhhY2tlcnMgTHRkLjEVMBMGA1UECxMMTmVvbiBRQSBE +ZXB0MRIwEAYDVQQDEwlsb2NhbGhvc3QxHjAcBgkqhkiG9w0BCQEWD25lb25Ad2Vi +ZGF2Lm9yZzBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQDzRU5sZ8+CWQPvPkqJw9Kl +oEgT2FqzZR9RT/qbJuRBmRphiRr0g7JOh5Mr7LXaKShedFLhGidutyKKwIZJnRht +AgMBAAGjggEBMIH+MB0GA1UdDgQWBBRFA3ktzHSuD9uB6mJOWoElmOtknzCBzgYD +VR0jBIHGMIHDgBRFA3ktzHSuD9uB6mJOWoElmOtkn6GBp6SBpDCBoTELMAkGA1UE +BhMCR0IxFzAVBgNVBAgTDkNhbWJyaWRnZXNoaXJlMRIwEAYDVQQHEwlDYW1icmlk +Z2UxGjAYBgNVBAoTEU5lb24gSGFja2VycyBMdGQuMRUwEwYDVQQLEwxOZW9uIFFB +IERlcHQxEjAQBgNVBAMTCWxvY2FsaG9zdDEeMBwGCSqGSIb3DQEJARYPbmVvbkB3 +ZWJkYXYub3JnggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADQQA80TYV +2F4QLveuldmxGoIOq5hHGxCR6aVsdtm4PGY49R5/ObCAgdWw/JV/Tc448JAz5QvU +ahr1x9kA4Vo5NZ4q +-----END CERTIFICATE----- diff --git a/test/openssl.conf b/test/openssl.conf new file mode 100644 index 0000000..9180b49 --- /dev/null +++ b/test/openssl.conf @@ -0,0 +1,77 @@ +[ca] +default_ca = neonca + +[neonca] +dir = ./ca +database = $dir/index.txt +new_certs_dir = $dir +certificate = $dir/cert.pem +serial = $dir/serial +private_key = $dir/key.pem +policy = policy_any +default_md = md5 +x509_extensions = issuedExt + +[policy_any] +countryName = optional +stateOrProvinceName = optional +localityName = optional +organizationName = optional +organizationalUnitName = optional +commonName = optional +emailAddress = optional + +[req] +distinguished_name = $ENV::REQDN +x509_extensions = caExt + +[caExt] +basicConstraints = CA:true + +[issuedExt] +basicConstraints = CA:false + +# subjectAltName extension sections +[altExt] +subjectAltName = DNS:localhost + +# 2+3: AltNames with multiple entries to test the matching logic +[altExt2] +subjectAltName = DNS:nohost.example.com, DNS:localhost + +[altExt3] +subjectAltName = DNS:localhost, DNS:nohost.example.com + +# an AltName with no DNS entries; should use commonName instead for +# identity check +[altExt4] +subjectAltName = email:neon@webdav.org + +[reqDN] +countryName = Country Name +stateOrProvinceName = State or Province Name +localityName = Locality Name +organizationName = Organization Name +organizationalUnitName = Organizational Unit Name +commonName = Common Name (eg, your name or your server\'s hostname) +emailAddress = Email Address + +# a DN which gives two commonName attributes. +[reqDN.doubleCN] +countryName = Country Name +stateOrProvinceName = State or Province Name +localityName = Locality Name +organizationName = Organization Name +organizationalUnitName = Organizational Unit Name +0.commonName = Common Name +1.commonName = Common Name +emailAddress = Email Address + +[reqDN.CNfirst] +commonName = Common Name + +[reqDN.missingCN] +countryName = CountryName + +[reqDN.justEmail] +emailAddress = CountryName diff --git a/test/props.c b/test/props.c new file mode 100644 index 0000000..61d3c22 --- /dev/null +++ b/test/props.c @@ -0,0 +1,508 @@ +/* + Tests for property handling + Copyright (C) 2002-2003, Joe Orton <joe@manyfish.co.uk> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "config.h" + +#include <sys/types.h> + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "ne_props.h" + +#include "tests.h" +#include "child.h" +#include "utils.h" + +static const ne_propname p_alpha = {"DAV:", "alpha"}, + p_beta = {"http://webdav.org/random/namespace", "beta"}, + p_delta = {NULL, "delta"}; + +/* Tests little except that ne_proppatch() doesn't segfault. */ +static int patch_simple(void) +{ + ne_session *sess; + ne_proppatch_operation ops[] = { + { &p_alpha, ne_propset, "fish" }, + { &p_beta, ne_propremove, NULL }, + { NULL, ne_propset, NULL } + }; + + CALL(make_session(&sess, single_serve_string, + "HTTP/1.1 200 Goferit\r\n" + "Connection: close\r\n\r\n")); + ONREQ(ne_proppatch(sess, "/fish", ops)); + ne_session_destroy(sess); + return await_server(); +} + +#define RESP207 "HTTP/1.0 207 Stuff\r\n" "Server: foo\r\n\r\n" + +static void dummy_results(void *ud, const char *href, + const ne_prop_result_set *rset) +{ + NE_DEBUG(NE_DBG_HTTP, "dummy_results.\n"); +} + +/* Regression tests for propfind bodies which caused segfaults. */ +static int regress(void) +{ + static const char *bodies[] = { + RESP207 "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + "<multistatus xmlns=\"DAV:\">" + "<response><propstat><prop><href>" + "</href></prop></propstat></response>" + "</multistatus>", + + /* segfaults with neon <= 0.23.5 */ + RESP207 "<?xml version=\"1.0\"?><D:multistatus xmlns:D=\"DAV:\">" + "<D:response><D:href>/foo/</D:href>" + "<D:propstat/>" + "<D:status>HTTP/1.1 404 Not Found</D:status>" + "</D:multistatus>", + NULL, + }; + ne_session *sess; + int n; + + for (n = 0; bodies[n] != NULL; n++) { + CALL(make_session(&sess, single_serve_string, (void *)bodies[n])); + ne_simple_propfind(sess, "/", 0, NULL, dummy_results, NULL); + ne_session_destroy(sess); + CALL(await_server()); + } + + return OK; +} + +static int pstat_count; + +/* tos_*: set of 207 callbacks which serialize the data back into a + * text stream, which can be easily checked for correctness. */ +static void *tos_startresp(void *buf, const char *href) +{ + ne_buffer_concat(buf, "start-resp[", href, "];", NULL); + pstat_count = 0; + return ne_strdup(href); +} + +static void tos_status_descr(ne_buffer *buf, const ne_status *status, + const char *description) +{ + if (status) { + char s[50]; + ne_snprintf(s, sizeof s, "-status={%d %s}", status->code, + status->reason_phrase); + ne_buffer_zappend(buf, s); + } + if (description) + ne_buffer_concat(buf, "-descr={", description, "}", NULL); +} + +static void tos_endresp(void *buf, void *response, + const ne_status *status, const char *description) +{ + char *href = response; + ne_buffer_concat(buf, "end-resp[", href, "]", NULL); + ne_free(href); + tos_status_descr(buf, status, description); + ne_buffer_zappend(buf, ";"); +} + +static void *tos_startpstat(void *buf, void *resphref) +{ + char num[20], *href; + sprintf(num, "-%d", ++pstat_count); + href = ne_concat(resphref, num, NULL); + ne_buffer_concat(buf, "start-pstat[", href, "];", NULL); + return href; +} + +static void tos_endpstat(void *buf, void *href, + const ne_status *status, const char *description) +{ + ne_buffer_concat(buf, "end-pstat[", href, "]", NULL); + tos_status_descr(buf, status, description); + ne_buffer_zappend(buf, ";"); + ne_free(href); +} + +struct propctx { + ne_207_parser *p207; + ne_buffer *buf; +}; + +#define STATE_myprop (NE_PROPS_STATE_TOP) + +static int tos_startprop(void *userdata, int parent, + const char *nspace, const char *name, + const char **atts) +{ + if (parent == NE_207_STATE_PROP && + strcmp(nspace, "DAV:") == 0 && + (strcmp(name, "propone") == 0 || strcmp(name, "proptwo") == 0)) { + /* Handle this! */ + struct propctx *ctx = userdata; + char *resphref = ne_207_get_current_response(ctx->p207); + char *pstathref = ne_207_get_current_propstat(ctx->p207); + + ne_buffer_concat(ctx->buf, "start-prop[", resphref, ",", pstathref, + ",", name, "];", NULL); + + return STATE_myprop; + } else { + return NE_XML_DECLINE; + } +} + +static int tos_cdata(void *userdata, int state, + const char *cdata, size_t len) +{ + struct propctx *ctx = userdata; + + ne_buffer_zappend(ctx->buf, "cdata-prop["); + ne_buffer_append(ctx->buf, cdata, len); + ne_buffer_zappend(ctx->buf, "];"); + return 0; +} + +static int tos_endprop(void *userdata, int state, + const char *nspace, const char *name) +{ + struct propctx *ctx = userdata; + + ne_buffer_concat(ctx->buf, "end-prop[", name, "];", NULL); + return 0; +} + + +static int run_207_response(char *resp, const char *expected) +{ + ne_buffer *buf = ne_buffer_create(); + ne_session *sess = ne_session_create("http", "localhost", 7777); + ne_xml_parser *p = ne_xml_create(); + ne_207_parser *p207 = ne_207_create(p, buf); + ne_request *req = ne_request_create(sess, "PROPFIND", "/foo"); + struct propctx ctx; + + ne_add_response_body_reader(req, ne_accept_207, ne_xml_parse_v, p); + + ne_207_set_response_handlers(p207, tos_startresp, tos_endresp); + ne_207_set_propstat_handlers(p207, tos_startpstat, tos_endpstat); + + ctx.buf = buf; + ctx.p207 = p207; + ne_xml_push_handler(p, tos_startprop, tos_cdata, tos_endprop, &ctx); + + CALL(spawn_server(7777, single_serve_string, resp)); + + ONREQ(ne_request_dispatch(req)); + + CALL(await_server()); + + ONV(!ne_xml_valid(p), + ("response body was invalid: %s", ne_xml_get_error(p))); + + ONV(strcmp(buf->data, expected), + ("comparison failed.\n" + "expected string: `%s'\n" + "got string: `%s'", expected, buf->data)); + + ne_buffer_destroy(buf); + ne_207_destroy(p207); + ne_xml_destroy(p); + ne_request_destroy(req); + ne_session_destroy(sess); + return OK; +} + +/* Macros for easily writing a 207 response body; all expand to + * a string literal. */ +#define MULTI_207(x) "HTTP/1.0 207 Foo\r\nConnection: close\r\n\r\n" \ +"<?xml version='1.0'?>\r\n" \ +"<D:multistatus xmlns:D='DAV:'>" x "</D:multistatus>" +#define RESP_207(href, x) "<D:response><D:href>" href "</D:href>" x \ +"</D:response>" +#define PSTAT_207(x) "<D:propstat>" x "</D:propstat>" +#define STAT_207(s) "<D:status>HTTP/1.1 " s "</D:status>" +#define DESCR_207(d) "<D:responsedescription>" d "</D:responsedescription>" +#define DESCR_REM "The end of the world, as we know it" + +#define PROPS_207(x) "<D:prop>" x "</D:prop>" +#define APROP_207(n, c) "<D:" n ">" c "</D:" n ">" + +/* Tests for the 207 interface: send a 207 response body, compare the + * re-serialized string returned with that expected. */ +static int two_oh_seven(void) +{ + static char *ts[][2] = { + { MULTI_207(RESP_207("/foo", "")), + "start-resp[/foo];end-resp[/foo];" }, + + /* test for response status handling */ + { MULTI_207(RESP_207("/bar", STAT_207("200 OK"))), + "start-resp[/bar];end-resp[/bar]-status={200 OK};" }, + + /* test that empty description == NULL description argument */ + { MULTI_207(RESP_207("/bar", STAT_207("200 OK") DESCR_207(""))), + "start-resp[/bar];end-resp[/bar]-status={200 OK};" }, + + /* test multiple responses */ + { MULTI_207(RESP_207("/hello/world", STAT_207("200 OK")) + RESP_207("/foo/bar", STAT_207("999 French Fries"))), + "start-resp[/hello/world];end-resp[/hello/world]-status={200 OK};" + "start-resp[/foo/bar];end-resp[/foo/bar]" + "-status={999 French Fries};" + }, + + /* test multiple propstats in mulitple responses */ + { MULTI_207(RESP_207("/al/pha", + PSTAT_207(STAT_207("321 Une")) + PSTAT_207(STAT_207("432 Deux")) + PSTAT_207(STAT_207("543 Trois"))) + RESP_207("/be/ta", + PSTAT_207(STAT_207("787 Quatre")) + PSTAT_207(STAT_207("878 Cinq")))), + "start-resp[/al/pha];" + "start-pstat[/al/pha-1];end-pstat[/al/pha-1]-status={321 Une};" + "start-pstat[/al/pha-2];end-pstat[/al/pha-2]-status={432 Deux};" + "start-pstat[/al/pha-3];end-pstat[/al/pha-3]-status={543 Trois};" + "end-resp[/al/pha];" + "start-resp[/be/ta];" + "start-pstat[/be/ta-1];end-pstat[/be/ta-1]-status={787 Quatre};" + "start-pstat[/be/ta-2];end-pstat[/be/ta-2]-status={878 Cinq};" + "end-resp[/be/ta];" + }, + + /* test that incomplete responses are completely ignored. */ + { MULTI_207("<D:response/>" + RESP_207("/", STAT_207("123 Hoorah")) + "<D:response/>" + "<D:response><D:propstat>hello</D:propstat></D:response>" + "<D:response><D:href/></D:response>" + RESP_207("/bar", STAT_207("200 OK"))), + "start-resp[/];end-resp[/]-status={123 Hoorah};" + "start-resp[/bar];end-resp[/bar]-status={200 OK};" }, + + /* tests for propstat status */ + { MULTI_207(RESP_207("/pstat", + PSTAT_207("<D:prop/>" STAT_207("666 Doomed")))), + "start-resp[/pstat];start-pstat[/pstat-1];" + "end-pstat[/pstat-1]-status={666 Doomed};end-resp[/pstat];" }, + + { MULTI_207(RESP_207("/pstat", PSTAT_207("<D:status/>"))), + "start-resp[/pstat];start-pstat[/pstat-1];" + "end-pstat[/pstat-1];end-resp[/pstat];" }, + + /* tests for responsedescription handling */ + { MULTI_207(RESP_207("/bar", STAT_207("200 OK") DESCR_207(DESCR_REM))), + "start-resp[/bar];end-resp[/bar]-status={200 OK}" + "-descr={" DESCR_REM "};" }, + + { MULTI_207(RESP_207("/bar", + PSTAT_207(STAT_207("456 Too Hungry") + DESCR_207("Not enough food available")) + STAT_207("200 OK") DESCR_207("Not " DESCR_REM))), + "start-resp[/bar];" + "start-pstat[/bar-1];end-pstat[/bar-1]-status={456 Too Hungry}" + "-descr={Not enough food available};" + "end-resp[/bar]-status={200 OK}-descr={Not " DESCR_REM "};" }, + + /* intermingle some random elements and cdata to make sure + * they are ignored. */ + { MULTI_207("<D:fish-food/>blargl" + RESP_207("/b<ping-pong/>ar", "<D:sausages/>" + PSTAT_207("<D:hello-mum/>blergl") + STAT_207("200 <pong-ping/> OK") "foop" + DESCR_207(DESCR_REM) "carroon") + "carapi"), + "start-resp[/bar];start-pstat[/bar-1];end-pstat[/bar-1];" + "end-resp[/bar]-status={200 OK}-descr={" DESCR_REM "};" }, + + /* test for properties within a 207. */ + { MULTI_207(RESP_207("/alpha", + PSTAT_207(PROPS_207( + APROP_207("propone", "hello") + APROP_207("proptwo", "foobar")) + STAT_207("200 OK")))), + "start-resp[/alpha];start-pstat[/alpha-1];" + "start-prop[/alpha,/alpha-1,propone];cdata-prop[hello];" + "end-prop[propone];" + "start-prop[/alpha,/alpha-1,proptwo];cdata-prop[foobar];" + "end-prop[proptwo];" + "end-pstat[/alpha-1]-status={200 OK};end-resp[/alpha];" } + + }; + size_t n; + + for (n = 0; n < sizeof(ts)/sizeof(ts[0]); n++) + CALL(run_207_response(ts[n][0], ts[n][1])); + + return OK; +} + +/* Serialize propfind result callbacks into a string */ +static int simple_iterator(void *buf, const ne_propname *name, + const char *value, const ne_status *st) +{ + char code[20]; + ne_buffer_concat(buf, "prop:[{", name->nspace, ",", + name->name, "}=", NULL); + if (value) + ne_buffer_concat(buf, "'", value, "'", NULL); + else + ne_buffer_zappend(buf, "#novalue#"); + sprintf(code, ":{%d ", st->code); + if (st->reason_phrase) + ne_buffer_concat(buf, code, st->reason_phrase, "}];", NULL); + else + ne_buffer_concat(buf, code, "#noreason#}];", NULL); + return 0; +} + +static void simple_results(void *buf, const char *href, + const ne_prop_result_set *rset) +{ + ne_buffer_concat(buf, "results(", href, ",", NULL); + ne_propset_iterate(rset, simple_iterator, buf); + ne_buffer_zappend(buf, ")//"); +} + +static int diffcmp(const char *expected, const char *actual) +{ + size_t n; + + if (!strcmp(expected, actual)) return OK; + + for (n = 0; expected[n] && actual[n]; n++) { + if (expected[n] != actual[n]) { + t_context("difference at byte %" NE_FMT_SIZE_T ": " + "`%.6s...' not `%.6s...'", + n, actual+n, expected+n); + break; + } + } + + return FAIL; +} + + +static int run_simple_propfind(const ne_propname *props, char *resp, + int depth, const char *expected) +{ + ne_session *sess = ne_session_create("http", "localhost", 7777); + ne_buffer *buf = ne_buffer_create(); + + CALL(spawn_server(7777, single_serve_string, resp)); + + ONREQ(ne_simple_propfind(sess, "/propfind", depth, props, + simple_results, buf)); + + CALL(await_server()); + + CALL(diffcmp(expected, buf->data)); + + ne_buffer_destroy(buf); + ne_session_destroy(sess); + return OK; +} + +/* a PROPFIND response body for the {DAV:}fishbone property, using + * given property value and status. */ +#define FISHBONE_RESP(value, status) MULTI_207(RESP_207("/foop", \ + PSTAT_207(PROPS_207(APROP_207("fishbone", value)) \ + STAT_207(status)))) + +static int pfind_simple(void) +{ + static const struct { + char *resp; + const char *expected; + int depth, pset; + } ts[] = { + /* simple single property. */ + { FISHBONE_RESP("hello, world", "212 Well OK"), + "results(/foop,prop:[{DAV:,fishbone}='hello, world':{212 Well OK}];)//", + 0, 0 }, + /* property with some nested elements. */ + { FISHBONE_RESP("this is <foo/> a property <bar><lemon>fish</lemon></bar> value", + "299 Just About OK"), + "results(/foop,prop:[{DAV:,fishbone}=" + "'this is <foo></foo> a property " + "<bar><lemon>fish</lemon></bar> value':" + "{299 Just About OK}];)//", + 0, 0 }, + + /* failed to fetch a property. */ + { FISHBONE_RESP("property value is ignored", + "404 Il n'ya pas de property"), + "results(/foop,prop:[{DAV:,fishbone}=#novalue#:" + "{404 Il n'ya pas de property}];)//", + 0, 0 }, + +#if 0 + /* propstat missing status should be ignored; if a response contains no + * valid propstats, it should also be ignored. */ + { MULTI_207(RESP_207("/alpha", PSTAT_207(APROP_207("fishbone", "unseen"))) + RESP_207("/beta", PSTAT_207(APROP_207("fishbone", "hello, world") + STAT_207("200 OK")))), + "results(/beta,prop:[{DAV:,fishbone}='hello, world':{200 OK}];)//", 0, 0}, +#endif + + /* props on several resources */ + { MULTI_207(RESP_207("/alpha", + PSTAT_207(PROPS_207(APROP_207("fishbone", "strike one")) + STAT_207("234 First is OK"))) + RESP_207("/beta", + PSTAT_207(PROPS_207(APROP_207("fishbone", "strike two")) + STAT_207("256 Second is OK")))), + "results(/alpha,prop:[{DAV:,fishbone}='strike one':{234 First is OK}];)//" + "results(/beta,prop:[{DAV:,fishbone}='strike two':{256 Second is OK}];)//", + 0, 0} + }; + const ne_propname pset1[] = { + { "DAV:", "fishbone", }, + { NULL, NULL } + }; + size_t n; + + for (n = 0; n < sizeof(ts)/sizeof(ts[0]); n++) { + const ne_propname *pset = pset1; + + CALL(run_simple_propfind(pset, ts[n].resp, ts[n].depth, + ts[n].expected)); + } + + + return OK; +} + +ne_test tests[] = { + T(two_oh_seven), + T(patch_simple), + T(pfind_simple), + T(regress), + T(NULL) +}; + diff --git a/test/redirect.c b/test/redirect.c new file mode 100644 index 0000000..80b23bb --- /dev/null +++ b/test/redirect.c @@ -0,0 +1,189 @@ +/* + Tests for 3xx redirect interface (ne_redirect.h) + Copyright (C) 2002-2003, Joe Orton <joe@manyfish.co.uk> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "config.h" + +#include <sys/types.h> + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "ne_redirect.h" + +#include "tests.h" +#include "child.h" +#include "utils.h" + +struct redir_args { + int code; + const char *dest; + const char *path; +}; + +static int serve_redir(ne_socket *sock, void *ud) +{ + struct redir_args *args = ud; + char buf[BUFSIZ]; + + CALL(discard_request(sock)); + + ne_snprintf(buf, BUFSIZ, + "HTTP/1.0 %d Get Ye Away\r\n" + "Content-Length: 0\r\n" + "Location: %s\r\n\n", + args->code, args->dest); + + SEND_STRING(sock, buf); + + return OK; +} + +/* Run a request to 'path' and retrieve the redirect destination to + * *redir. */ +static int process_redir(ne_session *sess, const char *path, + const ne_uri **redir) +{ + ONN("did not get NE_REDIRECT", any_request(sess, path) != NE_REDIRECT); + *redir = ne_redirect_location(sess); + return OK; +} + +static int check_redir(struct redir_args *args, const char *expect) +{ + ne_session *sess; + const ne_uri *loc; + char *unp; + + CALL(make_session(&sess, serve_redir, args)); + ne_redirect_register(sess); + + CALL(process_redir(sess, args->path, &loc)); + ONN("redirect location was NULL", loc == NULL); + + unp = ne_uri_unparse(loc); + ONV(strcmp(unp, expect), ("redirected to `%s' not `%s'", unp, expect)); + ne_free(unp); + + ne_session_destroy(sess); + CALL(await_server()); + + return OK; +} + +#define DEST "http://foo.com/blah/blah/bar" +#define PATH "/redir/me" + +static int simple(void) +{ + struct redir_args args[] = { + {301, DEST, PATH}, + {302, DEST, PATH}, + {303, DEST, PATH}, + {307, DEST, PATH}, + {0, NULL, NULL} + }; + int n; + + for (n = 0; args[n].code; n++) + CALL(check_redir(&args[n], DEST)); + + return OK; +} + +/* check that a non-absoluteURI is qualified properly */ +static int non_absolute(void) +{ + struct redir_args args = {302, "/foo/bar/blah", PATH}; + return check_redir(&args, "http://localhost:7777/foo/bar/blah"); +} + +static int relative_1(void) +{ + struct redir_args args = {302, "norman", "/foo/bar"}; + return check_redir(&args, "http://localhost:7777/foo/norman"); +} + +static int relative_2(void) +{ + struct redir_args args = {302, "wishbone", "/foo/bar/"}; + return check_redir(&args, "http://localhost:7777/foo/bar/wishbone"); +} + +#if 0 +/* could implement failure on self-referential redirects, but + * realistically, the application must implement a max-redirs count + * check, so it's kind of redundant. Mozilla takes this approach. */ +static int fail_loop(void) +{ + ne_session *sess; + + CALL(make_session(&sess, serve_redir, "http://localhost:7777/foo/bar")); + + ne_redirect_register(sess); + + ONN("followed looping redirect", + any_request(sess, "/foo/bar") != NE_ERROR); + + ne_session_destroy(sess); + return OK; +} +#endif + +/* ensure that ne_redirect_location returns NULL when no redirect has + * been encountered, or redirect hooks aren't registered. */ +static int no_redirect(void) +{ + ne_session *sess = ne_session_create("http", "localhost", 7777); + const ne_uri *loc; + + ONN("redirect non-NULL before register", ne_redirect_location(sess)); + ne_redirect_register(sess); + ONN("initial redirect non-NULL", ne_redirect_location(sess)); + + CALL(spawn_server(7777, single_serve_string, + "HTTP/1.0 200 OK\r\n\r\n\r\n")); + ONREQ(any_request(sess, "/noredir")); + CALL(await_server()); + + ONN("redirect non-NULL after non-redir req", ne_redirect_location(sess)); + + CALL(spawn_server(7777, single_serve_string, "HTTP/1.0 302 Get Ye Away\r\n" + "Location: /blah\r\n" "\r\n")); + CALL(process_redir(sess, "/foo", &loc)); + CALL(await_server()); + + ne_session_destroy(sess); + return OK; +} + +ne_test tests[] = { + T(lookup_localhost), + T(simple), + T(non_absolute), + T(relative_1), + T(relative_2), + T(no_redirect), + T(NULL) +}; + diff --git a/test/request.c b/test/request.c new file mode 100644 index 0000000..ed3029f --- /dev/null +++ b/test/request.c @@ -0,0 +1,1649 @@ +/* + HTTP request handling tests + Copyright (C) 2001-2003, Joe Orton <joe@manyfish.co.uk> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "config.h" + +#include <sys/types.h> + +#include <time.h> /* for time() */ + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "ne_request.h" +#include "ne_socket.h" + +#include "tests.h" +#include "child.h" +#include "utils.h" + +static char buffer[BUFSIZ]; + +static ne_session *def_sess; +static ne_request *def_req; + +static int prepare_request(server_fn fn, void *ud) +{ + static char uri[100]; + + def_sess = ne_session_create("http", "localhost", 7777); + + sprintf(uri, "/test%d", test_num); + + def_req = ne_request_create(def_sess, "GET", uri); + + CALL(spawn_server(7777, fn, ud)); + + return OK; +} + +static int finish_request(void) +{ + ne_request_destroy(def_req); + ne_session_destroy(def_sess); + return await_server(); +} + +#define RESP200 "HTTP/1.1 200 OK\r\n" "Server: neon-test-server\r\n" +#define TE_CHUNKED "Transfer-Encoding: chunked\r\n" + +/* takes response body chunks and appends them to a buffer. */ +static void collector(void *ud, const char *data, size_t len) +{ + ne_buffer *buf = ud; + ne_buffer_append(buf, data, len); +} + +typedef ne_request *(*construct_request)(ne_session *sess, void *userdata); + +/* construct a get request, callback for run_request. */ +static ne_request *construct_get(ne_session *sess, void *userdata) +{ + ne_request *r = ne_request_create(sess, "GET", "/"); + ne_buffer *buf = userdata; + + ne_add_response_body_reader(r, ne_accept_2xx, collector, buf); + + return r; +} + +/* run a request created by callback 'cb' in session 'sess'. */ +static int run_request(ne_session *sess, int status, + construct_request cb, void *userdata) +{ + ne_request *req = cb(sess, userdata); + + ON(req == NULL); + + ONREQ(ne_request_dispatch(req)); + + ONV(ne_get_status(req)->code != status, + ("response status-code was %d not %d", + ne_get_status(req)->code, status)); + + ne_request_destroy(req); + + return OK; +} + +/* Runs a server function 'fn', expecting to get a header 'name' with value + * 'value' in the response. */ +static int expect_header_value(const char *name, const char *value, + server_fn fn, void *userdata) +{ + ne_session *sess; + ne_request *req; + char *gotval = NULL; + + CALL(make_session(&sess, fn, userdata)); + + req = ne_request_create(sess, "FOO", "/bar"); + ne_add_response_header_handler(req, name, ne_duplicate_header, &gotval); + ONREQ(ne_request_dispatch(req)); + CALL(await_server()); + + ONN("no header value set", gotval == NULL); + ONV(strcmp(gotval, value), + ("header value mis-match: got [%s] not [%s]", gotval, value)); + + ne_request_destroy(req); + ne_session_destroy(sess); + ne_free(gotval); + + return OK; +} + +/* runs a server function 'fn', expecting response body to be equal to + * 'expect' */ +static int expect_response(const char *expect, server_fn fn, void *userdata) +{ + ne_session *sess = ne_session_create("http", "localhost", 7777); + ne_buffer *buf = ne_buffer_create(); + + ON(sess == NULL || buf == NULL); + ON(spawn_server(7777, fn, userdata)); + + CALL(run_request(sess, 200, construct_get, buf)); + + ON(await_server()); + + ONN("response body match", strcmp(buf->data, expect)); + + ne_session_destroy(sess); + ne_buffer_destroy(buf); + + return OK; +} + +#define EMPTY_RESP RESP200 "Content-Length: 0\r\n\r\n" + +/* Process a request with given method and response, expecting to get + * a zero-length response body. A second request is sent down the + * connection (to ensure that the response isn't silently eaten), so + * 'resp' must be an HTTP/1.1 response with no 'Connection: close' + * header. */ +static int expect_no_body(const char *method, const char *resp) +{ + ne_session *sess = ne_session_create("http", "localhost", 7777); + ne_request *req = ne_request_create(sess, method, "/first"); + ssize_t ret; + char *r = ne_malloc(strlen(resp) + sizeof(EMPTY_RESP)); + + strcpy(r, resp); + strcat(r, EMPTY_RESP); + ON(spawn_server(7777, single_serve_string, r)); + ne_free(r); + + ONN("failed to begin request", ne_begin_request(req)); + ret = ne_read_response_block(req, buffer, BUFSIZ); + ONV(ret != 0, ("got response block of size %" NE_FMT_SSIZE_T, ret)); + ONN("failed to end request", ne_end_request(req)); + + /* process following request; makes sure that nothing extra has + * been eaten by the first request. */ + ONV(any_request(sess, "/second"), + ("second request on connection failed: %s",ne_get_error(sess))); + + ON(await_server()); + + ne_request_destroy(req); + ne_session_destroy(sess); + return OK; +} + +static int reason_phrase(void) +{ + ne_session *sess; + + CALL(make_session(&sess, single_serve_string, RESP200 + "Connection: close\r\n\r\n")); + CALL(any_request(sess, "/foo")); + CALL(await_server()); + + ONV(strcmp(ne_get_error(sess), "200 OK"), + ("reason phrase mismatch: got `%s' not `200 OK'", + ne_get_error(sess))); + + ne_session_destroy(sess); + return OK; +} + +static int single_get_eof(void) +{ + return expect_response("a", single_serve_string, + RESP200 + "Connection: close\r\n" + "\r\n" + "a"); +} + +static int single_get_clength(void) +{ + return expect_response("a", single_serve_string, + RESP200 + "Content-Length: 1\r\n" + "\r\n" + "a" + "bbbbbbbbasdasd"); +} + +static int single_get_chunked(void) +{ + return expect_response("a", single_serve_string, + RESP200 TE_CHUNKED + "\r\n" + "1\r\n" + "a\r\n" + "0\r\n" "\r\n" + "g;lkjalskdjalksjd"); +} + +static int no_body_304(void) +{ + return expect_no_body("GET", "HTTP/1.1 304 Not Mfodified\r\n" + "Content-Length: 5\r\n\r\n"); +} + +static int no_body_204(void) +{ + return expect_no_body("GET", "HTTP/1.1 204 Not Modified\r\n" + "Content-Length: 5\r\n\r\n"); +} + +static int no_body_HEAD(void) +{ + return expect_no_body("HEAD", "HTTP/1.1 200 OK\r\n" + "Content-Length: 5\r\n\r\n"); +} + +static int no_body_empty_clength(void) +{ + return expect_no_body("GET", "HTTP/1.1 200 OK\r\n" + "Content-Length:\r\n\r\n"); +} + +static int no_body_bad_clength(void) +{ + return expect_no_body("GET", "HTTP/1.1 200 OK\r\n" + "Content-Length: foobar\r\n\r\n"); +} + +static int no_headers(void) +{ + return expect_response("abcde", single_serve_string, + "HTTP/1.1 200 OK\r\n\r\n" + "abcde"); +} + +#define CHUNK(len, data) #len "\r\n" data "\r\n" + +#define ABCDE_CHUNKS CHUNK(1, "a") CHUNK(1, "b") \ + CHUNK(1, "c") CHUNK(1, "d") \ + CHUNK(1, "e") CHUNK(0, "") + +static int chunks(void) +{ + /* lots of little chunks. */ + return expect_response("abcde", single_serve_string, + RESP200 TE_CHUNKED + "\r\n" + ABCDE_CHUNKS); +} + +static int te_header(void) +{ + return expect_response("abcde", single_serve_string, + RESP200 "Transfer-Encoding: CHUNKED\r\n" + "\r\n" ABCDE_CHUNKS); +} + +/* test that the presence of *any* t-e header implies a chunked + * response. */ +static int any_te_header(void) +{ + return expect_response("abcde", single_serve_string, RESP200 + "Transfer-Encoding: punked\r\n" "\r\n" + ABCDE_CHUNKS); +} + +static int chunk_numeric(void) +{ + /* leading zero's */ + return expect_response("0123456789abcdef", single_serve_string, + RESP200 TE_CHUNKED + "\r\n" + "000000010\r\n" "0123456789abcdef\r\n" + "000000000\r\n" "\r\n"); +} + +static int chunk_extensions(void) +{ + /* chunk-extensions. */ + return expect_response("0123456789abcdef", single_serve_string, + RESP200 TE_CHUNKED + "\r\n" + "000000010; foo=bar; norm=fish\r\n" + "0123456789abcdef\r\n" + "000000000\r\n" "\r\n"); +} + +static int chunk_trailers(void) +{ + /* trailers. */ + return expect_response("abcde", single_serve_string, + RESP200 TE_CHUNKED + "\r\n" + "00000005; foo=bar; norm=fish\r\n" + "abcde\r\n" + "000000000\r\n" + "X-Hello: world\r\n" + "X-Another: header\r\n" + "\r\n"); +} + +static int chunk_oversize(void) +{ +#define BIG (20000) + char *body = ne_malloc(BIG + 1); + static const char rnd[] = "abcdefghijklm"; + int n; + ne_buffer *buf = ne_buffer_create(); + + for (n = 0; n < BIG; n++) { + body[n] = rnd[n % (sizeof(rnd) - 1)]; + } + body[n] = '\0'; +#undef BIG + + ne_buffer_concat(buf, RESP200 TE_CHUNKED "\r\n" + "4E20\r\n", body, "\r\n", + "0\r\n\r\n", NULL); + + CALL(expect_response(body, single_serve_string, buf->data)); + + ne_buffer_destroy(buf); + ne_free(body); + + return OK; +} + +static int te_over_clength(void) +{ + /* T-E dominates over C-L. */ + return expect_response("abcde", single_serve_string, + RESP200 TE_CHUNKED + "Content-Length: 300\r\n" + "\r\n" + ABCDE_CHUNKS); +} + +/* te_over_clength with the headers the other way round; check for + * ordering problems. */ +static int te_over_clength2(void) +{ + return expect_response("abcde", single_serve_string, + RESP200 "Content-Length: 300\r\n" + TE_CHUNKED + "\r\n" + ABCDE_CHUNKS); +} + +/* obscure case which is possibly a valid request by 2616, but should + * be handled correctly in any case. neon <0.22.0 tries to + * eat the response body, which is probably incorrect. */ +static int no_body_chunks(void) +{ + return expect_no_body("HEAD", "HTTP/1.1 204 Not Modified\r\n" + TE_CHUNKED "\r\n"); +} + +static int serve_twice(ne_socket *sock, void *userdata) +{ + const char *resp = userdata; + + CALL(discard_request(sock)); + SEND_STRING(sock, resp); + + CALL(discard_request(sock)); + SEND_STRING(sock, resp); + + return OK; +} + +/* Test persistent connection handling: serve 'response' twice on a + * single TCP connection, expecting to get a response body equal to + * 'body' both times. */ +static int test_persist(const char *response, const char *body) +{ + ne_session *sess = ne_session_create("http", "localhost", 7777); + ne_buffer *buf = ne_buffer_create(); + + ON(sess == NULL || buf == NULL); + ON(spawn_server(7777, serve_twice, (char *)response)); + + CALL(run_request(sess, 200, construct_get, buf)); + + ONV(strcmp(buf->data, body), + ("response #1 mismatch: [%s] not [%s]", buf->data, body)); + + /* Run it again. */ + ne_buffer_clear(buf); + CALL(run_request(sess, 200, construct_get, buf)); + + ON(await_server()); + + ONV(strcmp(buf->data, body), + ("response #2 mismatch: [%s] not [%s]", buf->data, body)); + + ne_session_destroy(sess); + ne_buffer_destroy(buf); + + return OK; +} + +static int persist_http11(void) +{ + return test_persist(RESP200 "Content-Length: 5\r\n\r\n" "abcde", + "abcde"); +} + +static int persist_chunked(void) +{ + return test_persist(RESP200 TE_CHUNKED "\r\n" ABCDE_CHUNKS, + "abcde"); +} + +static int persist_http10(void) +{ + return test_persist("HTTP/1.0 200 OK\r\n" + "Connection: keep-alive\r\n" + "Content-Length: 5\r\n\r\n" "abcde", + "abcde"); +} + +/* Server function for fail_early_eof */ +static int serve_eof(ne_socket *sock, void *ud) +{ + const char *resp = ud; + + /* dummy request/response. */ + CALL(discard_request(sock)); + CALL(SEND_STRING(sock, RESP200 "Content-Length: 0\r\n\r\n")); + /* real request/response. */ + CALL(discard_request(sock)); + CALL(SEND_STRING(sock, resp)); + + return OK; +} + +/* Utility function: 'resp' is a truncated response; such that an EOF + * arrives early during response processing; but NOT as a valid + * premature EOF due to a persistent connection timeout. It is an + * error if the request is then retried, and the test fails. */ +static int fail_early_eof(const char *resp) +{ + ne_session *sess = ne_session_create("http", "localhost", 7777); + + CALL(spawn_server_repeat(7777, serve_eof, (char *)resp, 3)); + + ONREQ(any_request(sess, "/foo")); + ONN("request retried after early EOF", + any_request(sess, "/foobar") == NE_OK); + + CALL(reap_server()); + ne_session_destroy(sess); + return OK; +} + +/* This failed with neon <0.22. */ +static int fail_eof_continued(void) +{ + return fail_early_eof("HTTP/1.1 100 OK\r\n\r\n"); +} + +static int fail_eof_headers(void) +{ + return fail_early_eof("HTTP/1.1 200 OK\r\nJimbob\r\n"); +} + +static int fail_eof_chunk(void) +{ + return fail_early_eof(RESP200 TE_CHUNKED "\r\n" "1\r\n" "a"); +} + +static int fail_eof_badclen(void) +{ + return fail_early_eof(RESP200 "Content-Length: 10\r\n\r\n" "abcde"); +} + +/* Persistent connection timeout where a FIN is sent to terminate the + * connection, which is caught by a 0 return from the read() when the + * second request reads the status-line. */ +static int ptimeout_eof(void) +{ + ne_session *sess = ne_session_create("http", "localhost", 7777); + + CALL(spawn_server_repeat(7777, single_serve_string, + RESP200 "Content-Length: 0\r\n" "\r\n", 4)); + + CALL(any_2xx_request(sess, "/first")); + CALL(any_2xx_request(sess, "/second")); + + ONN("server died prematurely?", dead_server()); + reap_server(); + + ne_session_destroy(sess); + return OK; +} + +/* Persistent connection timeout where a FIN is sent to terminate the + * connection, but the request fails in the write() call which sends + * the body. */ +static int ptimeout_eof2(void) +{ + ne_session *sess = ne_session_create("http", "localhost", 7777); + + CALL(spawn_server_repeat(7777, single_serve_string, + RESP200 "Content-Length: 0\r\n" "\r\n", 4)); + + CALL(any_2xx_request(sess, "/first")); + minisleep(); + CALL(any_2xx_request_body(sess, "/second")); + + ONN("server died prematurely?", dead_server()); + reap_server(); + + ne_session_destroy(sess); + return OK; +} + +/* TODO: add a ptimeout_reset too, if an RST can be reliably generated + * mid-connection. */ + +/* Emulates a persistent connection timeout on the server. This tests + * the timeout occuring after between 1 and 10 requests down the + * connection. */ +static int persist_timeout(void) +{ + ne_session *sess = ne_session_create("http", "localhost", 7777); + ne_buffer *buf = ne_buffer_create(); + int n; + struct many_serve_args args; + + ON(sess == NULL || buf == NULL); + + args.str = RESP200 "Content-Length: 5\r\n\r\n" "abcde"; + + for (args.count = 1; args.count < 10; args.count++) { + + ON(spawn_server(7777, many_serve_string, &args)); + + for (n = 0; n < args.count; n++) { + + ONV(run_request(sess, 200, construct_get, buf), + ("%d of %d, request failed: %s", n, args.count, + ne_get_error(sess))); + + ONV(strcmp(buf->data, "abcde"), + ("%d of %d, response body mismatch", n, args.count)); + + /* Ready for next time. */ + ne_buffer_clear(buf); + } + + ON(await_server()); + + } + + ne_session_destroy(sess); + ne_buffer_destroy(buf); + + return OK; +} + +/* Test that an HTTP/1.0 server is not presumed to support persistent + * connections by default. */ +static int no_persist_http10(void) +{ + ne_session *sess = ne_session_create("http", "localhost", 7777); + + CALL(spawn_server_repeat(7777, single_serve_string, + "HTTP/1.0 200 OK\r\n" + "Content-Length: 5\r\n\r\n" + "abcde" + "Hello, world - what a nice day!\r\n", + 4)); + + /* if the connection is treated as persistent, the status-line for + * the second request will be "Hello, world...", which will + * fail. */ + + ONREQ(any_request(sess, "/foobar")); + ONREQ(any_request(sess, "/foobar")); + + ONN("server died prematurely?", dead_server()); + CALL(reap_server()); + ne_session_destroy(sess); + return OK; +} + +static int ignore_bad_headers(void) +{ + return expect_response("abcde", single_serve_string, + RESP200 + "Stupid Header\r\n" + "ReallyStupidHeader\r\n" + "Content-Length: 5\r\n" + "\r\n" + "abcde"); +} + +static int fold_headers(void) +{ + return expect_response("abcde", single_serve_string, + RESP200 "Content-Length: \r\n 5\r\n" + "\r\n" + "abcde"); +} + +static int fold_many_headers(void) +{ + return expect_response("abcde", single_serve_string, + RESP200 "Content-Length: \r\n \r\n \r\n \r\n 5\r\n" + "\r\n" + "abcde"); +} + +#define NO_BODY "Content-Length: 0\r\n\r\n" + +static int empty_header(void) +{ + return expect_header_value("ranDom-HEader", "", + single_serve_string, + RESP200 "RANDom-HeADEr:\r\n" + NO_BODY); +} + +static int ignore_header_case(void) +{ + return expect_header_value("ranDom-HEader", "noddy", + single_serve_string, + RESP200 "RANDom-HeADEr: noddy\r\n" + NO_BODY); +} + +static int ignore_header_ws(void) +{ + return expect_header_value("ranDom-HEader", "fishy", + single_serve_string, + RESP200 "RANDom-HeADEr: fishy\r\n" + NO_BODY); +} + +static int ignore_header_ws2(void) +{ + return expect_header_value("ranDom-HEader", "fishy", + single_serve_string, + RESP200 "RANDom-HeADEr \t : fishy\r\n" + NO_BODY); +} + +static int ignore_header_ws3(void) +{ + return expect_header_value("ranDom-HEader", "fishy", + single_serve_string, + RESP200 "RANDom-HeADEr: fishy \r\n" + NO_BODY); +} + +static int ignore_header_tabs(void) +{ + return expect_header_value("ranDom-HEader", "geezer", + single_serve_string, + RESP200 "RANDom-HeADEr: \t \tgeezer\r\n" + NO_BODY); +} + +static int trailing_header(void) +{ + return expect_header_value("gONe", "fishing", + single_serve_string, + RESP200 TE_CHUNKED + "\r\n0\r\n" + "Hello: world\r\n" + "GONE: fishing\r\n" + "\r\n"); +} + +static int continued_header(void) +{ + return expect_header_value("hello", "w o r l d", single_serve_string, + RESP200 "Hello: \n\tw\r\n\to r l\r\n\td \r\n" + NO_BODY); +} + +static void mh_header(void *ctx, const char *value) +{ + int *state = ctx; + static const char *hdrs[] = { "jim", "jab", "jar" }; + + if (*state < 0 || *state > 2) { + /* already failed. */ + return; + } + + if (strcmp(value, hdrs[*state])) + *state = -*state; + else + (*state)++; +} + +/* check headers callbacks are working correctly. */ +static int multi_header(void) +{ + ne_session *sess = ne_session_create("http", "localhost", 7777); + ne_request *req; + int state = 0; + + ON(sess == NULL); + ON(spawn_server(7777, single_serve_string, + RESP200 + "X-Header: jim\r\n" + "x-header: jab\r\n" + "x-Header: jar\r\n" + "Content-Length: 0\r\n\r\n")); + + req = ne_request_create(sess, "GET", "/"); + ON(req == NULL); + + ne_add_response_header_handler(req, "x-header", mh_header, &state); + + ONREQ(ne_request_dispatch(req)); + + ON(await_server()); + + ON(state != 3); + + ne_request_destroy(req); + ne_session_destroy(sess); + + return OK; +} + +struct s1xx_args { + int count; + int hdrs; +}; + +static int serve_1xx(ne_socket *sock, void *ud) +{ + struct s1xx_args *args = ud; + CALL(discard_request(sock)); + + do { + if (args->hdrs) { + SEND_STRING(sock, "HTTP/1.1 100 Continue\r\n" + "Random: header\r\n" + "Another: header\r\n\r\n"); + } else { + SEND_STRING(sock, "HTTP/1.1 100 Continue\r\n\r\n"); + } + } while (--args->count > 0); + + SEND_STRING(sock, RESP200 "Content-Length: 0\r\n\r\n"); + + return OK; +} + +#define sess def_sess + +static int skip_interim_1xx(void) +{ + struct s1xx_args args = {0, 0}; + ON(prepare_request(serve_1xx, &args)); + ONREQ(ne_request_dispatch(def_req)); + return finish_request(); +} + +static int skip_many_1xx(void) +{ + struct s1xx_args args = {5, 0}; + ON(prepare_request(serve_1xx, &args)); + ONREQ(ne_request_dispatch(def_req)); + return finish_request(); +} + +static int skip_1xx_hdrs(void) +{ + struct s1xx_args args = {5, 5}; + ON(prepare_request(serve_1xx, &args)); + ONREQ(ne_request_dispatch(def_req)); + return finish_request(); +} + +#undef sess + +/* server for expect_100_once: eats a dummy request, then serves a + * 100-continue request, and fails if the request body is sent + * twice. */ +static int serve_100_once(ne_socket *sock, void *ud) +{ + struct s1xx_args args = {2, 0}; + char ch; + /* dummy first request. */ + CALL(discard_request(sock)); + CALL(SEND_STRING(sock, RESP200 "Content-Length: 0\r\n\r\n")); + /* now the real 1xx request. */ + CALL(serve_1xx(sock, &args)); + CALL(discard_body(sock)); + ONN("body was served twice", ne_sock_read(sock, &ch, 1) == 1); + return OK; +} + +/* regression test; fails with neon <0.22, where the request body was + * served *every* time a 1xx response was received, rather than just + * once. */ +static int expect_100_once(void) +{ + ne_session *sess; + ne_request *req; + char body[BUFSIZ]; + + CALL(make_session(&sess, serve_100_once, NULL)); + ne_set_expect100(sess, 1); + + /* 100-continue is only used if the server is known to claim + * HTTP/1.1 compliance; make a dummy request on the socket first, + * to trigger that logic. */ + CALL(any_request(sess, "/foo")); + + /* now the real request. */ + req = ne_request_create(sess, "GET", "/foo"); + memset(body, 'A', sizeof(body)); + ne_set_request_body_buffer(req, body, sizeof(body)); + ONN("request failed", ne_request_dispatch(req)); + ne_request_destroy(req); + ne_session_destroy(sess); + CALL(await_server()); + return OK; +} + +struct body { + char *body; + size_t size; +}; + +static int want_body(ne_socket *sock, void *userdata) +{ + struct body *b = userdata; + char *buf = ne_malloc(b->size); + + clength = 0; + CALL(discard_request(sock)); + ONN("request has c-l header", clength == 0); + + ONN("request length", clength != (int)b->size); + + NE_DEBUG(NE_DBG_HTTP, + "reading body of %" NE_FMT_SIZE_T " bytes...\n", b->size); + + ON(ne_sock_fullread(sock, buf, b->size)); + + ON(SEND_STRING(sock, RESP200 "Content-Length: 0\r\n\r\n")); + + ON(memcmp(buf, b->body, b->size)); + + ne_free(buf); + return OK; +} + +static ssize_t provide_body(void *userdata, char *buf, size_t buflen) +{ + static const char *pnt; + static size_t left; + struct body *b = userdata; + + if (buflen == 0) { + pnt = b->body; + left = b->size; + } else { + if (left < buflen) buflen = left; + memcpy(buf, pnt, buflen); + left -= buflen; + } + + return buflen; +} + +static int send_bodies(void) +{ + unsigned int n, m; + + struct body bodies[] = { + { "abcde", 5 }, + { "\0\0\0\0\0\0", 6 }, + { NULL, 50000 }, + { NULL } + }; + +#define BIG 2 + /* make the body with some cruft. */ + bodies[BIG].body = ne_malloc(bodies[BIG].size); + for (n = 0; n < bodies[BIG].size; n++) { + bodies[BIG].body[n] = (char)n%80; + } + + for (m = 0; m < 2; m++) { + for (n = 0; bodies[n].body != NULL; n++) { + ne_session *sess = ne_session_create("http", "localhost", 7777); + ne_request *req; + + ON(sess == NULL); + ON(spawn_server(7777, want_body, &(bodies[n]))); + + req = ne_request_create(sess, "PUT", "/"); + ON(req == NULL); + + if (m == 0) { + ne_set_request_body_buffer(req, bodies[n].body, bodies[n].size); + } else { + ne_set_request_body_provider(req, bodies[n].size, + provide_body, &bodies[n]); + } + + ONREQ(ne_request_dispatch(req)); + + CALL(await_server()); + + ne_request_destroy(req); + ne_session_destroy(sess); + } + } + + ne_free(bodies[BIG].body); + return OK; +} + +static int serve_infinite_headers(ne_socket *sock, void *userdata) +{ + CALL(discard_request(sock)); + + SEND_STRING(sock, RESP200); + + for (;;) { + SEND_STRING(sock, "x-foo: bar\r\n"); + } + + return 0; +} + +/* Utility function: run a request using the given server fn, and the + * request should fail. If 'error' is non-NULL, it must be a substring + * of the error string. */ +static int fail_request_with_error(int with_body, server_fn fn, void *ud, + int forever, const char *error) +{ + ne_session *sess = ne_session_create("http", "localhost", 7777); + ne_request *req; + int ret; + + ON(sess == NULL); + + if (forever) { + ON(spawn_server_repeat(7777, fn, ud, 100)); + } else { + ON(spawn_server(7777, fn, ud)); + } + + req = ne_request_create(sess, "GET", "/"); + ON(req == NULL); + + if (with_body) { + static const char *body = "random stuff"; + + ne_set_request_body_buffer(req, body, strlen(body)); + } + + /* request should fail. */ + ret = ne_request_dispatch(req); + ONN("request succeeded", ret == NE_OK); + + if (!forever) { + /* reap the server, don't care what it's doing. */ + reap_server(); + } + + NE_DEBUG(NE_DBG_HTTP, "Response gave error `%s'\n", ne_get_error(sess)); + + ONV(error && strstr(ne_get_error(sess), error) == NULL, + ("failed with error `%s', no `%s'", ne_get_error(sess), error)); + + if (!forever) + ONV(any_request(sess, "/fail/to/connect") != NE_CONNECT, + ("subsequent request re-used connection?")); + + ne_request_destroy(req); + ne_session_destroy(sess); + + return OK; +} + +/* Run a random GET request which is given 'body' as the response; the + * request must fail, and 'error' must be found in the error + * string. */ +static int invalid_response_gives_error(const char *resp, const char *error) +{ + return fail_request_with_error(0, single_serve_string, (void *)resp, 0, error); +} + +/* Utility function: run a request using the given server fn, and the + * request must fail. */ +static int fail_request(int with_body, server_fn fn, void *ud, int forever) +{ + return fail_request_with_error(with_body, fn, ud, forever, NULL); +} + +static int unbounded_headers(void) +{ + return fail_request(0, serve_infinite_headers, NULL, 0); +} + +static int blank_response(void) +{ + return fail_request(0, single_serve_string, "\r\n", 0); +} + +static int serve_non_http(ne_socket *sock, void *ud) +{ + SEND_STRING(sock, "Hello Mum.\n"); + ne_sock_readline(sock, buffer, BUFSIZ); + return OK; +} + +/* Test behaviour when not speaking to an HTTP server. Regression test + * for infinite loop. */ +static int not_http(void) +{ + return fail_request(0, serve_non_http, NULL, 0); +} + +static int serve_infinite_folds(ne_socket *sock, void *ud) +{ + SEND_STRING(sock, "HTTP/1.0 200 OK\r\nFoo: bar\r\n"); + for (;;) { + SEND_STRING(sock, " hello there.\r\n"); + } + return OK; +} + +static int unbounded_folding(void) +{ + return fail_request(0, serve_infinite_folds, NULL, 0); +} + +static int serve_close(ne_socket *sock, void *ud) +{ + /* do nothing; the socket will be closed. */ + return 0; +} + +/* Returns non-zero if port is alive. */ +static int is_alive(int port) +{ + ne_sock_addr *addr; + ne_socket *sock = ne_sock_create(); + const ne_inet_addr *ia; + int connected = 0; + + addr = ne_addr_resolve("localhost", 0); + for (ia = ne_addr_first(addr); ia && !connected; ia = ne_addr_next(addr)) + connected = ne_sock_connect(sock, ia, 7777) == 0; + ne_addr_destroy(addr); + if (sock == NULL) + return 0; + else { + ne_sock_close(sock); + return 1; + } +} + +/* This is a regression test for neon 0.17.0 and earlier, which goes + * into an infinite loop if a request with a body is sent to a server + * which simply closes the connection. */ +static int closed_connection(void) +{ + int ret; + + /* This spawns a server process which will run the 'serve_close' + * response function 200 times, then die. This guarantees that the + * request eventually fails... */ + CALL(fail_request(1, serve_close, NULL, 1)); + /* if server died -> infinite loop was detected. */ + ret = !is_alive(7777); + reap_server(); + ONN("server aborted, infinite loop?", ret); + return OK; +} + +static int serve_close2(ne_socket *sock, void *userdata) +{ + int *count = userdata; + *count += 1; + if (*count == 1) + return 0; + NE_DEBUG(NE_DBG_HTTP, "Re-entered! Buggy client.\n"); + CALL(discard_request(sock)); + CALL(SEND_STRING(sock, RESP200 "Content-Length: 0\r\n\r\n")); + return 0; +} + +/* As closed_connection(); but check that the client doesn't retry + * after receiving the EOF on the first request down a new + * connection. */ +static int close_not_retried(void) +{ + int count = 0; + ne_session *sess = ne_session_create("http", "localhost", 7777); + CALL(spawn_server_repeat(7777, serve_close2, &count, 3)); + ONN("request was retried after EOF", any_request(sess, "/foo") == NE_OK); + reap_server(); + ne_session_destroy(sess); + return OK; +} + +static enum { + prog_error, /* error */ + prog_transfer, /* doing a transfer */ + prog_done /* finished. */ +} prog_state = prog_transfer; + +static off_t prog_last = -1, prog_total; + +/* callback for send_progress. */ +static void s_progress(void *userdata, off_t prog, off_t total) +{ + NE_DEBUG(NE_DBG_HTTP, + "progress callback: %" NE_FMT_OFF_T "/%" NE_FMT_OFF_T ".\n", + prog, total); + + switch (prog_state) { + case prog_error: + case prog_done: + return; + case prog_transfer: + if (total != prog_total) { + t_context("total unexpected: %ld not %ld", total, prog_total); + prog_state = prog_error; + } + else if (prog > total) { + t_context("first progress was invalid (%ld/%ld)", prog, total); + prog_state = prog_error; + } + else if (prog_last != -1 && prog_last > prog) { + t_context("progess went backwards: %ld to %ld", prog_last, prog); + prog_state = prog_error; + } + else if (prog_last == prog) { + t_context("no progress made! %ld to %ld", prog_last, prog); + prog_state = prog_error; + } + else if (prog == total) { + prog_state = prog_done; + } + break; + } + + prog_last = prog; +} + +static ssize_t provide_progress(void *userdata, char *buf, size_t bufsiz) +{ + int *count = userdata; + + if (*count >= 0 && buf != NULL) { + buf[0] = 'a'; + *count -= 1; + return 1; + } else { + return 0; + } +} + +static int send_progress(void) +{ + static int count = 200; + + ON(prepare_request(single_serve_string, + RESP200 "Connection: close\r\n\r\n")); + + prog_total = 200; + + ne_set_progress(def_sess, s_progress, NULL); + ne_set_request_body_provider(def_req, count, + provide_progress, &count); + +#define sess def_sess + ONREQ(ne_request_dispatch(def_req)); +#undef sess + + ON(finish_request()); + + CALL(prog_state == prog_error); + + return OK; +} + +static int read_timeout(void) +{ + ne_session *sess; + ne_request *req; + time_t start, finish; + int ret; + + CALL(make_session(&sess, sleepy_server, NULL)); + + /* timeout after one second. */ + ne_set_read_timeout(sess, 1); + + req = ne_request_create(sess, "GET", "/timeout"); + + time(&start); + ret = ne_request_dispatch(req); + time(&finish); + + reap_server(); + + ONN("request succeeded, should have timed out", ret == NE_OK); + ONV(ret != NE_TIMEOUT, + ("request failed non-timeout error: %s", ne_get_error(sess))); + ONN("timeout ignored, or very slow machine", finish - start > 3); + + ne_request_destroy(req); + ne_session_destroy(sess); + + return OK; +} + +/* expect failure code 'code', for request to given hostname and port, + * without running a server. */ +static int fail_noserver(const char *hostname, unsigned int port, int code) +{ + ne_session *sess = ne_session_create("http", hostname, port); + int ret = any_request(sess, "/foo"); + ne_session_destroy(sess); + + ONV(ret == NE_OK, + ("request to server at %s:%u succeded?!", hostname, port)); + ONV(ret != code, ("request failed with %d not %d", ret, code)); + + return OK; +} + +static int fail_lookup(void) +{ + return fail_noserver("no.such.domain", 7777, NE_LOOKUP); +} + +/* neon 0.23.0 to 0.23.3: if a nameserver lookup failed, subsequent + * requests on the session would crash. */ +static int fail_double_lookup(void) +{ + ne_session *sess = ne_session_create("http", "nohost.example.com", 80); + ONN("request did not give lookup failure", + any_request(sess, "/foo") != NE_LOOKUP); + ONN("second request did not give lookup failure", + any_request(sess, "/bar") != NE_LOOKUP); + ne_session_destroy(sess); + return OK; +} + +static int fail_connect(void) +{ + return fail_noserver("localhost", 7777, NE_CONNECT); +} + +/* Test that the origin server hostname is NOT resolved for a proxied + * request. */ +static int proxy_no_resolve(void) +{ + ne_session *sess = ne_session_create("http", "no.such.domain", 80); + int ret; + + ne_session_proxy(sess, "localhost", 7777); + CALL(spawn_server(7777, single_serve_string, + RESP200 "Content-Length: 0\r\n\r\n")); + + ret = any_request(sess, "/foo"); + ne_session_destroy(sess); + + ONN("origin server name resolved when proxy used", ret == NE_LOOKUP); + + CALL(await_server()); + + return OK; +} + +/* If the chunk size is entirely invalid, the request should be + * aborted. Fails with neon <0.22; invalid chunk sizes would be + * silently treated as 'zero'. */ +static int fail_chunksize(void) +{ + return fail_request(0, single_serve_string, + RESP200 TE_CHUNKED "\r\n" "ZZZZZ\r\n\r\n", 0); +} + +/* in neon <0.22, if an error occcurred whilst reading the response + * body, the connection would not be closed (though this test will + * succeed in neon <0.22 since it the previous test fails). */ +static int abort_respbody(void) +{ + ne_session *sess; + + CALL(make_session(&sess, single_serve_string, + RESP200 TE_CHUNKED "\r\n" + "zzz\r\n" + RESP200 "Content-Length: 0\r\n\r\n")); + + /* connection must be aborted on the first request, since it + * contains an invalid chunk size. */ + ONN("invalid chunk size was accepted?", + any_request(sess, "/foo") != NE_ERROR); + + CALL(await_server()); + + /* second request should fail since server has gone away. */ + ONN("connection was not aborted", any_request(sess, "/foo") == NE_OK); + + ne_session_destroy(sess); + return OK; +} + +static int serve_abort(ne_socket *sock, void *ud) +{ + exit(0); +} + +/* Test that after an aborted request on a peristent connection, a + * failure of the *subsequent* request is not treated as a persistent + * connection timeout and retried. */ +static int retry_after_abort(void) +{ + ne_session *sess; + + /* Serve two responses down a single persistent connection, the + * second of which is invalid and will cause the request to be + * aborted. */ + CALL(make_session(&sess, single_serve_string, + RESP200 "Content-Length: 0\r\n\r\n" + RESP200 TE_CHUNKED "\r\n" + "zzzzz\r\n")); + + CALL(any_request(sess, "/first")); + ONN("second request should fail", any_request(sess, "/second") == NE_OK); + CALL(await_server()); + + /* spawn a server, abort the server immediately. If the + * connection reset is interpreted as a p.conn timeout, a new + * connection will be attempted, which will fail with + * NE_CONNECT. */ + CALL(spawn_server(7777, serve_abort, NULL)); + ONN("third request was retried", + any_request(sess, "/third") == NE_CONNECT); + reap_server(); + + ne_session_destroy(sess); + return OK; +} + +/* Fail to parse the response status line: check the error message is + * sane. Failed during 0.23-dev briefly, and possibly with 0.22.0 + * too. */ +static int fail_statusline(void) +{ + ne_session *sess; + int ret; + + CALL(make_session(&sess, single_serve_string, "Fish.\r\n")); + + ret = any_request(sess, "/fail"); + ONV(ret != NE_ERROR, ("request failed with %d not NE_ERROR", ret)); + + /* FIXME: will break for i18n. */ + ONV(strcmp(ne_get_error(sess), "Could not parse response status line."), + ("session error was `%s'", ne_get_error(sess))); + + ne_session_destroy(sess); + return OK; +} + +#define LEN (9000) +static int fail_long_header(void) +{ + char resp[LEN + 500] = "HTTP/1.1 200 OK\r\n" + "Server: fish\r\n"; + size_t len = strlen(resp); + + /* add a long header */ + memset(resp + len, 'a', LEN); + resp[len + LEN] = '\0'; + + strcat(resp, "\r\n\r\n"); + + return invalid_response_gives_error(resp, "Line too long"); +} + +static int fail_corrupt_chunks(void) +{ + static const struct { + const char *resp, *error; + } ts[] = { + /* not CRLF */ + { RESP200 TE_CHUNKED "\r\n" "5\r\n" "abcdeFISH", + "delimiter was invalid" }, + /* short CRLF */ + { RESP200 TE_CHUNKED "\r\n" "5\r\n" "abcde\n", + "not read chunk delimiter" }, + /* CR-notLF */ + { RESP200 TE_CHUNKED "\r\n" "5\r\n" "abcde\rZZZ", + "delimiter was invalid" }, + { NULL, NULL } + }; + int n; + + for (n = 0; ts[n].resp; n++) + CALL(invalid_response_gives_error(ts[n].resp, ts[n].error)); + + return OK; +} + +static int versions(void) +{ + ne_session *sess; + + CALL(make_session(&sess, single_serve_string, + "HTTP/1.1 200 OK\r\n" + "Content-Length: 0\r\n\r\n" + + "HTTP/1.0 200 OK\r\n" + "Content-Length: 0\r\n\r\n")); + + CALL(any_request(sess, "/http11")); + + ONN("did not detect HTTP/1.1 compliance", + ne_version_pre_http11(sess) != 0); + + CALL(any_request(sess, "/http10")); + + ONN("did not detect lack of HTTP/1.1 compliance", + ne_version_pre_http11(sess) == 0); + + ne_session_destroy(sess); + + return OK; +} + +struct cr_args { + const char *method, *uri; + int result; +}; + +static void hk_createreq(ne_request *req, void *userdata, + const char *method, const char *requri) +{ + struct cr_args *args = userdata; + + args->result = 1; /* presume failure */ + + if (strcmp(args->method, method)) + t_context("Hook got method %s not %s", method, args->method); + else if (strcmp(args->uri, requri)) + t_context("Hook got Req-URI %s not %s", requri, args->uri); + else + args->result = 0; +} + +static int hook_create_req(void) +{ + ne_session *sess; + struct cr_args args; + + CALL(make_session(&sess, single_serve_string, EMPTY_RESP EMPTY_RESP)); + + ne_hook_create_request(sess, hk_createreq, &args); + + args.method = "GET"; + args.uri = "/foo"; + args.result = -1; + + CALL(any_request(sess, "/foo")); + + ONN("first hook never called", args.result == -1); + if (args.result) return FAIL; + + args.uri = "http://localhost:7777/bar"; + args.result = -1; + + /* force use of absoluteURI in request-uri */ + ne_session_proxy(sess, "localhost", 7777); + + CALL(any_request(sess, "/bar")); + + ONN("second hook never called", args.result == -1); + if (args.result) return FAIL; + + ne_session_destroy(sess); + + return OK; +} + +static int serve_check_method(ne_socket *sock, void *ud) +{ + char *method = ud; + char buf[20]; + size_t methlen = strlen(method); + + if (ne_sock_read(sock, buf, methlen) != (ssize_t)methlen) + return -1; + + ONN("method corrupted", memcmp(buf, method, methlen)); + + return single_serve_string(sock, "HTTP/1.1 204 OK\r\n\r\n"); +} + + +/* Test that the method string passed to ne_request_create is + * strdup'ed. */ +static int dup_method(void) +{ + char method[] = "FOO"; + ne_session *sess; + ne_request *req; + + CALL(make_session(&sess, serve_check_method, method)); + + req = ne_request_create(sess, method, "/bar"); + + strcpy(method, "ZZZ"); + + ONREQ(ne_request_dispatch(req)); + ne_request_destroy(req); + ne_session_destroy(sess); + CALL(await_server()); + + return OK; +} + +ne_test tests[] = { + T(lookup_localhost), + T(single_get_clength), + T(single_get_eof), + T(single_get_chunked), + T(no_body_204), + T(no_body_304), + T(no_body_HEAD), + T(no_body_empty_clength), + T(no_body_bad_clength), + T(no_headers), + T(chunks), + T(te_header), + T(any_te_header), + T(reason_phrase), + T(chunk_numeric), + T(chunk_extensions), + T(chunk_trailers), + T(chunk_oversize), + T(te_over_clength), + T(te_over_clength2), + T(no_body_chunks), + T(persist_http11), + T(persist_chunked), + T(persist_http10), + T(persist_timeout), + T(no_persist_http10), + T(ptimeout_eof), + T(ptimeout_eof2), + T(closed_connection), + T(close_not_retried), + T(send_progress), + T(ignore_bad_headers), + T(fold_headers), + T(fold_many_headers), + T(multi_header), + T(empty_header), + T(trailing_header), + T(ignore_header_case), + T(ignore_header_ws), + T(ignore_header_ws2), + T(ignore_header_ws3), + T(ignore_header_tabs), + T(continued_header), + T(skip_interim_1xx), + T(skip_many_1xx), + T(skip_1xx_hdrs), + T(send_bodies), + T(expect_100_once), + T(unbounded_headers), + T(unbounded_folding), + T(blank_response), + T(not_http), + T(fail_eof_continued), + T(fail_eof_headers), + T(fail_eof_chunk), + T(fail_eof_badclen), + T(fail_long_header), + T(fail_corrupt_chunks), + T(read_timeout), + T(fail_lookup), + T(fail_double_lookup), + T(fail_connect), + T(proxy_no_resolve), + T(fail_chunksize), + T(abort_respbody), + T(retry_after_abort), + T(fail_statusline), + T(dup_method), + T(versions), + T(hook_create_req), + T(NULL) +}; diff --git a/test/resolve.c b/test/resolve.c new file mode 100644 index 0000000..bd0789e --- /dev/null +++ b/test/resolve.c @@ -0,0 +1,59 @@ +/* + Test program for the neon resolver interface + Copyright (C) 2002-2003, Joe Orton <joe@manyfish.co.uk> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "config.h" + +#include <stdio.h> + +#include "ne_socket.h" + +int main(int argc, char **argv) +{ + ne_sock_addr *addr; + char buf[256]; + int ret = 0; + + if (argc < 2) { + printf("Usage: %s hostname\n", argv[0]); + return 1; + } + + if (ne_sock_init()) { + printf("%s: Failed to initialize socket library.\n", argv[0]); + return 1; + } + + addr = ne_addr_resolve(argv[1], 0); + if (ne_addr_result(addr)) { + printf("Could not resolve `%s': %s\n", argv[1], + ne_addr_error(addr, buf, sizeof buf)); + ret = 2; + } else { + const ne_inet_addr *ia; + printf("Resolved `%s' OK:", argv[1]); + for (ia = ne_addr_first(addr); ia; ia = ne_addr_next(addr)) { + printf(" <%s>", ne_iaddr_print(ia, buf, sizeof buf)); + } + putchar('\n'); + } + ne_addr_destroy(addr); + + return ret; +} diff --git a/test/run.sh b/test/run.sh new file mode 100644 index 0000000..7d72749 --- /dev/null +++ b/test/run.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +rm -f debug.log +rm -f child.log + +# for shared builds. +LD_LIBRARY_PATH=../src/.libs:$LD_LIBRARY_PATH + +# enable an safety-checking malloc in glibc which will abort() if +# heap corruption is detected. +MALLOC_CHECK_=2 + +export LD_LIBRARY_PATH MALLOC_CHECK_ + +for f in $*; do + if ${HARNESS} ./$f ${SRCDIR}; then + : + else + echo FAILURE + [ -z "$CARRYON" ] && exit 1 + fi +done + +exit 0 diff --git a/test/server.key b/test/server.key new file mode 100644 index 0000000..cdfb91b --- /dev/null +++ b/test/server.key @@ -0,0 +1,9 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIBOwIBAAJBAPNFTmxnz4JZA+8+SonD0qWgSBPYWrNlH1FP+psm5EGZGmGJGvSD +sk6HkyvstdopKF50UuEaJ263IorAhkmdGG0CAwEAAQJAJBhYdoVAqNqEVu8rKB3C +F4kcqLUlYBDVAL+ZM4QlwgWncAKk2C53BwH4PVWIIfyysleyt3bTAtqg/tgMNM06 +AQIhAP1HKbuppa+UY4rNP4Xcyj5BrCU4wVz77sg/ygW+mWIhAiEA9eKcUnnaIpig +hlWtx9qz++85/JtahA85j6T48v0hBM0CIQCa8ByUg2wq45CdSX+xiOZjfVMslfKb +yjZBY9xW9UjpYQIgdy9j5JqKANEIpnTran95VLot2mMXagHTPeySe331PlUCIQD0 +rL1AXeIR3Vd4D8dgab/FVbg4i94qBiY0731nyPJRoQ== +-----END RSA PRIVATE KEY----- diff --git a/test/session.c b/test/session.c new file mode 100644 index 0000000..6fe04bd --- /dev/null +++ b/test/session.c @@ -0,0 +1,158 @@ +/* + Tests for session handling + Copyright (C) 2002-2003, Joe Orton <joe@manyfish.co.uk> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "config.h" + +#include <sys/types.h> + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "ne_session.h" + +#include "tests.h" + +/* should be in a 'session.c'? */ +static int fill_uri(void) +{ + ne_uri uri = {0}; + ne_session *sess = ne_session_create("http", "localhost", 7777); + + ne_fill_server_uri(sess, &uri); + + ONN("hostname mis-match", strcmp(uri.host, "localhost")); + ONN("port mis-match", uri.port != 7777); + ONN("scheme mis-match", strcmp(uri.scheme, "http")); + + ne_session_destroy(sess); + ne_uri_free(&uri); + + return OK; +} + +static int match_hostport(const char *scheme, const char *hostname, int port, + const char *hostport) +{ + ne_session *sess = ne_session_create(scheme, hostname, port); + const char *hp = ne_get_server_hostport(sess); + ONV(strcmp(hp, hostport), + ("hostport incorrect for %s: `%s' not `%s'", scheme, hp, hostport)); + ne_session_destroy(sess); + return OK; +} + +static int hostports(void) +{ + static const struct { + const char *scheme, *hostname; + int port; + const char *hostport; + } hps[] = { + { "http", "host.name", 80, "host.name" }, + { "http", "host.name", 555, "host.name:555" }, + { "http", "host.name", 443, "host.name:443" }, + { "https", "host.name", 80, "host.name:80" }, + { "https", "host.name", 443, "host.name" }, + { "https", "host.name", 700, "host.name:700" }, + { NULL } + }; + int n; + + for (n = 0; hps[n].scheme; n++) { + CALL(match_hostport(hps[n].scheme, hps[n].hostname, + hps[n].port, hps[n].hostport)); + } + + return OK; +} + + +/* Check that ne_set_error is passing through to printf correctly. */ +static int errors(void) +{ + ne_session *sess = ne_session_create("http", "foo.com", 80); + +#define EXPECT "foo, hello world, 100, bar!" + + ne_set_error(sess, "foo, %s, %d, bar!", "hello world", 100); + + ONV(strcmp(ne_get_error(sess), EXPECT), + ("session error was `%s' not `%s'", + ne_get_error(sess), EXPECT)); +#undef EXPECT + + ne_session_destroy(sess); + return OK; +} + +#define ID1 "foo" +#define ID2 "bar" + +static int privates(void) +{ + ne_session *sess = ne_session_create("http", "localhost", 80); + char *v1 = "hello", *v2 = "world"; + + ne_set_session_private(sess, ID1, v1); + ne_set_session_private(sess, ID2, v2); + +#define PRIV(msg, id, val) \ +ONN(msg, ne_get_session_private(sess, id) != val) + + PRIV("private #1 wrong", ID1, v1); + PRIV("private #2 wrong", ID2, v2); + PRIV("unknown id wrong", "no such ID", NULL); + + ne_session_destroy(sess); + return OK; +} + +/* test that ne_session_create doesn't really care what scheme you + * give it, and that ne_get_scheme() works. */ +static int get_scheme(void) +{ + static const char *schemes[] = { + "http", "https", "ftp", "ldap", "foobar", NULL + }; + int n; + + for (n = 0; schemes[n]; n++) { + ne_session *sess = ne_session_create(schemes[n], "localhost", 80); + ONV(strcmp(ne_get_scheme(sess), schemes[n]), + ("scheme was `%s' not `%s'!", ne_get_scheme(sess), schemes[n])); + ne_session_destroy(sess); + } + + return OK; +} + +ne_test tests[] = { + T(fill_uri), + T(hostports), + T(errors), + T(privates), + T(get_scheme), + T(NULL) +}; + diff --git a/test/skeleton.c b/test/skeleton.c new file mode 100644 index 0000000..9574bdb --- /dev/null +++ b/test/skeleton.c @@ -0,0 +1,51 @@ +/* + neon test suite + Copyright (C) 2002-2003, Joe Orton <joe@manyfish.co.uk> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "config.h" + +#include <sys/types.h> + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "ne_request.h" +#include "ne_socket.h" + +#include "tests.h" +#include "child.h" +#include "utils.h" + +static int foo(void) +{ + /* This is a skeleton test suite file. */ + return OK; +} + +ne_test tests[] = { + T(foo), /* test functions here */ + + /* end of test functions. */ + T(NULL) +}; + diff --git a/test/socket.c b/test/socket.c new file mode 100644 index 0000000..26b107b --- /dev/null +++ b/test/socket.c @@ -0,0 +1,918 @@ +/* + Socket handling tests + Copyright (C) 2002-2003, Joe Orton <joe@manyfish.co.uk> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/* This module can be compiled with -DSOCKET_SSL enabled, to run all + * the tests over an SSL connection. */ + +#include "config.h" + +#include <sys/types.h> +#include <sys/socket.h> /* for AF_INET6 */ + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif +#include <time.h> /* for time() */ + +#include "ne_socket.h" +#include "ne_utils.h" +#include "ne_alloc.h" + +#include "child.h" +#include "tests.h" +#include "utils.h" + +#ifdef SOCKET_SSL +#include <openssl/err.h> +#include <openssl/ssl.h> + +#include "ne_ssl.h" + + +SSL_CTX *server_ctx; +ne_ssl_context *client_ctx; +#endif + +static ne_sock_addr *localhost; +static char buffer[BUFSIZ]; + +#if defined(AF_INET6) && defined(USE_GETADDRINFO) +#define TEST_IPV6 +#endif + +/* tests for doing init/finish multiple times. */ +static int multi_init(void) +{ + int res1 = ne_sock_init(), res2 = ne_sock_init(); + + ONV(res1 != res2, ("cached init result changed from %d to %d", + res1, res2)); + + ne_sock_exit(); + res1 = ne_sock_init(); + + ONV(res1 != res2, ("re-init after exit gave %d not %d", + res1, res2)); + + res2 = ne_sock_init(); + + ONV(res1 != res2, ("second time, cached init result changed from %d to %d", + res1, res2)); + + return OK; +} + +static ne_socket *do_connect(ne_sock_addr *addr, unsigned int port) +{ + ne_socket *sock = ne_sock_create(); + const ne_inet_addr *ia; + + if (!sock) return NULL; + + for (ia = ne_addr_first(addr); ia; ia = ne_addr_next(addr)) { + if (ne_sock_connect(sock, ia, port) == 0) + return sock; + } + + ne_sock_close(sock); + return NULL; +} + +#ifdef SOCKET_SSL + +/* FIXME: largely cut'n'pasted from ssl.c. */ +static int init_ssl(void) +{ + char *server_key; + ne_ssl_certificate *cert; + + /* take srcdir as argv[1]. */ + if (test_argc > 1) { + server_key = ne_concat(test_argv[1], "/server.key", NULL); + } else { + server_key = "server.key"; + } + + ONN("sock_init failed.\n", ne_sock_init()); + server_ctx = SSL_CTX_new(SSLv23_server_method()); + ONN("SSL_CTX_new failed", server_ctx == NULL); + ONN("failed to load private key", + !SSL_CTX_use_PrivateKey_file(server_ctx, server_key, + SSL_FILETYPE_PEM)); + ONN("failed to load certificate", + !SSL_CTX_use_certificate_file(server_ctx, "server.cert", + SSL_FILETYPE_PEM)); + + client_ctx = ne_ssl_context_create(); + ONN("SSL_CTX_new failed for client", client_ctx == NULL); + + cert = ne_ssl_cert_read("ca/cert.pem"); + ONN("could not load ca/cert.pem", cert == NULL); + + ne_ssl_ctx_trustcert(client_ctx, cert); + + return OK; +} +#endif + +static int resolve(void) +{ + char buf[256]; + localhost = ne_addr_resolve("localhost", 0); + ONV(ne_addr_result(localhost), + ("could not resolve `localhost': %s", + ne_addr_error(localhost, buf, sizeof buf))); + /* and again for child.c */ + return lookup_localhost(); +} + +static int serve_close(ne_socket *sock, void *ud) +{ + return 0; +} + +#ifdef SOCKET_SSL +struct serve_pair { + server_fn fn; + void *userdata; +}; + +static int wrap_serve(ne_socket *sock, void *ud) +{ + struct serve_pair *pair = ud; + int fd = ne_sock_fd(sock), ret; + SSL *ssl = SSL_new(server_ctx); + BIO *bio = BIO_new_socket(fd, BIO_NOCLOSE); + + ONN("SSL_new failed", ssl == NULL); + SSL_set_bio(ssl, bio, bio); + +#define ERROR_SSL_STRING (ERR_reason_error_string(ERR_get_error())) + + NE_DEBUG(NE_DBG_SOCKET, "Doing SSL accept:\n"); + ret = SSL_accept(ssl); + if (ret != 1) { + NE_DEBUG(NE_DBG_SOCKET, "SSL_accept failed: %s\n", ERROR_SSL_STRING); + return 1; + } + + NE_DEBUG(NE_DBG_SOCKET, "SSL accept okay.\n"); + ne_sock_switch_ssl(sock, ssl); + return pair->fn(sock, pair->userdata); +} + +static int begin(ne_socket **sock, server_fn fn, void *ud) +{ + struct serve_pair pair; + pair.fn = fn; + pair.userdata = ud; + CALL(spawn_server(7777, wrap_serve, &pair)); + *sock = do_connect(localhost, 7777); + ONN("could not connect to localhost:7777", *sock == NULL); + ONV(ne_sock_connect_ssl(*sock, client_ctx), + ("SSL negotation failed: %s", ne_sock_error(*sock))); + return OK; +} + +#else +/* non-SSL begin() function. */ +static int begin(ne_socket **sock, server_fn fn, void *ud) +{ + CALL(spawn_server(7777, fn, ud)); + *sock = do_connect(localhost, 7777); + ONN("could not connect to localhost:7777", *sock == NULL); + return OK; +} +#endif + +static int resolve_numeric(void) +{ + ne_sock_addr *addr = ne_addr_resolve("127.0.0.1", 0); + ONV(ne_addr_result(addr), + ("failed to resolve 127.0.0.1: %s", + ne_addr_error(addr, buffer, sizeof buffer))); + ONN("ne_addr_first returned NULL", ne_addr_first(addr) == NULL); + ONN("ne_iaddr_print didn't return buffer", + ne_iaddr_print(ne_addr_first(addr), buffer, sizeof buffer) != buffer); + ONV(strcmp(buffer, "127.0.0.1"), ("ntop gave `%s' not 127.0.0.1", buffer)); + ne_addr_destroy(addr); + return OK; +} + +#if 0 +static int resolve_ipv6(void) +{ + char err[256]; + ne_sock_addr *addr = ne_addr_resolve("[::1]", 0); + ONV(ne_addr_result(addr), + ("could not resolve `[::1]': %s", + ne_addr_error(addr, err, sizeof err))); + ne_addr_destroy(addr); + return OK; +} +#endif + +static const unsigned char raw_127[4] = "\x7f\0\0\01", /* 127.0.0.1 */ +raw6_nuls[16] = /* :: */ "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; +#ifdef TEST_IPV6 +static const unsigned char +raw6_cafe[16] = /* feed::cafe */ "\xfe\xed\0\0\0\0\0\0\0\0\0\0\0\0\xca\xfe", +raw6_babe[16] = /* cafe:babe:: */ "\xca\xfe\xba\xbe\0\0\0\0\0\0\0\0\0\0\0\0"; +#endif + +static int addr_make_v4(void) +{ + ne_inet_addr *ia; + char pr[50]; + + ia = ne_iaddr_make(ne_iaddr_ipv4, raw_127); + ONN("ne_iaddr_make returned NULL", ia == NULL); + + ne_iaddr_print(ia, pr, sizeof pr); + ONV(strcmp(pr, "127.0.0.1"), ("address was %s not 127.0.0.1", pr)); + + ne_iaddr_free(ia); + return OK; +} + +static int addr_make_v6(void) +{ +#ifdef TEST_IPV6 + struct { + const unsigned char *addr; + const char *rep; + } as[] = { + { raw6_cafe, "feed::cafe" }, + { raw6_babe, "cafe:babe::" }, + { raw6_nuls, "::" }, + { NULL, NULL } + }; + int n; + + for (n = 0; as[n].rep != NULL; n++) { + ne_inet_addr *ia = ne_iaddr_make(ne_iaddr_ipv6, as[n].addr); + char pr[128]; + + ONV(ia == NULL, ("could not make address for %d", n)); + ne_iaddr_print(ia, pr, sizeof pr); + ONV(strcmp(pr, as[n].rep), + ("address %d was '%s' not '%s'", n, pr, as[n].rep)); + + ne_iaddr_free(ia); + } + + return OK; +#else + /* should fail when lacking IPv6 support. */ + ne_inet_addr *ia = ne_iaddr_make(ne_iaddr_ipv6, raw6_nuls); + ONN("ne_iaddr_make did not return NULL", ia != NULL); +#endif + return OK; +} + +static int addr_compare(void) +{ + ne_inet_addr *ia1, *ia2; + int ret; + + ia1 = ne_iaddr_make(ne_iaddr_ipv4, raw_127); + ia2 = ne_iaddr_make(ne_iaddr_ipv4, raw_127); + ONN("addr_make returned NULL", !ia1 || !ia2); + + ret = ne_iaddr_cmp(ia1, ia2); + ONV(ret != 0, ("comparison of equal IPv4 addresses was %d", ret)); + ne_iaddr_free(ia2); + + ia2 = ne_iaddr_make(ne_iaddr_ipv4, "abcd"); + ret = ne_iaddr_cmp(ia1, ia2); + ONN("comparison of unequal IPv4 addresses was zero", ret == 0); + +#ifdef TEST_IPV6 + ne_iaddr_free(ia2); + ia2 = ne_iaddr_make(ne_iaddr_ipv6, "feed::1"); + ret = ne_iaddr_cmp(ia1, ia2); + ONN("comparison of IPv4 and IPv6 addresses was zero", ret == 0); + + ne_iaddr_free(ia1); + ia1 = ne_iaddr_make(ne_iaddr_ipv4, "feed::1"); + ret = ne_iaddr_cmp(ia1, ia2); + ONN("comparison of equal IPv6 addresses was zero", ret == 0); + +#endif + + ne_iaddr_free(ia1); + ne_iaddr_free(ia2); + return OK; +} + +static int just_connect(void) +{ + ne_socket *sock; + + CALL(begin(&sock, serve_close, NULL)); + ne_sock_close(sock); + return await_server(); +} + +/* Connect to an address crafted using ne_iaddr_make rather than from + * the resolver. */ +static int addr_connect(void) +{ + ne_socket *sock = ne_sock_create(); + ne_inet_addr *ia; + + ia = ne_iaddr_make(ne_iaddr_ipv4, raw_127); + ONN("ne_iaddr_make returned NULL", ia == NULL); + + CALL(spawn_server(7777, serve_close, NULL)); + ONN("could not connect", ne_sock_connect(sock, ia, 7777)); + ne_sock_close(sock); + CALL(await_server()); + + ne_iaddr_free(ia); + return OK; +} + +/* Exect a read() to return EOF */ +static int expect_close(ne_socket *sock) +{ + ssize_t n = ne_sock_read(sock, buffer, 1); + ONV(n > 0, ("read got %" NE_FMT_SSIZE_T "bytes not closure", n)); + ONV(n < 0 && n != NE_SOCK_CLOSED, + ("read got error not closure: `%s'", ne_sock_error(sock))); + return OK; +} + +static int good_close(ne_socket *sock) +{ + NE_DEBUG(NE_DBG_SOCKET, "Socket error was %s\n", ne_sock_error(sock)); + ONN("close failed", ne_sock_close(sock)); + return OK; +} + +/* Finish a test, closing socket and rejoining child. If eof is non-zero, + * expects to read EOF from the socket before closing. */ +static int finish(ne_socket *sock, int eof) +{ + if (eof) + CALL(expect_close(sock)); + CALL(good_close(sock)); + return await_server(); +} + +/* Exect a ne_sock_peek() to return EOF */ +static int expect_peek_close(ne_socket *sock) +{ + ssize_t n = ne_sock_read(sock, buffer, 1); + ONV(n != NE_SOCK_CLOSED, ("peek gave %" NE_FMT_SSIZE_T " not closure", n)); + return OK; +} + +/* Test that just does a connect then a close. */ +static int read_close(void) +{ + ne_socket *sock; + + CALL(begin(&sock, serve_close, NULL)); + CALL(expect_close(sock)); + ONN("close failed", ne_sock_close(sock)); + return await_server(); +} + +/* Test that just does a connect then a close (but gets the close via + * ne_sock_peek). */ +static int peek_close(void) +{ + ne_socket *sock; + + CALL(begin(&sock, serve_close, NULL)); + CALL(expect_peek_close(sock)); + ONN("close failed", ne_sock_close(sock)); + return await_server(); +} + + +struct string { + char *data; + size_t len; +}; + +static int serve_string(ne_socket *sock, void *ud) +{ + struct string *str = ud; + + NE_DEBUG(NE_DBG_SOCKET, "Serving string: [[[%.*s]]]\n", + (int)str->len, str->data); + + ONN("write failed", ne_sock_fullwrite(sock, str->data, str->len)); + + return 0; +} + +static int serve_string_slowly(ne_socket *sock, void *ud) +{ + struct string *str = ud; + size_t n; + + NE_DEBUG(NE_DBG_SOCKET, "Slowly serving string: [[[%.*s]]]\n", + (int)str->len, str->data); + + for (n = 0; n < str->len; n++) { + ONN("write failed", ne_sock_fullwrite(sock, &str->data[n], 1)); + minisleep(); + } + + return 0; +} + +/* Don't change this string. */ +#define STR "Hello, World." + +/* do a sock_peek() on sock for 'len' bytes, and expect 'str'. */ +static int peek_expect(ne_socket *sock, const char *str, size_t len) +{ + ssize_t ret = ne_sock_peek(sock, buffer, len); + ONV((ssize_t)len != ret, + ("peek got %" NE_FMT_SSIZE_T " bytes not %" NE_FMT_SIZE_T, ret, len)); + ONV(memcmp(str, buffer, len), + ("peek mismatch: `%.*s' not `%.*s'", + (int)len, buffer, (int)len, str)); + return OK; +} + +/* do a sock_read() on sock for 'len' bytes, and expect 'str'. */ +static int read_expect(ne_socket *sock, const char *str, size_t len) +{ + ssize_t ret = ne_sock_read(sock, buffer, len); + ONV((ssize_t)len != ret, + ("peek got %" NE_FMT_SSIZE_T " bytes not %" NE_FMT_SIZE_T, ret, len)); + ONV(memcmp(str, buffer, len), + ("read mismatch: `%.*s' not `%.*s'", + (int)len, buffer, (int)len, str)); + return OK; +} + +/* Declare a struct string */ +#define DECL(var,str) struct string var = { str, 0 }; var.len = strlen(str) + +/* Test a simple read. */ +static int single_read(void) +{ + ne_socket *sock; + DECL(hello, STR); + + CALL(begin(&sock, serve_string, &hello)); + CALL(read_expect(sock, STR, strlen(STR))); + CALL(expect_close(sock)); + CALL(good_close(sock)); + + return await_server(); +} + +/* Test a simple peek. */ +static int single_peek(void) +{ + ne_socket *sock; + DECL(hello, STR); + + CALL(begin(&sock, serve_string, &hello)); + CALL(peek_expect(sock, STR, strlen(STR))); + + return finish(sock, 0); +} + +/* Test lots of 1-byte reads. */ +static int small_reads(void) +{ + ne_socket *sock; + char *pnt; + DECL(hello, STR); + + CALL(begin(&sock, serve_string, &hello)); + + /* read the string byte-by-byte. */ + for (pnt = hello.data; *pnt; pnt++) { + CALL(read_expect(sock, pnt, 1)); + } + + return finish(sock, 1); +} + +/* peek or read, expecting to get given string. */ +#define READ(str) CALL(read_expect(sock, str, strlen(str))) +#define PEEK(str) CALL(peek_expect(sock, str, strlen(str))) + +/* Stress out the read buffer handling a little. */ +static int read_and_peek(void) +{ + ne_socket *sock; + DECL(hello, STR); + + CALL(begin(&sock, serve_string, &hello)); + + PEEK("Hello"); + PEEK("Hell"); + PEEK(STR); + READ("He"); + PEEK("llo, "); + READ("l"); + PEEK("lo, World."); + READ("lo, Worl"); + PEEK("d."); PEEK("d"); + READ("d."); + + return finish(sock, 1); +} + +/* Read more bytes than were written. */ +static int larger_read(void) +{ + ne_socket *sock; + ssize_t nb; + DECL(hello, STR); + + CALL(begin(&sock, serve_string, &hello)); + + nb = ne_sock_read(sock, buffer, hello.len + 10); + ONV(nb != (ssize_t)hello.len, + ("read gave too many bytes (%" NE_FMT_SSIZE_T ")", nb)); + ONN("read gave wrong data", memcmp(buffer, hello.data, hello.len)); + + return finish(sock, 1); +} + +static int line_expect(ne_socket *sock, const char *line) +{ + ssize_t ret = ne_sock_readline(sock, buffer, BUFSIZ); + size_t len = strlen(line); + ONV(ret == NE_SOCK_CLOSED, ("socket closed, expecting `%s'", line)); + ONV(ret < 0, ("socket error `%s', expecting `%s'", + ne_sock_error(sock), line)); + ONV((size_t)ret != len, + ("readline got %" NE_FMT_SSIZE_T ", expecting `%s'", ret, line)); + ONV(strcmp(line, buffer), + ("readline mismatch: `%s' not `%s'", buffer, line)); + return OK; +} + +#define LINE(x) CALL(line_expect(sock, x)) + +#define STR2 "Goodbye, cruel world." + +static int line_simple(void) +{ + ne_socket *sock; + DECL(oneline, STR "\n" STR2 "\n"); + + CALL(begin(&sock, serve_string, &oneline)); + LINE(STR "\n"); + LINE(STR2 "\n"); + + return finish(sock, 1); +} + +static int line_closure(void) +{ + ne_socket *sock; + ssize_t ret; + DECL(oneline, STR "\n" "foobar"); + + CALL(begin(&sock, serve_string, &oneline)); + LINE(STR "\n"); + + ret = ne_sock_readline(sock, buffer, BUFSIZ); + ONV(ret != NE_SOCK_CLOSED, + ("readline got %" NE_FMT_SSIZE_T " not EOF", ret)); + + return finish(sock, 0); +} + +/* check that empty lines are handled correctly. */ +static int line_empty(void) +{ + ne_socket *sock; + DECL(oneline, "\n\na\n\n"); + + CALL(begin(&sock, serve_string, &oneline)); + LINE("\n"); LINE("\n"); + LINE("a\n"); LINE("\n"); + + return finish(sock, 1); +} + +static int line_toolong(void) +{ + ne_socket *sock; + ssize_t ret; + DECL(oneline, "AAAAAA\n"); + + CALL(begin(&sock, serve_string, &oneline)); + ret = ne_sock_readline(sock, buffer, 5); + ONV(ret != NE_SOCK_ERROR, + ("readline should fail on long line: %" NE_FMT_SSIZE_T, ret)); + + return finish(sock, 0); +} + +/* readline()s mingled with other operations: buffering tests. */ +static int line_mingle(void) +{ + ne_socket *sock; + DECL(oneline, "alpha\nbeta\ndelta\ngamma\n"); + + CALL(begin(&sock, serve_string, &oneline)); + + READ("a"); LINE("lpha\n"); + READ("beta"); LINE("\n"); + PEEK("d"); PEEK("delt"); + LINE("delta\n"); + READ("gam"); LINE("ma\n"); + + return finish(sock, 1); +} + +/* readline which needs multiple read() calls. */ +static int line_chunked(void) +{ + ne_socket *sock; + DECL(oneline, "this is a line\n"); + + CALL(begin(&sock, serve_string_slowly, &oneline)); + + LINE("this is a line\n"); + + return finish(sock, 1); +} + +static time_t to_start, to_finish; + +static int to_begin(ne_socket **sock) +{ + CALL(begin(sock, sleepy_server, NULL)); + ne_sock_read_timeout(*sock, 1); + to_start = time(NULL); + return OK; +} + +static int to_end(ne_socket *sock) +{ + to_finish = time(NULL); + reap_server(); /* hopefully it's hung. */ + ONN("timeout ignored, or very slow machine", to_finish - to_start > 3); + ONN("close failed", ne_sock_close(sock)); + return OK; +} + +#define TO_BEGIN ne_socket *sock; CALL(to_begin(&sock)) +#define TO_OP(x) do { int to_ret = (x); \ +ONV(to_ret != NE_SOCK_TIMEOUT, ("operation did not timeout: %d", to_ret)); \ +} while (0) +#define TO_FINISH return to_end(sock) + +static int peek_timeout(void) +{ + TO_BEGIN; + TO_OP(ne_sock_peek(sock, buffer, 1)); + TO_FINISH; +} + +static int read_timeout(void) +{ + TO_BEGIN; + TO_OP(ne_sock_read(sock, buffer, 1)); + TO_FINISH; +} + +static int readline_timeout(void) +{ + TO_BEGIN; + TO_OP(ne_sock_readline(sock, buffer, 1)); + TO_FINISH; +} + +static int fullread_timeout(void) +{ + TO_BEGIN; + TO_OP(ne_sock_fullread(sock, buffer, 1)); + TO_FINISH; +} + +static int serve_expect(ne_socket *sock, void *ud) +{ + struct string *str = ud; + ssize_t ret; + + while (str->len && + (ret = ne_sock_read(sock, buffer, sizeof(buffer))) > 0) { + NE_DEBUG(NE_DBG_SOCKET, "Got %" NE_FMT_SSIZE_T " bytes.\n", ret); + ONV(memcmp(str->data, buffer, ret), + ("unexpected data: [%.*s] not [%.*s]", + (int)ret, buffer, (int)ret, str->data)); + str->data += ret; + str->len -= ret; + NE_DEBUG(NE_DBG_SOCKET, "%" NE_FMT_SIZE_T " bytes left.\n", str->len); + } + + NE_DEBUG(NE_DBG_SOCKET, "All data read.\n"); + return OK; +} + +static int full_write(ne_socket *sock, const char *data, size_t len) +{ + int ret = ne_sock_fullwrite(sock, data, len); + ONV(ret, ("write failed (%d): %s", ret, ne_sock_error(sock))); + return OK; +} + +#define WRITEL(str) CALL(full_write(sock, str, strlen(str))); \ +minisleep() + +static int small_writes(void) +{ + ne_socket *sock; + DECL(str, "This\nIs\nSome\nText.\n"); + + CALL(begin(&sock, serve_expect, &str)); + + WRITEL("This\n"); WRITEL("Is\n"); WRITEL("Some\n"); WRITEL("Text.\n"); + + return finish(sock, 1); +} + +static int large_writes(void) +{ +#define LARGE_SIZE (123456) + struct string str; + ne_socket *sock; + ssize_t n; + + str.data = ne_malloc(LARGE_SIZE); + str.len = LARGE_SIZE; + + for (n = 0; n < LARGE_SIZE; n++) + str.data[n] = 41 + n % 130; + + CALL(begin(&sock, serve_expect, &str)); + CALL(full_write(sock, str.data, str.len)); + + ne_free(str.data); + return finish(sock, 1); +} + +/* echoes back lines. */ +static int echo_server(ne_socket *sock, void *ud) +{ + ssize_t ret; + + while ((ret = ne_sock_readline(sock, buffer, sizeof(buffer))) > 0) { + NE_DEBUG(NE_DBG_SOCKET, "Line: %s", buffer); + ONN("write failed", ne_sock_fullwrite(sock, buffer, ret)); + NE_DEBUG(NE_DBG_SOCKET, "Wrote line.\n"); + } + + ONN("readline failed", ret != NE_SOCK_CLOSED); + return 0; +} + +static int echo_expect(ne_socket *sock, const char *line) +{ + CALL(full_write(sock, line, strlen(line))); + return line_expect(sock, line); +} + +#define ECHO(line) CALL(echo_expect(sock, line)) + +static int echo_lines(void) +{ + ne_socket *sock; + CALL(begin(&sock, echo_server, NULL)); + ECHO("hello,\n"); + ECHO("\n"); + ECHO("world\n"); + return finish(sock, 0); +} + +#ifdef SOCKET_SSL +/* harder to simulate closure cases for an SSL connection, since it + * may be doing multiple read()s or write()s per ne_sock_* call. */ +static int ssl_closure(void) +{ + ne_socket *sock; + ssize_t ret; + CALL(begin(&sock, serve_close, NULL)); + CALL(full_write(sock, "a", 1)); + CALL(await_server()); + ret = ne_sock_fullwrite(sock, "a", 1); + ONV(ret != NE_SOCK_RESET && ret != NE_SOCK_CLOSED, + ("write got %" NE_FMT_SSIZE_T " not reset or closure", ret)); + return good_close(sock); +} + +static int serve_truncate(ne_socket *sock, void *userdata) +{ + exit(0); +} + +/* when an EOF is received without a clean shutdown (close_notify + message). */ +static int ssl_truncate(void) +{ + ne_socket *sock; int ret; + + CALL(begin(&sock, serve_truncate, NULL)); + ret = ne_sock_read(sock, buffer, 1); + ONV(ret != NE_SOCK_TRUNC, + ("socket got error %d not truncation: `%s'", ret, + ne_sock_error(sock))); + return finish(sock, 0); +} + +#else +/* thanks to W Richard Stevens for the precise repro case for getting + * an RST on demand. */ +static int write_reset(void) +{ + ne_socket *sock; + int ret; + CALL(begin(&sock, serve_close, NULL)); + CALL(full_write(sock, "a", 1)); + CALL(await_server()); + ret = ne_sock_fullwrite(sock, "a", 1); + ONV(ret != NE_SOCK_RESET, ("write got %d not reset", ret)); + return good_close(sock); +} + +static int read_reset(void) +{ + ne_socket *sock; + ssize_t ret; + CALL(begin(&sock, serve_close, NULL)); + CALL(full_write(sock, "a", 1)); + CALL(await_server()); + ret = ne_sock_read(sock, buffer, 1); + ONV(ret != NE_SOCK_RESET, ("read got %" NE_FMT_SSIZE_T " not reset", ret)); + return good_close(sock); +} +#endif + +ne_test tests[] = { + T(multi_init), + T_LEAKY(resolve), + T(resolve_numeric), +#ifdef SOCKET_SSL + T_LEAKY(init_ssl), +#endif + T(addr_make_v4), + T(addr_make_v6), + T(addr_compare), + T(just_connect), + T(addr_connect), + T(read_close), + T(peek_close), + T(single_read), + T(single_peek), + T(small_reads), + T(read_and_peek), + T(larger_read), + T(line_simple), + T(line_closure), + T(line_empty), + T(line_toolong), + T(line_mingle), + T(line_chunked), + T(small_writes), + T(large_writes), + T(echo_lines), +#ifdef SOCKET_SSL + T(ssl_closure), + T(ssl_truncate), +#else + T(write_reset), + T(read_reset), +#endif + T(read_timeout), + T(peek_timeout), + T(readline_timeout), + T(fullread_timeout), + T(NULL) +}; diff --git a/test/ssl.c b/test/ssl.c new file mode 100644 index 0000000..7fc94e4 --- /dev/null +++ b/test/ssl.c @@ -0,0 +1,1476 @@ +/* + neon test suite + Copyright (C) 2002-2003, Joe Orton <joe@manyfish.co.uk> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "config.h" + +#include <sys/types.h> + +#include <sys/stat.h> + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "ne_request.h" +#include "ne_socket.h" +#include "ne_auth.h" + +#include "tests.h" +#include "child.h" +#include "utils.h" + +#ifndef NEON_SSL +/* this file shouldn't be built if SSL is not enabled. */ +#error SSL not supported +#endif + +#include <openssl/ssl.h> +#include <openssl/err.h> +#include <openssl/evp.h> + +#define ERROR_SSL_STRING (ERR_reason_error_string(ERR_get_error())) + +#define SERVER_CERT "server.cert" +#define CA_CERT "ca/cert.pem" + +#define SERVER_DNAME "Neon QA Dept, Neon Hackers Ltd, " \ + "Cambridge, Cambridgeshire, GB" +#define CACERT_DNAME "Random Dept, Neosign, Oakland, California, US" + +static SSL_CTX *server_ctx = NULL; + +static char *srcdir = "."; + +static ne_ssl_certificate *def_ca_cert = NULL, *def_server_cert; +static ne_ssl_client_cert *def_cli_cert; + +static int check_dname(const ne_ssl_dname *dn, const char *expected, + const char *which); + +static int s_strwrite(SSL *s, const char *buf) +{ + size_t len = strlen(buf); + + ONV(SSL_write(s, buf, len) != (int)len, + ("SSL_write failed: %s", ERROR_SSL_STRING)); + + return OK; +} + +/* Do an SSL response over socket given context; returning ssl session + * structure in *sess if sess is non-NULL. */ +static int do_ssl_response(ne_socket *sock, SSL_CTX *ctx, SSL_SESSION **sess, + const char *resp, int unclean) +{ + int fd = ne_sock_fd(sock), ret; + /* we don't want OpenSSL to close this socket for us. */ + BIO *bio = BIO_new_socket(fd, BIO_NOCLOSE); + char buf[BUFSIZ]; + SSL *ssl = SSL_new(ctx); + + ONN("SSL_new failed", ssl == NULL); + + SSL_set_bio(ssl, bio, bio); + + ONV(SSL_accept(ssl) != 1, + ("SSL_accept failed: %s", ERROR_SSL_STRING)); + + ret = SSL_read(ssl, buf, BUFSIZ - 1); + if (ret == 0) + return 0; /* connection closed by parent; give up. */ + ONV(ret < 0, ("SSL_read failed (%d): %s", ret, ERROR_SSL_STRING)); + + buf[ret] = '\0'; + + NE_DEBUG(NE_DBG_HTTP, "Request over SSL was: [%s]\n", buf); + + ONN("request over SSL contained Proxy-Authorization header", + strstr(buf, "Proxy-Authorization:") != NULL); + + CALL(s_strwrite(ssl, resp)); + + /* copy out the session if requested. */ + if (sess) { + *sess = SSL_get1_session(ssl); + } + + if (!unclean) { + /* Erk, shutdown is messy! See Eric Rescorla's article: + * http://www.linuxjournal.com/article.php?sid=4822 ; we'll just + * hide our heads in the sand here. */ + SSL_shutdown(ssl); + SSL_free(ssl); + } + + return 0; +} + +#define DEF_RESP "HTTP/1.0 200 OK\r\nContent-Length: 0\r\n\r\n" + +/* Standard server callback to send an HTTP response; SSL negotiated + * using certificate passed as userdata. */ +static int serve_ssl(ne_socket *sock, void *ud) +{ + const char *cert = ud; + + NE_DEBUG(NE_DBG_HTTP, "using server cert %s\n", cert); + + ONN("failed to load certificate", + !SSL_CTX_use_certificate_file(server_ctx, cert, SSL_FILETYPE_PEM)); + + CALL(do_ssl_response(sock, server_ctx, NULL, DEF_RESP, 0)); + + return OK; +} + +static int serve_response_unclean(ne_socket *sock, void *ud) +{ + const char *resp = ud; + + ONN("failed to load certificate", + !SSL_CTX_use_certificate_file(server_ctx, + SERVER_CERT, SSL_FILETYPE_PEM)); + + CALL(do_ssl_response(sock, server_ctx, NULL, resp, 1)); + + return OK; +} + +/* Server function which requires the use of a client cert. + * 'userdata' must be the name of the file giving acceptable CA + * certificates. */ +static int serve_ccert(ne_socket *sock, void *ud) +{ + const char *calist = ud; + + ONN("failed to load certificate", + !SSL_CTX_use_certificate_file(server_ctx, SERVER_CERT, SSL_FILETYPE_PEM)); + + /* require a client cert. */ + SSL_CTX_set_verify(server_ctx, SSL_VERIFY_PEER | + SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); + + /* load the CA used to verify the client cert. */ + ONN("failed to load CA cert", + SSL_CTX_load_verify_locations(server_ctx, CA_CERT, NULL) != 1); + + if (calist) { + /* send acceptable CA cert list to the client */ + SSL_CTX_set_client_CA_list(server_ctx, SSL_load_client_CA_file(calist)); + } + + CALL(do_ssl_response(sock, server_ctx, NULL, DEF_RESP, 0)); + + return OK; +} + +/* serve_ssl wrapper which ignores server failure and always succeeds */ +static int fail_serve(ne_socket *sock, void *ud) +{ + serve_ssl(sock, ud); + return OK; +} + +/* Wrapper for serve_ssl which registers the verify location, so that + * the CA cert will be sent along with the server cert itself in the + * certificate exchange. */ +static int serve_ssl_chained(ne_socket *sock, void *ud) +{ + SSL_CTX_load_verify_locations(server_ctx, "ca/cert.pem", NULL); + return serve_ssl(sock, ud); +} + +#define DEFSESS (ne_session_create("https", "localhost", 7777)) + +/* Run a request in the given session. */ +static int any_ssl_request(ne_session *sess, server_fn fn, void *server_ud, + char *ca_cert, + ne_ssl_verify_fn verify_fn, void *verify_ud) +{ + int ret; + + if (ca_cert) { + ne_ssl_certificate *ca = ne_ssl_cert_read(ca_cert); + ONV(ca == NULL, ("could not load CA cert `%s'", ca_cert)); + ne_ssl_trust_cert(sess, ca); + ne_ssl_cert_free(ca); + } + + CALL(spawn_server(7777, fn, server_ud)); + + if (verify_fn) + ne_ssl_set_verify(sess, verify_fn, verify_ud); + + ret = any_request(sess, "/foo"); + + CALL(await_server()); + + ONREQ(ret); + + return OK; +} + +static int init(void) +{ + char *server_key; + + /* take srcdir as argv[1]. */ + if (test_argc > 1) { + srcdir = test_argv[1]; + server_key = ne_concat(srcdir, "/server.key", NULL); + } else { + server_key = "server.key"; + } + + if (ne_sock_init()) { + t_context("could not initialize socket/SSL library."); + return FAILHARD; + } + + server_ctx = SSL_CTX_new(SSLv23_server_method()); + if (server_ctx == NULL) { + t_context("could not create SSL_CTX: %s", ERROR_SSL_STRING); + return FAILHARD; + } else if (!SSL_CTX_use_PrivateKey_file(server_ctx, server_key, + SSL_FILETYPE_PEM)) { + t_context("failed to load private key: %s", ERROR_SSL_STRING); + return FAILHARD; + } + + def_ca_cert = ne_ssl_cert_read(CA_CERT); + if (def_ca_cert == NULL) { + t_context("couldn't load CA cert %s", CA_CERT); + return FAILHARD; + } + + def_server_cert = ne_ssl_cert_read(SERVER_CERT); + if (def_server_cert == NULL) { + t_context("couldn't load server cert %s", SERVER_CERT); + return FAILHARD; + } + + /* tests for the encrypted client cert, client.p12 */ + def_cli_cert = ne_ssl_clicert_read("client.p12"); + ONN("could not load client.p12", def_cli_cert == NULL); + + ONN("client.p12 is not encrypted!?", + !ne_ssl_clicert_encrypted(def_cli_cert)); + + ONN("failed to decrypt client.p12", + ne_ssl_clicert_decrypt(def_cli_cert, "foobar")); + + return OK; +} + +/* just check the result codes of loading server certs. */ +static int load_server_certs(void) +{ + ne_ssl_certificate *cert; + + cert = ne_ssl_cert_read("Makefile"); + ONN("invalid CA cert file loaded successfully", cert != NULL); + + cert = ne_ssl_cert_read("nonesuch.pem"); + ONN("non-existent 'nonesuch.pem' loaded successfully", cert != NULL); + + cert = ne_ssl_cert_read("ssigned.pem"); + ONN("could not load ssigned.pem", cert == NULL); + ne_ssl_cert_free(cert); + + return OK; +} + +static int trust_default_ca(void) +{ + ne_session *sess = DEFSESS; + ne_ssl_trust_default_ca(sess); + ne_session_destroy(sess); + return OK; +} + +#define CC_NAME "Just A Neon Client Cert" + +/* Tests for loading client certificates */ +static int load_client_cert(void) +{ + ne_ssl_client_cert *cc; + const ne_ssl_certificate *cert; + const char *name; + + cc = ne_ssl_clicert_read("client.p12"); + ONN("could not load client.p12", cc == NULL); + ONN("client.p12 not encrypted!?", !ne_ssl_clicert_encrypted(cc)); + name = ne_ssl_clicert_name(cc); + ONN("no friendly name given", name == NULL); + ONV(strcmp(name, CC_NAME), ("friendly name was %s not %s", name, CC_NAME)); + ONN("failed to decrypt", ne_ssl_clicert_decrypt(cc, "foobar")); + ne_ssl_clicert_free(cc); + + cc = ne_ssl_clicert_read("client.p12"); + ONN("decrypted client.p12 with incorrect password!?", + ne_ssl_clicert_decrypt(cc, "barfoo") == 0); + ne_ssl_clicert_free(cc); + + /* tests for the unencrypted client cert, client2.p12 */ + cc = ne_ssl_clicert_read("unclient.p12"); + ONN("could not load unencrypted cert unclient.p12", cc == NULL); + ONN("unencrypted cert marked encrypted?", ne_ssl_clicert_encrypted(cc)); + cert = ne_ssl_clicert_owner(cc); + ONN("client cert had no certificate", cert == NULL); + CALL(check_dname(ne_ssl_cert_subject(cert), + "Neon Client Cert, Neon Hackers Ltd, " + "Cambridge, Cambridgeshire, GB", + "client cert subject")); + CALL(check_dname(ne_ssl_cert_issuer(cert), CACERT_DNAME, + "client cert issuer")); + ne_ssl_clicert_free(cc); + + /* test for ccert without a friendly name, noclient.p12 */ + cc = ne_ssl_clicert_read("noclient.p12"); + ONN("could not load noclient.p12", cc == NULL); + name = ne_ssl_clicert_name(cc); + ONV(name != NULL, ("noclient.p12 had friendly name `%s'", name)); + ne_ssl_clicert_free(cc); + + /* tests for loading bogus files. */ + cc = ne_ssl_clicert_read("Makefile"); + ONN("loaded Makefile as client cert!?", cc != NULL); + + /* test for loading nonexistent file. */ + cc = ne_ssl_clicert_read("nosuch.pem"); + ONN("loaded nonexistent file as client cert!?", cc != NULL); + + return OK; +} + +/* Test that 'cert', which is signed by CA_CERT, is accepted + * unconditionaly. */ +static int accept_signed_cert(char *cert) +{ + ne_session *sess = DEFSESS; + /* no verify callback needed. */ + CALL(any_ssl_request(sess, serve_ssl, cert, CA_CERT, NULL, NULL)); + ne_session_destroy(sess); + return OK; +} + +static int simple(void) +{ + return accept_signed_cert(SERVER_CERT); +} + +/* Serves using HTTP/1.0 get-till-EOF semantics. */ +static int serve_eof(ne_socket *sock, void *ud) +{ + const char *cert = ud; + + NE_DEBUG(NE_DBG_HTTP, "using server cert %s\n", cert); + + ONN("failed to load certificate", + !SSL_CTX_use_certificate_file(server_ctx, cert, SSL_FILETYPE_PEM)); + + CALL(do_ssl_response(sock, server_ctx, NULL, + "HTTP/1.0 200 OK\r\n" + "Connection: close\r\n" + "\r\n" + "This is a response body, like it or not.", + 0)); + + return OK; +} + +/* Test read-til-EOF behaviour with SSL. */ +static int simple_eof(void) +{ + ne_session *sess = DEFSESS; + + CALL(any_ssl_request(sess, serve_eof, SERVER_CERT, CA_CERT, NULL, NULL)); + ne_session_destroy(sess); + return OK; +} + +static int empty_truncated_eof(void) +{ + ne_session *sess = DEFSESS; + + CALL(any_ssl_request(sess, serve_response_unclean, + "HTTP/1.0 200 OK\r\n" "\r\n", + CA_CERT, NULL, NULL)); + + ne_session_destroy(sess); + return OK; +} + +static int fail_truncated_eof(void) +{ + ne_session *sess = DEFSESS; + int ret; + + ne_ssl_trust_cert(sess, def_ca_cert); + CALL(spawn_server(7777, serve_response_unclean, + "HTTP/1.0 200 OK\r\n" "\r\n" + "This is some content\n" + "Followed by a truncation attack!\n")); + + ret = any_request(sess, "/foo"); + CALL(await_server()); + + ONV(ret != NE_ERROR, + ("request failed with %d not error: `%s'", ret, ne_get_error(sess))); + ne_session_destroy(sess); + return OK; +} + +/* Server function which just sends a string then EOF. */ +static int just_serve_string(ne_socket *sock, void *userdata) +{ + const char *str = userdata; + server_send(sock, str, strlen(str)); + return 0; +} + +/* test for the SSL negotiation failing. */ +static int fail_not_ssl(void) +{ + ne_session *sess = DEFSESS; + int ret; + + CALL(spawn_server(7777, just_serve_string, "Hello, world.\n")); + ret = any_request(sess, "/bar"); + CALL(await_server()); + + ONN("request did not fail", ret != NE_ERROR); + + ne_session_destroy(sess); + return OK; +} + +static int wildcard_ok = 0; + +static int wildcard_init(void) +{ + struct stat stbuf; + + t_context("wildcard.cert not found:\n" + "This test requires a Linux-like hostname command, see makekeys.sh"); + PRECOND(stat("wildcard.cert", &stbuf) == 0); + + PRECOND(lookup_hostname() == OK); + + wildcard_ok = 1; + return OK; +} + +static int wildcard_match(void) +{ + ne_session *sess; + + PRECOND(wildcard_ok); + + sess = ne_session_create("https", local_hostname, 7777); + + CALL(any_ssl_request(sess, serve_ssl, + "wildcard.cert", CA_CERT, NULL, NULL)); + ne_session_destroy(sess); + + return OK; +} + +/* Check that hostname comparisons are not cases-sensitive. */ +static int caseless_match(void) +{ + return accept_signed_cert("caseless.cert"); +} + +/* Test that the subjectAltName extension has precedence over the + * commonName attribute */ +static int subject_altname(void) +{ + return accept_signed_cert("altname.cert"); +} + +/* tests for multiple altNames. */ +static int two_subject_altname(void) +{ + return accept_signed_cert("altname2.cert"); +} + +static int two_subject_altname2(void) +{ + return accept_signed_cert("altname3.cert"); +} + +/* Test that a subject altname with *only* an eMail entry is + * ignored, and the commonName is used instead. */ +static int notdns_altname(void) +{ + return accept_signed_cert("altname4.cert"); +} + +/* test that the *most specific* commonName attribute is used. */ +static int multi_commonName(void) +{ + return accept_signed_cert("twocn.cert"); +} + +/* regression test for neon <= 0.23.4 where if commonName was the first + * RDN in the subject DN, it was ignored. */ +static int commonName_first(void) +{ + return accept_signed_cert("cnfirst.cert"); +} + +static int check_dname(const ne_ssl_dname *dn, const char *expected, + const char *which) +{ + char *dname; + + ONV(dn == NULL, ("certificate %s dname was NULL", which)); + + dname = ne_ssl_readable_dname(dn); + + NE_DEBUG(NE_DBG_SSL, "Got dname `%s', expecting `%s'\n", dname, expected); + + ONV(strcmp(dname, expected), + ("certificate %s dname was `%s' not `%s'", which, dname, expected)); + + ne_free(dname); + + return 0; +} + +/* Check that the readable subject issuer dnames of 'cert' match + * 'subject' and 'issuer' (if non-NULL). */ +static int check_cert_dnames(const ne_ssl_certificate *cert, + const char *subject, const char *issuer) +{ + ONN("no server certificate presented", cert == NULL); + CALL(check_dname(ne_ssl_cert_subject(cert), subject, "subject")); + return issuer ? check_dname(ne_ssl_cert_issuer(cert), issuer, "issuer") : OK; +} + +/* Verify callback which checks that the certificate presented has the + * predetermined subject and issuer DN (as per makekeys.sh). */ +static int check_cert(void *userdata, int fs, const ne_ssl_certificate *cert) +{ + int *ret = userdata; + + if (check_cert_dnames(cert, SERVER_DNAME, CACERT_DNAME) == FAIL) + *ret = -1; + else + *ret = 1; + + return 0; +} + +/* Check that certificate attributes are passed correctly. */ +static int parse_cert(void) +{ + ne_session *sess = DEFSESS; + int ret = 0; + + /* don't give a CA cert; should force the verify callback to be + * used. */ + CALL(any_ssl_request(sess, serve_ssl, SERVER_CERT, NULL, + check_cert, &ret)); + ne_session_destroy(sess); + + ONN("cert verification never called", ret == 0); + + if (ret == -1) + return FAIL; + + return OK; +} + +/* Check the certificate chain presented against known dnames. */ +static int check_chain(void *userdata, int fs, const ne_ssl_certificate *cert) +{ + int *ret = userdata; + + if (check_cert_dnames(cert, SERVER_DNAME, CACERT_DNAME) == FAIL) { + *ret = -1; + return 0; + } + + cert = ne_ssl_cert_signedby(cert); + if (cert == NULL) { + t_context("no CA cert in chain"); + *ret = -1; + return 0; + } + + if (check_cert_dnames(cert, CACERT_DNAME, CACERT_DNAME) == FAIL) { + *ret = -1; + return 0; + } + + *ret = 1; + return 0; +} + +/* Check that certificate attributes are passed correctly. */ +static int parse_chain(void) +{ + ne_session *sess = DEFSESS; + int ret = 0; + + /* don't give a CA cert; should force the verify callback to be + * used. */ + CALL(any_ssl_request(sess, serve_ssl_chained, SERVER_CERT, NULL, + check_chain, &ret)); + ne_session_destroy(sess); + + ONN("cert verification never called", ret == 0); + + if (ret == -1) + return FAIL; + + return OK; +} + + +static int count_vfy(void *userdata, int fs, const ne_ssl_certificate *c) +{ + int *count = userdata; + (*count)++; + return 0; +} + +static int no_verify(void) +{ + ne_session *sess = DEFSESS; + int count = 0; + + CALL(any_ssl_request(sess, serve_ssl, SERVER_CERT, CA_CERT, count_vfy, + &count)); + + ONN("verify callback called unnecessarily", count != 0); + + ne_session_destroy(sess); + + return OK; +} + +static int cache_verify(void) +{ + ne_session *sess = DEFSESS; + int ret, count = 0; + + /* force verify cert. */ + ret = any_ssl_request(sess, serve_ssl, SERVER_CERT, NULL, count_vfy, + &count); + + CALL(spawn_server(7777, serve_ssl, SERVER_CERT)); + ret = any_request(sess, "/foo2"); + CALL(await_server()); + + ONV(count != 1, + ("verify callback result not cached: called %d times", count)); + + ne_session_destroy(sess); + + return OK; +} + +/* Copy failures into *userdata, and fail verification. */ +static int get_failures(void *userdata, int fs, const ne_ssl_certificate *c) +{ + int *out = userdata; + *out = fs; + return -1; +} + +/* Helper function: run a request using the given self-signed server + * certificate, and expect the request to fail with the given + * verification failure flags. */ +static int fail_ssl_request(char *cert, char *cacert, + const char *msg, int failures) +{ + ne_session *sess = DEFSESS; + int gotf = 0, ret; + + ret = any_ssl_request(sess, fail_serve, cert, cacert, + get_failures, &gotf); + + ONV(gotf == 0, + ("no error in verification callback; request failed: %s", + ne_get_error(sess))); + + ONV(gotf & ~NE_SSL_FAILMASK, + ("verification flags %x outside mask %x", gotf, NE_SSL_FAILMASK)); + + /* check the failure flags were as expected. */ + ONV(failures != gotf, + ("verification flags were %d not %d", gotf, failures)); + + /* and check that the request was failed too. */ + ONN(msg, ret == NE_OK); + + ne_session_destroy(sess); + + return OK; +} + +/* Note that the certs used for fail_* are all self-signed, so the + * cert is passed as CA cert and server cert to fail_ssl_request. */ + +/* Check that a certificate with the incorrect commonName attribute is + * flagged as such. */ +static int fail_wrongCN(void) +{ + return fail_ssl_request("wrongcn.pem", "wrongcn.pem", + "certificate with incorrect CN was accepted", + NE_SSL_IDMISMATCH); +} + +/* Check that an expired certificate is flagged as such. */ +static int fail_expired(void) +{ + char *c = ne_concat(srcdir, "/expired.pem", NULL); + CALL(fail_ssl_request(c, c, "expired certificate was accepted", + NE_SSL_EXPIRED)); + ne_free(c); + return OK; +} + +static int fail_notvalid(void) +{ + char *c = ne_concat(srcdir, "/notvalid.pem", NULL); + CALL(fail_ssl_request(c, c, "not yet valid certificate was accepted", + NE_SSL_NOTYETVALID)); + ne_free(c); + return OK; +} + +/* Check that a server cert with a random issuer and self-signed cert + * fail with UNTRUSTED. */ +static int fail_untrusted_ca(void) +{ + return fail_ssl_request("server.cert", NULL, "untrusted CA.", + NE_SSL_UNTRUSTED); +} + +static int fail_self_signed(void) +{ + return fail_ssl_request("ssigned.pem", NULL, "self-signed cert", + NE_SSL_UNTRUSTED); +} + +/* Test for failure when a server cert is presented which has no + * commonName (and no alt names either). */ +static int fail_missing_CN(void) +{ + ne_session *sess = DEFSESS; + + ONN("accepted server cert with missing commonName", + any_ssl_request(sess, fail_serve, "missingcn.cert", SERVER_CERT, + NULL, NULL) == NE_OK); + + ONV(strstr(ne_get_error(sess), "missing commonName") == NULL, + ("unexpected session error `%s'", ne_get_error(sess))); + + ne_session_destroy(sess); + return OK; +} + +struct scache_args { + SSL_CTX *ctx; + char *cert; + int count; + SSL_SESSION *sess; +}; + +/* FIXME: factor out shared code with serve_ssl */ +static int serve_scache(ne_socket *sock, void *ud) +{ + struct scache_args *args = ud; + SSL_SESSION *sess; + + if (args->count == 0) { + /* enable OpenSSL's internal session cache, enabling the + * negotiation to re-use a session if both sides support it. */ + SSL_CTX_set_session_cache_mode(args->ctx, SSL_SESS_CACHE_SERVER); + + ONN("failed to load certificate", + !SSL_CTX_use_certificate_file(args->ctx, + args->cert, SSL_FILETYPE_PEM)); + } + + args->count++; + + CALL(do_ssl_response(sock, args->ctx, &sess, DEF_RESP, 0)); + + /* dump session to child.log for debugging. */ + SSL_SESSION_print_fp(ne_debug_stream, sess); + + if (args->count == 1) { + /* save the session. */ + args->sess = sess; + } else { + /* could just to do this with SSL_CTX_sess_hits really, + * but this is a more thorough test. */ + ONN("cached SSL session not used", + SSL_SESSION_cmp(args->sess, sess)); + SSL_SESSION_free(args->sess); + SSL_SESSION_free(sess); + } + + return 0; +} + +/* Test that the SSL session is cached across connections. */ +static int session_cache(void) +{ + struct scache_args args; + ne_session *sess = ne_session_create("https", "localhost", 7777); + + args.ctx = server_ctx; + args.count = 0; + args.cert = SERVER_CERT; + + ne_ssl_trust_cert(sess, def_ca_cert); + + /* have spawned server listen for several connections. */ + CALL(spawn_server_repeat(7777, serve_scache, &args, 4)); + + ONREQ(any_request(sess, "/req1")); + ONREQ(any_request(sess, "/req2")); + ne_session_destroy(sess); + /* server should still be waiting for connections: if not, + * something went wrong. */ + ONN("error from child", dead_server()); + /* now get rid of it. */ + reap_server(); + + return OK; +} + +/* Callback for client_cert_provider; takes a c. cert as userdata and + * registers it. */ +static void ccert_provider(void *userdata, ne_session *sess, + const ne_ssl_dname *const *dns, int dncount) +{ + const ne_ssl_client_cert *cc = userdata; + ne_ssl_set_clicert(sess, cc); +} + +/* Test that the on-demand client cert provider callback is used. */ +static int client_cert_provided(void) +{ + ne_session *sess = DEFSESS; + ne_ssl_client_cert *cc; + + cc = ne_ssl_clicert_read("client.p12"); + ONN("could not load client.p12", cc == NULL); + ONN("could not decrypt client.p12", + ne_ssl_clicert_decrypt(cc, "foobar")); + + ne_ssl_provide_clicert(sess, ccert_provider, cc); + CALL(any_ssl_request(sess, serve_ccert, NULL, CA_CERT, + NULL, NULL)); + + ne_session_destroy(sess); + ne_ssl_clicert_free(cc); + return OK; +} + +static void cc_check_dnames(void *userdata, ne_session *sess, + const ne_ssl_dname *const *dns, int dncount) +{ + int n, *ret = userdata; + static const char *expected[4] = { + "First Random CA, CAs Ltd., Lincoln, Lincolnshire, GB", + "Second Random CA, CAs Ltd., Falmouth, Cornwall, GB", + "Third Random CA, CAs Ltd., Ipswich, Suffolk, GB", + "Fourth Random CA, CAs Ltd., Norwich, Norfolk, GB" + }; + + ne_ssl_set_clicert(sess, def_cli_cert); + + if (dncount != 4) { + t_context("dname count was %d not 4", dncount); + *ret = -1; + return; + } + + for (n = 0; n < 4; n++) { + char which[5]; + + sprintf(which, "%d", n); + + if (check_dname(dns[n], expected[n], which) == FAIL) { + *ret = -1; + return; + } + } + + *ret = 1; +} + +/* Test for the list of acceptable dnames sent to the client. */ +static int cc_provided_dnames(void) +{ + int check = 0; + ne_session *sess = DEFSESS; + + PRECOND(def_cli_cert); + + ne_ssl_provide_clicert(sess, cc_check_dnames, &check); + + CALL(any_ssl_request(sess, serve_ccert, "calist.pem", CA_CERT, NULL, NULL)); + + ne_session_destroy(sess); + + ONN("provider function not called", check == 0); + + return (check == -1) ? FAIL : OK; +} + +/* Tests use of a client certificate. */ +static int client_cert_pkcs12(void) +{ + ne_session *sess = DEFSESS; + + PRECOND(def_cli_cert); + + ne_ssl_set_clicert(sess, def_cli_cert); + CALL(any_ssl_request(sess, serve_ccert, NULL, CA_CERT, NULL, NULL)); + + ne_session_destroy(sess); + return OK; +} + + +/* Tests use of an unencrypted client certificate. */ +static int ccert_unencrypted(void) +{ + ne_session *sess = DEFSESS; + ne_ssl_client_cert *ccert; + + ccert = ne_ssl_clicert_read("unclient.p12"); + ONN("unclient.p12 was encrypted", ne_ssl_clicert_encrypted(ccert)); + + ne_ssl_set_clicert(sess, ccert); + CALL(any_ssl_request(sess, serve_ccert, NULL, CA_CERT, NULL, NULL)); + + ne_ssl_clicert_free(ccert); + ne_session_destroy(sess); + return OK; +} + +static int serve_tunnel(ne_socket *sock, void *ud) +{ + CALL(discard_request(sock)); + + SEND_STRING(sock, "HTTP/1.1 200 OK\r\nServer: Fish\r\n\r\n"); + return serve_ssl(sock, ud); +} + +/* neon versions <= 0.21.2 segfault here because ne_sock_close would + * be called twice on the socket after the server cert verification + * fails. */ +static int fail_tunnel(void) +{ + ne_session *sess = ne_session_create("https", "example.com", 443); + ne_session_proxy(sess, "localhost", 7777); + + ONN("server cert verification didn't fail", + any_ssl_request(sess, serve_tunnel, SERVER_CERT, CA_CERT, + NULL, NULL) != NE_ERROR); + + ne_session_destroy(sess); + return OK; +} + +static int proxy_tunnel(void) +{ + ne_session *sess = ne_session_create("https", "localhost", 443); + ne_session_proxy(sess, "localhost", 7777); + + /* CA cert is trusted, so no verify callback should be needed. */ + CALL(any_ssl_request(sess, serve_tunnel, SERVER_CERT, CA_CERT, + NULL, NULL)); + + ne_session_destroy(sess); + return OK; +} + +/* a tricky test which requires spawning a second server process in + * time for a new connection after a 407. */ +static int apt_post_send(ne_request *req, void *ud, const ne_status *st) +{ + if (st->code == 407) { + NE_DEBUG(NE_DBG_HTTP, "Got 407, awaiting server...\n"); + CALL(await_server()); + NE_DEBUG(NE_DBG_HTTP, "Spawning proper tunnel server...\n"); + CALL(spawn_server(7777, serve_tunnel, SERVER_CERT)); + NE_DEBUG(NE_DBG_HTTP, "Spawned.\n"); + } + return OK; +} + +static int apt_creds(void *userdata, const char *realm, int attempt, + char *username, char *password) +{ + strcpy(username, "foo"); + strcpy(password, "bar"); + return 0; +} + +/* Test for using SSL over a CONNECT tunnel via a proxy server which + * requires authentication. Broke briefly between 0.23.x and + * 0.24.0. */ +static int auth_proxy_tunnel(void) +{ + ne_session *sess = ne_session_create("https", "localhost", 443); + int ret; + + ne_session_proxy(sess, "localhost", 7777); + ne_hook_post_send(sess, apt_post_send, NULL); + ne_set_proxy_auth(sess, apt_creds, NULL); + + CALL(spawn_server(7777, single_serve_string, + "HTTP/1.0 407 I WANT MORE BISCUITS\r\n" + "Proxy-Authenticate: Basic realm=\"bigbluesea\"\r\n" + "Connection: close\r\n" "\r\n")); + + /* trust the CA */ + ne_ssl_trust_cert(sess, def_ca_cert); + /* run the dreaded request. */ + ret = any_request(sess, "/foobar"); + CALL(await_server()); + ONREQ(ret); + + ne_session_destroy(sess); + return 0; +} + +/* Compare against known digest of notvalid.pem. Via: + * $ openssl x509 -fingerprint -sha1 -noout -in notvalid.pem */ +#define THE_DIGEST "cf:5c:95:93:76:c6:3c:01:8b:62:" \ + "b1:6f:f7:7f:42:32:ac:e6:69:1b" + +static int cert_fingerprint(void) +{ + char *fn = ne_concat(srcdir, "/notvalid.pem", NULL); + ne_ssl_certificate *cert = ne_ssl_cert_read(fn); + char digest[60]; + + ne_free(fn); + + ONN("could not load notvalid.pem", cert == NULL); + + ONN("failed to digest", ne_ssl_cert_digest(cert, digest)); + ne_ssl_cert_free(cert); + + ONV(strcmp(digest, THE_DIGEST), + ("digest was %s not %s", digest, THE_DIGEST)); + + return OK; +} + +/* verify that identity of certificate in filename 'fname' is 'identity' */ +static int check_identity(const char *fname, const char *identity) +{ + ne_ssl_certificate *cert = ne_ssl_cert_read(fname); + const char *id; + + ONV(cert == NULL, ("could not read cert `%s'", fname)); + + id = ne_ssl_cert_identity(cert); + + if (identity) { + ONV(id == NULL, ("certificate `%s' had no identity", fname)); + ONV(strcmp(id, identity), + ("certificate `%s' had identity `%s' not `%s'", fname, + id, identity)); + } else { + ONV(id != NULL, ("certificate `%s' had identity `%s' (expected none)", + fname, id)); + } + + ne_ssl_cert_free(cert); + return OK; +} + +/* check certificate identities. */ +static int cert_identities(void) +{ + static const struct { + const char *fname, *identity; + } certs[] = { + { "twocn.cert", "localhost" }, + { "altname.cert", "localhost" }, + { "altname2.cert", "nohost.example.com" }, + { "altname4.cert", "localhost" }, + { "ca4.pem", "fourth.example.com" }, + { NULL, NULL } + }; + int n; + + for (n = 0; certs[n].fname != NULL; n++) + CALL(check_identity(certs[n].fname, certs[n].identity)); + + return OK; +} + +static int check_validity(const char *fname, + const char *from, const char *until) +{ + char actfrom[NE_SSL_VDATELEN], actuntil[NE_SSL_VDATELEN]; + ne_ssl_certificate *cert; + + cert = ne_ssl_cert_read(fname); + ONV(cert == NULL, ("could not load cert `%s'", fname)); + + /* cover all calling combos for nice coverage analysis */ + ne_ssl_cert_validity(cert, NULL, NULL); + ne_ssl_cert_validity(cert, actfrom, NULL); + ne_ssl_cert_validity(cert, NULL, actuntil); + ne_ssl_cert_validity(cert, actfrom, actuntil); + + ONV(strcmp(actfrom, from), + ("%s: start time was `%s' not `%s'", fname, actfrom, from)); + + ONV(strcmp(actuntil, until), + ("%s: end time was `%s' not `%s'", fname, actuntil, until)); + + ne_ssl_cert_free(cert); + return OK; +} + +/* ceritificate validity times. */ +static int cert_validity(void) +{ + char *cert = ne_concat(srcdir, "/expired.pem", NULL); + CALL(check_validity(cert, "Jan 21 20:39:04 2002 GMT", "Jan 31 20:39:04 2002 GMT")); + ne_free(cert); + cert = ne_concat(srcdir, "/notvalid.pem", NULL); + CALL(check_validity(cert, "Dec 27 20:40:29 2023 GMT", "Dec 28 20:40:29 2023 GMT")); + ne_free(cert); + return OK; +} + +/* dname comparisons. */ +static int dname_compare(void) +{ + ne_ssl_certificate *ssigned; + const ne_ssl_dname *dn1, *dn2; + + dn1 = ne_ssl_cert_subject(def_server_cert); + dn2 = ne_ssl_cert_subject(def_server_cert); + ONN("identical subject names not equal", ne_ssl_dname_cmp(dn1, dn2) != 0); + + dn2 = ne_ssl_cert_issuer(def_server_cert); + ONN("issuer and subject names equal for signed cert", + ne_ssl_dname_cmp(dn1, dn2) == 0); + + dn1 = ne_ssl_cert_subject(def_ca_cert); + ONN("issuer of signed cert not equal to subject of CA cert", + ne_ssl_dname_cmp(dn1, dn2) != 0); + + ssigned = ne_ssl_cert_read("ssigned.pem"); + ONN("could not load ssigned.pem", ssigned == NULL); + + dn1 = ne_ssl_cert_subject(ssigned); + dn2 = ne_ssl_cert_issuer(ssigned); + ONN("issuer and subject names not equal for self-signed cert", + ne_ssl_dname_cmp(dn1, dn2)); + ne_ssl_cert_free(ssigned); + + return OK; +} + +/* tests for ne_ssl_readable_dname */ +static int dname_readable(void) +{ + ne_ssl_certificate *cert; + + cert = ne_ssl_cert_read("justmail.cert"); + ONN("could not load justmail.cert", cert == NULL); + + CALL(check_cert_dnames(cert, "blah@example.com", NULL)); + ne_ssl_cert_free(cert); + + return OK; +} + +/* test cert comparisons */ +static int cert_compare(void) +{ + ne_ssl_certificate *c1, *c2; + + c1 = ne_ssl_cert_read("server.cert"); + c2 = ne_ssl_cert_read("server.cert"); + ONN("identical certs don't compare equal", ne_ssl_cert_cmp(c1, c2) != 0); + ONN("identical certs don't compare equal", ne_ssl_cert_cmp(c2, c1) != 0); + ne_ssl_cert_free(c2); + + c2 = ne_ssl_cert_read("ssigned.pem"); + ONN("different certs don't compare different", + ne_ssl_cert_cmp(c1, c2) == 0); + ONN("different certs don't compare different", + ne_ssl_cert_cmp(c2, c1) == 0); + ne_ssl_cert_free(c2); + ne_ssl_cert_free(c1); + + return OK; +} + +/* Extract raw base64 string from a PEM file */ +static int flatten_pem(const char *fname, char **out) +{ + FILE *fp = fopen(fname, "r"); + char buf[80]; + size_t outlen = 0; + int ignore = 1; + + ONV(fp == NULL, ("could not open %s", fname)); + + *out = NULL; + + while (fgets(buf, sizeof buf, fp) != NULL) { + size_t len = strlen(buf) - 1; + + if (len < 1) continue; + + /* look for the wrapper lines. */ + if (strncmp(buf, "-----", 5) == 0) { + ignore = !ignore; + continue; + } + + /* ignore until the first wrapper line */ + if (ignore) continue; + + *out = realloc(*out, outlen + len + 1); + memcpy(*out + outlen, buf, len); + outlen += len; + } + + (*out)[outlen] = '\0'; + fclose(fp); + + return OK; +} + +/* check export cert data 'actual' against expected data 'expected */ +static int check_exported_data(const char *actual, const char *expected) +{ + ONN("could not export cert", actual == NULL); + + ONN("export data contained newline", + strchr(actual, '\r') || strchr(actual, '\n')); + + ONV(strcmp(actual, expected), ("exported cert differed from expected:\n" + "actual: %s\nexpected: %s", + actual, expected)); + return OK; +} + +/* Test import and export of certificates. The export format is PEM + * without the line feeds and wrapping; compare against . */ +static int import_export(void) +{ + char *expected, *actual; + ne_ssl_certificate *cert, *imp; + + CALL(flatten_pem("server.cert", &expected)); + + cert = ne_ssl_cert_read("server.cert"); + ONN("could not load server.cert", cert == NULL); + + /* export the cert to and compare it with the PEM file */ + actual = ne_ssl_cert_export(cert); + CALL(check_exported_data(actual, expected)); + + /* import the exported cert data, check it looks the same */ + imp = ne_ssl_cert_import(actual); + ONN("failed to import exported cert", imp == NULL); + ONN("imported cert was different to original", + ne_ssl_cert_cmp(imp, cert)); + + /* re-export the imported cert and check that looks the same */ + ne_free(actual); + actual = ne_ssl_cert_export(imp); + CALL(check_exported_data(actual, expected)); + ne_ssl_cert_free(imp); + + /* try importing from bogus data */ + imp = ne_ssl_cert_import("!!"); + ONN("imported bogus cert from bogus base64", imp != NULL); + imp = ne_ssl_cert_import("aaaa"); + ONN("imported bogus cert from valid base64", imp != NULL); + + ne_ssl_cert_free(cert); + ne_free(actual); + ne_free(expected); + return OK; +} + +/* Test write/read */ +static int read_write(void) +{ + ne_ssl_certificate *c1, *c2; + + c1 = ne_ssl_cert_read("server.cert"); + ONN("could not load server.cert", c1 == NULL); + + ONN("could not write output.pem", ne_ssl_cert_write(c1, "output.pem")); + + ONN("wrote to nonexistent directory", + ne_ssl_cert_write(c1, "nonesuch/output.pem") == 0); + + c2 = ne_ssl_cert_read("output.pem"); + ONN("could not read output.pem", c2 == NULL); + + ONN("read of output.pem differs from original", + ne_ssl_cert_cmp(c2, c1)); + + ne_ssl_cert_free(c1); + ne_ssl_cert_free(c2); + + return OK; +} + +/* A verification callback which caches the passed cert. */ +static int verify_cache(void *userdata, int fs, + const ne_ssl_certificate *cert) +{ + char **cache = userdata; + + if (*cache == NULL) { + *cache = ne_ssl_cert_export(cert); + return 0; + } else { + return -1; + } +} + +/* Test a common use of the SSL API; cache the server cert across + * sessions. */ +static int cache_cert(void) +{ + ne_session *sess = DEFSESS; + char *cache = NULL; + ne_ssl_certificate *cert; + + ONREQ(any_ssl_request(sess, serve_ssl, "ssigned.pem", CA_CERT, + verify_cache, &cache)); + ne_session_destroy(sess); + + ONN("no cert was cached", cache == NULL); + + /* make a real cert */ + cert = ne_ssl_cert_import(cache); + ONN("could not import cached cert", cert == NULL); + ne_free(cache); + + /* create a new session */ + sess = DEFSESS; + /* trust the cert */ + ne_ssl_trust_cert(sess, cert); + ne_ssl_cert_free(cert); + /* now, the request should succeed without manual verification */ + ONREQ(any_ssl_request(sess, serve_ssl, "ssigned.pem", CA_CERT, + NULL, NULL)); + ne_session_destroy(sess); + return OK; +} + +/* TODO: code paths still to test in cert verification: + * - server cert changes between connections: Mozilla gives + * a "bad MAC decode" error for this; can do better? + * - server presents no certificate (using ADH ciphers)... can + * only really happen if they mess with the SSL_CTX and enable + * ADH cipher manually; but good to check the failure case is + * safe. + * From the SSL book: + * - an early FIN should be returned as a possible truncation attack, + * NOT just an NE_SOCK_CLOSED. + * - unexpected close_notify is an error but not an attack. + * - never attempt session resumption after any aborted connection. + */ + +ne_test tests[] = { + T_LEAKY(init), + + T(load_server_certs), + T(trust_default_ca), + + T(cert_fingerprint), + T(cert_identities), + T(cert_validity), + T(cert_compare), + T(dname_compare), + T(dname_readable), + T(import_export), + T(read_write), + + T(load_client_cert), + + T(simple), + T(simple_eof), + T(empty_truncated_eof), + T(fail_truncated_eof), + T(fail_not_ssl), + T(cache_cert), + + T(client_cert_pkcs12), + T(ccert_unencrypted), + T(client_cert_provided), + T(cc_provided_dnames), + + T(parse_cert), + T(parse_chain), + + T(no_verify), + T(cache_verify), + T_LEAKY(wildcard_init), + T(wildcard_match), + T(caseless_match), + + T(subject_altname), + T(two_subject_altname), + T(two_subject_altname2), + T(notdns_altname), + + T(multi_commonName), + T(commonName_first), + + T(fail_wrongCN), + T(fail_expired), + T(fail_notvalid), + T(fail_untrusted_ca), + T(fail_self_signed), + T(fail_missing_CN), + + T(session_cache), + + T(fail_tunnel), + T(proxy_tunnel), + T(auth_proxy_tunnel), + + T(NULL) +}; diff --git a/test/string-tests.c b/test/string-tests.c new file mode 100644 index 0000000..93c8508 --- /dev/null +++ b/test/string-tests.c @@ -0,0 +1,492 @@ +/* + String handling tests + Copyright (C) 2001-2003, Joe Orton <joe@manyfish.co.uk> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "config.h" + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif +#ifdef HAVE_ERRNO_H +#include <errno.h> /* for the ENOENT definitions in str_errors */ +#endif + +#include "ne_string.h" + +#include "tests.h" + +#undef ONCMP +#define ONCMP(a,b) ONV(strcmp(a, b), \ + ("result was [%s] not [%s]", a, b)) + +static int simple(void) { + ne_buffer *s = ne_buffer_create(); + ON(s == NULL); + ne_buffer_zappend(s, "abcde"); + ONCMP(s->data, "abcde"); + ON(ne_buffer_size(s) != 5); + ne_buffer_destroy(s); + return OK; +} + +static int buf_concat(void) +{ + ne_buffer *s = ne_buffer_create(); + ON(s == NULL); + ne_buffer_concat(s, "a", "b", "c", "d", "e", "f", "g", NULL); + ONCMP(s->data, "abcdefg"); + ON(ne_buffer_size(s) != 7); + ne_buffer_destroy(s); + return OK; +} + +static int buf_concat2(void) +{ +#define RES "alphabetagammadeltaepsilonetatheta" + ne_buffer *s = ne_buffer_create(); + ON(s == NULL); + ne_buffer_concat(s, "alpha", "beta", "gamma", "delta", "epsilon", + "eta", "theta", NULL); + ONCMP(s->data, RES); + ON(ne_buffer_size(s) != strlen(RES)); + ne_buffer_destroy(s); + return OK; +} + +static int buf_concat3(void) +{ + ne_buffer *s = ne_buffer_create(); + ON(s == NULL); + ne_buffer_zappend(s, "foobar"); + ne_buffer_concat(s, "norman", NULL); + ONCMP(s->data, "foobarnorman"); + ON(ne_buffer_size(s) != 12); + ne_buffer_destroy(s); + return OK; +} + +static int append(void) { + ne_buffer *s = ne_buffer_create(); + ON(s == NULL); + ne_buffer_append(s, "a", 1); + ne_buffer_append(s, "b", 1); + ne_buffer_append(s, "c", 1); + ONCMP(s->data, "abc"); + ON(ne_buffer_size(s) != 3); + ne_buffer_destroy(s); + return OK; +} + +static int grow(void) +{ + ne_buffer *s = ne_buffer_ncreate(2); + ON(s == NULL); + ne_buffer_append(s, "a", 1); + ne_buffer_grow(s, 4); + ONCMP(s->data, "a"); + ne_buffer_destroy(s); + return OK; +} + +static int alter(void) { + ne_buffer *s = ne_buffer_create(); + char *d; + ON(s == NULL); + ne_buffer_zappend(s, "abcdefg"); + d = s->data; + ON(d == NULL); + d[2] = '\0'; + ne_buffer_altered(s); + ONCMP(s->data, "ab"); + ON(ne_buffer_size(s) != 2); + ne_buffer_zappend(s, "hijkl"); + ONCMP(s->data, "abhijkl"); + ne_buffer_destroy(s); + return OK; +} + +/* Macros for testing ne_token. */ + +#define TEST(res) do { \ + char *tok = ne_token(&pnt, ','); \ + ONN(res ": return", tok == NULL); \ + ONN(res ": compare", strcmp(tok, (res))); \ + ONN(res ": modify", pnt == NULL); \ +} while (0) + +#define LASTTEST(res) do { \ + char *tok = ne_token(&pnt, ','); \ + ONN(res ": last return", tok == NULL); \ + ONN(res ": last compare", strcmp(tok, (res))); \ + ONN(res ": last modify", pnt != NULL); \ +} while (0) + +#define QTEST(res) do { \ + char *tok = ne_qtoken(&pnt, ',', QUOTES); \ + ONN(res ": return", tok == NULL); \ + ONN(res ": compare", strcmp(tok, (res))); \ + ONN(res ": modify", pnt == NULL); \ +} while (0) + +#define QLASTTEST(res) do { \ + char *tok = ne_qtoken(&pnt, ',', QUOTES); \ + ONN(res ": last return", tok == NULL); \ + ONN(res ": last compare", strcmp(tok, (res))); \ + ONN(res ": last modify", pnt != NULL); \ +} while (0) + +static int token1(void) +{ + char *str = ne_strdup("a,b,c,d"), *pnt = str; + + TEST("a"); TEST("b"); TEST("c"); LASTTEST("d"); + + ne_free(str); + return OK; +} + +static int token2(void) +{ + char *str = ne_strdup("norman,fishing, elsewhere"), *pnt = str; + + TEST("norman"); TEST("fishing"); LASTTEST(" elsewhere"); + + ne_free(str); + return OK; +} + +static int nulls(void) +{ + char *str = ne_strdup("alpha,,gamma"), *pnt = str; + + TEST("alpha"); TEST(""); LASTTEST("gamma"); + ne_free(str); + + pnt = str = ne_strdup(",,,wooo"); + TEST(""); TEST(""); TEST(""); LASTTEST("wooo"); + ne_free(str); + + pnt = str = ne_strdup("wooo,,,"); + TEST("wooo"); TEST(""); TEST(""); LASTTEST(""); + ne_free(str); + + return OK; +} + +static int empty(void) +{ + char *str = ne_strdup(""), *pnt = str; + + LASTTEST(""); + ne_free(str); + + return OK; +} + +#undef QUOTES +#define QUOTES "'" + +static int quoted(void) +{ + char *str = + ne_strdup("alpha,'beta, a fish called HELLO!?',sandwiches"); + char *pnt = str; + + QTEST("alpha"); + QTEST("'beta, a fish called HELLO!?'"); + QLASTTEST("sandwiches"); + + ne_free(str); + return OK; +} + +static int badquotes(void) +{ + char *str = ne_strdup("alpha,'blah"), *pnt = str; + + QTEST("alpha"); + ON(ne_qtoken(&pnt, ',', QUOTES) != NULL); + + ne_free(str); + return OK; +} + +/* for testing ne_shave. */ +#undef TEST +#define TEST(str, ws, res) do { \ + char *s = ne_strdup((str)); \ + char *r = ne_shave(s, (ws)); \ + ONN("[" str "]", strcmp(r, (res))); \ + ne_free(s); \ +} while (0) + +static int shave(void) +{ + TEST(" b ", " ", "b"); + TEST("b", " ", "b"); + TEST(" b ", " ", "b"); + TEST("--bbb-----", "-", "bbb"); + TEST("hello, world ", " ", "hello, world"); + TEST("<<><<<><<this is foo<<><<<<><<", "<>", "this is foo"); + TEST("09809812342347I once saw an helicopter0012312312398", "0123456789", + "I once saw an helicopter"); + return OK; +} + +/* Regression test for ne_shave call which should produce an empty + * string. */ +static int shave_regress(void) +{ + TEST("\"\"", "\"", ""); + return OK; +} + +/* Test the ne_token/ne_shave combination. */ + +#undef TEST +#undef LASTTEST + +#define TEST(res) do { \ + char *tok = ne_token(&pnt, ','); \ + ONN(res ": return", tok == NULL); \ + tok = ne_shave(tok, " "); \ + ONN(res ": shave", tok == NULL); \ + ONN(res ": compare", strcmp(tok, (res))); \ + ONN(res ": modify", pnt == NULL); \ +} while (0) + + +#define LASTTEST(res) do { \ + char *tok = ne_token(&pnt, ','); \ + ONN(res ": last return", tok == NULL); \ + tok = ne_shave(tok, " "); \ + ONN(res ": last shave", tok == NULL); \ + ONN(res ": last compare", strcmp(tok, (res))); \ + ONN(res ": last modify", pnt != NULL); \ +} while (0) + +/* traditional use of ne_token/ne_shave. */ +static int combo(void) +{ + char *str = ne_strdup(" fred , mary, jim , alice, david"), *pnt = str; + + TEST("fred"); TEST("mary"); TEST("jim"); TEST("alice"); + LASTTEST("david"); + + ne_free(str); + return 0; +} + +static int concat(void) +{ +#define CAT(res, args) do { char *str = ne_concat args; \ +ONCMP(str, res); \ +ne_free(str); } while (0) + CAT("alphabeta", ("alpha", "beta", NULL)); + CAT("alpha", ("alpha", "", "", NULL)); + CAT("", ("", NULL)); + CAT("", ("", "", "", NULL)); + CAT("alpha", ("", "a", "lph", "", "a", NULL)); + return OK; +} + +static int str_errors(void) +{ + char expect[200], actual[200]; + + strncpy(expect, strerror(ENOENT), sizeof(expect)); + ONN("ne_strerror did not return passed-in buffer", + ne_strerror(ENOENT, actual, sizeof(actual)) != actual); + + ONV(strcmp(expect, actual), + ("error from ENOENT was `%s' not `%s'", actual, expect)); + + /* Test truncated error string is still NUL-terminated. */ + ne_strerror(ENOENT, actual, 6); + ONN("truncated string had wrong length", strlen(actual) != 5); + + return OK; +} + +static int strnzcpy(void) +{ + char buf[5]; + + ne_strnzcpy(buf, "abcdefghi", sizeof buf); + ONV(strcmp(buf, "abcd"), ("result was `%s' not `abcd'", buf)); + + ne_strnzcpy(buf, "ab", sizeof buf); + ONV(strcmp(buf, "ab"), ("result was `%s' not `ab'", buf)); + + return OK; +} + +#define FOX_STRING "The quick brown fox jumped over the lazy dog" +#define PUNC_STRING "<>,.;'#:@~[]{}!\"$%^&*()_+-=" + +static int cleaner(void) +{ + static const char *strings[] = { + "alpha", "alpha", + "pretty\033[41mcolours", "pretty [41mcolours", + "beta\n", "beta ", + "del\rt\na", "del t a", + FOX_STRING, FOX_STRING, + "0123456789", "0123456789", + PUNC_STRING, PUNC_STRING, + "\01blah blee\05bloo", " blah blee bloo", + NULL, + }; + unsigned int n; + + for (n = 0; strings[n]; n+=2) { + char *act = ne_strclean(ne_strdup(strings[n])); + + ONV(strcmp(act, strings[n+1]), + ("cleansed to `%s' not `%s'", act, strings[n+1])); + + ne_free(act); + } + + return OK; +} + +/* Check that raw data 'raw', of length 'len', has base64 encoding + * of 'expected'. */ +static int b64_check(const unsigned char *raw, size_t len, + const char *expected) +{ + char *encoded = ne_base64(raw, len); + unsigned char *decoded; + size_t dlen; + + ONV(strcmp(encoded, expected), + ("base64(\"%s\") gave \"%s\" not \"%s\"", raw, encoded, expected)); + + dlen = ne_unbase64(encoded, &decoded); + ONV(dlen != len, + ("decoded `%s' length was %" NE_FMT_SIZE_T " not %" NE_FMT_SIZE_T, + expected, dlen, len)); + + ONV(memcmp(raw, decoded, dlen), + ("decoded `%s' as `%.*s' not `%.*s'", + expected, dlen, decoded, dlen, raw)); + + ne_free(decoded); + ne_free(encoded); + return OK; +} + +/* ALLBITS: base64 encoding of "\0..\377" */ +#define ALLBITS \ +"AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKiss" \ +"LS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZ" \ +"WltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWG" \ +"h4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKz" \ +"tLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g" \ +"4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w==" + +static int base64(void) +{ + unsigned char bits[256]; + size_t n; + +#define B64B(x, l, y) CALL(b64_check(x, l, y)) +#define B64(x, y) B64B(x, strlen(x), y) + + /* invent these with + * $ printf "string" | uuencode -m blah + */ + B64("a", "YQ=="); + B64("bb", "YmI="); + B64("ccc", "Y2Nj"); + B64("Hello, world", "SGVsbG8sIHdvcmxk"); + B64("Aladdin:open sesame", "QWxhZGRpbjpvcGVuIHNlc2FtZQ=="); + B64("I once saw a dog called norman.\n", + "SSBvbmNlIHNhdyBhIGRvZyBjYWxsZWQgbm9ybWFuLgo="); + B64("The quick brown fox jumped over the lazy dog", + "VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wZWQgb3ZlciB0aGUgbGF6eSBkb2c="); + + /* binary data.. + * $ printf "string" | wc -c # get the length + * $ printf "string" | uuencode -m blah # get the base64 + */ + B64B("\0\0\0\0\0\n", 6, "AAAAAAAK"); + B64B("I once wished \0 upon a \0 fish.", 30, + "SSBvbmNlIHdpc2hlZCAAIHVwb24gYSAAIGZpc2gu"); + B64B("\201\202\203\204", 4, "gYKDhA=="); + + for (n = 0; n < sizeof bits; n++) + bits[n] = (unsigned char)n; + CALL(b64_check(bits, sizeof bits, ALLBITS)); + +#undef B64 +#undef B64B + return OK; +} + +static int unbase64(void) +{ + static const char *ts[] = { + "", "a", "ab", "abc", + "}bcd", "a}cd", "ab}d", "abc}", " ", + "^bcd", "a^cd", "ab^d", "abc^", + "====", "=bcd", "a=cd", "ab=d", "a==d", "a=c=", + NULL + }; + size_t n; + + for (n = 0; ts[n]; n++) { + unsigned char *tmp; + ONV(ne_unbase64(ts[n], &tmp) != 0, + ("invalid string `%s' was decoded", ts[n])); + } + + return OK; +} + +ne_test tests[] = { + T(simple), + T(buf_concat), + T(buf_concat2), + T(buf_concat3), + T(append), + T(grow), + T(alter), + T(token1), + T(token2), + T(nulls), + T(empty), + T(quoted), + T(badquotes), + T(shave), + T(shave_regress), + T(combo), + T(concat), + T(str_errors), + T(strnzcpy), + T(cleaner), + T(base64), + T(unbase64), + T(NULL) +}; + diff --git a/test/stubs.c b/test/stubs.c new file mode 100644 index 0000000..494f8f0 --- /dev/null +++ b/test/stubs.c @@ -0,0 +1,171 @@ +/* + neon test suite + Copyright (C) 2002-2003, Joe Orton <joe@manyfish.co.uk> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** These tests show that the stub functions produce appropriate + * results to provide ABI-compatibility when a particular feature is + * not supported by the library. + **/ + +#include "config.h" + +#include <sys/types.h> + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "ne_request.h" +#include "ne_socket.h" +#include "ne_compress.h" + +#include "tests.h" +#include "child.h" +#include "utils.h" + +#if defined(NEON_ZLIB) && defined(NEON_SSL) +#define NO_TESTS 1 +#endif + +#ifndef NEON_ZLIB +static int sd_result = OK; + +static void sd_reader(void *ud, const char *block, size_t len) +{ + const char *expect = ud; + if (strncmp(expect, block, len) != 0) { + sd_result = FAIL; + t_context("decompress reader got bad data"); + } +} + +static int stub_decompress(void) +{ + ne_session *sess; + ne_decompress *dc; + ne_request *req; + int ret; + + CALL(make_session(&sess, single_serve_string, + "HTTP/1.1 200 OK" EOL + "Connection: close" EOL EOL + "abcde")); + + req = ne_request_create(sess, "GET", "/foo"); + + dc = ne_decompress_reader(req, ne_accept_2xx, sd_reader, "abcde"); + + ret = ne_request_dispatch(req); + + CALL(await_server()); + + ONREQ(ret); + + ONN("decompress_destroy failed", ne_decompress_destroy(dc)); + + ne_request_destroy(req); + ne_session_destroy(sess); + + /* This is a skeleton test suite file. */ + return sd_result; +} +#endif + +#ifndef NEON_SSL +static int stub_ssl(void) +{ + ne_session *sess = ne_session_create("https", "localhost", 7777); + ne_ssl_certificate *cert; + ne_ssl_client_cert *cc; + + /* these should all fail when SSL is not supported. */ + cert = ne_ssl_cert_read("Makefile"); + if (cert) { + char *dn, digest[60], date[NE_SSL_VDATELEN]; + const ne_ssl_certificate *issuer; + + /* This branch should never be executed, but lets pretend it + * will to prevent the compiler optimising this code away if + * it's placed after the cert != NULL test. And all that + * needs to be tested is that these functions link OK. */ + dn = ne_ssl_readable_dname(ne_ssl_cert_subject(cert)); + ONN("this code shouldn't run", dn != NULL); + dn = ne_ssl_readable_dname(ne_ssl_cert_issuer(cert)); + ONN("this code shouldn't run", dn != NULL); + issuer = ne_ssl_cert_signedby(cert); + ONN("this code shouldn't run", issuer != NULL); + ONN("this code shouldn't run", ne_ssl_cert_digest(cert, digest)); + ne_ssl_cert_validity(cert, date, date); + ONN("this code shouldn't run", + ne_ssl_dname_cmp(ne_ssl_cert_subject(cert), + ne_ssl_cert_issuer(cert))); + ONN("this code shouldn't run", ne_ssl_cert_identity(issuer) != NULL); + ONN("this code shouldn't run", ne_ssl_cert_export(cert) != NULL); + } + + ONN("this code shouldn't run", ne_ssl_cert_import("foo") != NULL); + ONN("this code shouldn't run", ne_ssl_cert_read("Makefile") != NULL); + ONN("this code shouldn't succeed", ne_ssl_cert_cmp(NULL, NULL) == 0); + + ONN("certificate load succeeded", cert != NULL); + ne_ssl_cert_free(cert); + + cc = ne_ssl_clicert_read("Makefile"); + if (cc) { + const char *name; + /* dead branch as above. */ + cert = (void *)ne_ssl_clicert_owner(cc); + ONN("this code shouldn't run", cert != NULL); + name = ne_ssl_clicert_name(cc); + ONN("this code shouldn't run", name != NULL); + ONN("this code shouldn't run", ne_ssl_clicert_decrypt(cc, "fubar")); + ne_ssl_set_clicert(sess, cc); + } + + ONN("client certificate load succeeded", cc != NULL); + ne_ssl_clicert_free(cc); + + ne_ssl_trust_default_ca(sess); + + ne_session_destroy(sess); + return OK; +} +#endif + +#ifdef NO_TESTS +static int null_test(void) { return OK; } +#endif + +ne_test tests[] = { +#ifndef NEON_ZLIB + T(stub_decompress), +#endif +#ifndef NEON_SSL + T(stub_ssl), +#endif +/* to prevent failure when SSL and zlib are supported. */ +#ifdef NO_TESTS + T(null_test), +#endif + T(NULL) +}; + diff --git a/test/uri-tests.c b/test/uri-tests.c new file mode 100644 index 0000000..ce35f22 --- /dev/null +++ b/test/uri-tests.c @@ -0,0 +1,377 @@ +/* + URI tests + Copyright (C) 2001-2003, Joe Orton <joe@manyfish.co.uk> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "config.h" + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif + +#include "ne_uri.h" +#include "ne_alloc.h" + +#include "tests.h" + +static int simple(void) +{ + ne_uri p = {0}; + ON(ne_uri_parse("http://www.webdav.org/foo", &p)); + ON(strcmp(p.host, "www.webdav.org")); + ON(strcmp(p.path, "/foo")); + ON(strcmp(p.scheme, "http")); + ON(p.port); + ON(p.authinfo != NULL); + ne_uri_free(&p); + return 0; +} + +static int simple_ssl(void) +{ + ne_uri p = {0}; + ON(ne_uri_parse("https://webdav.org/", &p)); + ON(strcmp(p.scheme, "https")); + ON(p.port); + ne_uri_free(&p); + return OK; +} + +static int no_path(void) +{ + ne_uri p = {0}; + ON(ne_uri_parse("https://webdav.org", &p)); + ON(strcmp(p.path, "/")); + ne_uri_free(&p); + return OK; +} + +#define STR "/a¹²³¼½/" +static int escapes(void) +{ + char *un, *esc; + esc = ne_path_escape(STR); + ON(esc == NULL); + un = ne_path_unescape(esc); + ON(un == NULL); + ON(strcmp(un, STR)); + ne_free(un); + ne_free(esc); + ONN("unescape accepted invalid URI", + ne_path_unescape("/foo%zzbar") != NULL); + /* no-escape path */ + esc = ne_path_escape("/foobar"); + ON(strcmp(esc, "/foobar")); + ne_free(esc); + return OK; +} + +static int parents(void) +{ + static const struct { + const char *path, *parent; + } ps[] = { + { "/a/b/c", "/a/b/" }, + { "/a/b/c/", "/a/b/" }, + { "/alpha/beta", "/alpha/" }, + { "/foo", "/" }, + { "norman", NULL }, + { "/", NULL }, + { "", NULL }, + { NULL, NULL } + }; + int n; + + for (n = 0; ps[n].path != NULL; n++) { + char *p = ne_path_parent(ps[n].path); + if (ps[n].parent == NULL) { + ONV(p != NULL, ("parent of `%s' was `%s' not NULL", + ps[n].path, p)); + } else { + ONV(p == NULL, ("parent of `%s' was NULL", ps[n].path)); + ONV(strcmp(p, ps[n].parent), + ("parent of `%s' was `%s' not `%s'", + ps[n].path, p, ps[n].parent)); + ne_free(p); + } + } + + return OK; +} + +static int compares(void) +{ + const char *alpha = "/alpha"; + + ON(ne_path_compare("/a", "/a/") != 0); + ON(ne_path_compare("/a/", "/a") != 0); + ON(ne_path_compare("/ab", "/a/") == 0); + ON(ne_path_compare("/a/", "/ab") == 0); + ON(ne_path_compare("/a/", "/a/") != 0); + ON(ne_path_compare("/alpha/", "/beta/") == 0); + ON(ne_path_compare("/alpha", "/b") == 0); + ON(ne_path_compare("/alpha/", "/alphash") == 0); + ON(ne_path_compare("/fish/", "/food") == 0); + ON(ne_path_compare(alpha, alpha) != 0); + ON(ne_path_compare("/a/b/c/d", "/a/b/c/") == 0); + return OK; +} + +/* Checks that a URI comparison of 'u1' and 'u2', which have differing + * 'field', doesn't compare to equal; and that they are ordered + * correctly. */ +static int cmp_differ(const char *field, + const ne_uri *u1, const ne_uri *u2) +{ + ONV(ne_uri_cmp(u1, u2) == 0, + ("URIs with different %s were equal", field)); + + ONV(ne_uri_cmp(u2, u1) == 0, + ("URIs with different %s were equal (reversed)", field)); + + /* relies on strcmp return value being of equal magnitude when + * arguments are reversed; not sure if this is portable + * assumption. */ + ONV(ne_uri_cmp(u1, u2) + ne_uri_cmp(u2, u1) != 0, + ("relative ordering of URIs with different %s incorrect", field)); + + return OK; +} + +static int cmp(void) +{ + ne_uri alpha, beta; + + alpha.path = "/alpha"; + alpha.scheme = "http"; + alpha.host = "example.com"; + alpha.port = 80; + + beta = alpha; /* structure copy. */ + + ONN("equal URIs not equal", ne_uri_cmp(&alpha, &beta) != 0); + + beta.path = "/beta"; + CALL(cmp_differ("path", &alpha, &beta)); + + beta = alpha; beta.scheme = "https"; + CALL(cmp_differ("scheme", &alpha, &beta)); + + beta = alpha; beta.port = 433; + CALL(cmp_differ("port", &alpha, &beta)); + + beta = alpha; beta.host = "fish.com"; + CALL(cmp_differ("host", &alpha, &beta)); + + beta = alpha; beta.host = "EXAMPLE.CoM"; + ONN("hostname comparison not case-insensitive", + ne_uri_cmp(&alpha, &beta) != 0); + + beta = alpha; beta.scheme = "HtTp"; + ONN("scheme comparison not case-insensitive", + ne_uri_cmp(&alpha, &beta) != 0); + + beta = alpha; beta.path = ""; alpha.path = "/"; + ONN("empty abspath doesn't match '/'", + ne_uri_cmp(&alpha, &beta) != 0); + ONN("'/' doesn't match empty abspath", + ne_uri_cmp(&beta, &alpha) != 0); + + beta = alpha; alpha.path = ""; beta.path = "/foo"; + ONN("empty abspath matched '/foo'", ne_uri_cmp(&alpha, &beta) == 0); + ONN("'/foo' matched empty abspath ", ne_uri_cmp(&beta, &alpha) == 0); + + return OK; +} + +static int children(void) +{ + ON(ne_path_childof("/a", "/a/b") == 0); + ON(ne_path_childof("/a/", "/a/b") == 0); + ON(ne_path_childof("/aa/b/c", "/a/b/c/d/e") != 0); + ON(ne_path_childof("////", "/a") != 0); + return OK; +} + +static int slash(void) +{ + ON(ne_path_has_trailing_slash("/a/") == 0); + ON(ne_path_has_trailing_slash("/a") != 0); + { + /* check the uri == "" case. */ + char *foo = "/"; + ON(ne_path_has_trailing_slash(&foo[1])); + } + return OK; +} + +static int just_hostname(void) +{ + ne_uri p = {0}; + ON(ne_uri_parse("host.name.com", &p)); + ON(strcmp(p.host, "host.name.com")); + ne_uri_free(&p); + return 0; +} + +static int just_path(void) +{ + ne_uri p = {0}; + ON(ne_uri_parse("/argh", &p)); + ON(strcmp(p.path, "/argh")); + ne_uri_free(&p); + return 0; +} + +static int default_port(void) +{ + ONN("default http: port incorrect", ne_uri_defaultport("http") != 80); + ONN("default https: port incorrect", ne_uri_defaultport("https") != 443); + ONN("unspecified scheme: port incorrect", ne_uri_defaultport("ldap") != 0); + return OK; +} + +static int parse(void) +{ + static const struct test_uri { + const char *uri, *scheme, *host; + unsigned int port; + const char *path, *authinfo; + } uritests[] = { + { "http://webdav.org/norman", "http", "webdav.org", 0, "/norman", NULL }, + { "http://webdav.org:/norman", "http", "webdav.org", 0, "/norman", NULL }, + { "https://webdav.org/foo", "https", "webdav.org", 0, "/foo", NULL }, + { "http://webdav.org:8080/bar", "http", "webdav.org", 8080, "/bar", NULL }, + { "http://a/b", "http", "a", 0, "/b", NULL }, + { "http://webdav.org/bar:fish", "http", "webdav.org", 0, "/bar:fish", NULL }, + { "http://webdav.org", "http", "webdav.org", 0, "/", NULL }, + { "http://webdav.org/fish@food", "http", "webdav.org", 0, "/fish@food", NULL }, + /* authinfo */ + { "ftp://jim:bob@jim.com", "ftp", "jim.com", 0, "/", "jim:bob" }, + { "ldap://fred:bloggs@fish.com/foobar", "ldap", "fish.com", 0, + "/foobar", "fred:bloggs" }, + /* relativeURIs accepted for dubious legacy reasons. */ + { "a/b", NULL, "a", 0, "/b", NULL }, + { "a:8080/b", NULL, "a", 8080, "/b", NULL }, + { "/fish", NULL, NULL, 0, "/fish", NULL }, + { "webdav.org:8080", NULL, "webdav.org", 8080, "/", NULL }, + /* IPv6 hex strings allowed even if IPv6 not supported. */ + { "http://[::1]/foo", "http", "[::1]", 0, "/foo", NULL }, + { "http://[a:a:a:a::0]/foo", "http", "[a:a:a:a::0]", 0, "/foo", NULL }, + { "http://[::1]:8080/bar", "http", "[::1]", 8080, "/bar", NULL }, + { "ftp://[feed::cafe]:555", "ftp", "[feed::cafe]", 555, "/", NULL }, + { "http://fish/[foo]/bar", "http", "fish", 0, "/[foo]/bar", NULL }, + /* and some dubious ones: */ + { "[::1]/foo", NULL, "[::1]", 0, "/foo", NULL }, + { "[::1]:8000/foo", NULL, "[::1]", 8000, "/foo", NULL }, + { NULL } + }; + int n; + + for (n = 0; uritests[n].uri != NULL; n++) { + ne_uri res; + const struct test_uri *exp = &uritests[n]; + ONV(ne_uri_parse(exp->uri, &res) != 0, + ("%s: parse failed", exp->uri)); + ONV(res.port != exp->port, + ("%s: parsed port was %d not %d", exp->uri, res.port, exp->port)); + ONCMP(exp->scheme, res.scheme, exp->uri, "scheme"); + ONCMP(exp->host, res.host, exp->uri, "host"); + ONV(strcmp(res.path, exp->path), + ("%s: parsed path was %s not %s", exp->uri, res.path, exp->path)); + ONCMP(exp->authinfo, res.authinfo, exp->uri, "authinfo"); + ne_uri_free(&res); + } + + return OK; +} + +static int failparse(void) +{ + static const char *uris[] = { + "", + "http://[::1/", + NULL + }; + int n; + + for (n = 0; uris[n] != NULL; n++) { + ne_uri p; + ONV(ne_uri_parse(uris[n], &p) == 0, + ("`%s' did not fail to parse", uris[n])); + ne_uri_free(&p); + } + + return 0; +} + +static int unparse(void) +{ + const char *uris[] = { + "http://foo.com/bar", + "https://bar.com/foo/wishbone", + "http://www.random.com:8000/", + "http://[::1]:8080/", + "ftp://ftp.foo.bar/abc/def", + NULL + }; + int n; + + for (n = 0; uris[n] != NULL; n++) { + ne_uri parsed; + char *unp; + + ONV(ne_uri_parse(uris[n], &parsed), + ("failed to parse %s", uris[n])); + + if (parsed.port == 0) + parsed.port = ne_uri_defaultport(parsed.scheme); + + unp = ne_uri_unparse(&parsed); + + ONV(strcmp(unp, uris[n]), + ("unparse got %s from %s", unp, uris[n])); + + ne_uri_free(&parsed); + ne_free(unp); + } + + return OK; +} + +ne_test tests[] = { + T(simple), + T(simple_ssl), + T(no_path), + T(escapes), + T(parents), + T(compares), + T(cmp), + T(children), + T(slash), + T(just_hostname), + T(just_path), + T(default_port), + T(parse), + T(failparse), + T(unparse), + T(NULL) +}; diff --git a/test/util-tests.c b/test/util-tests.c new file mode 100644 index 0000000..3a774bc --- /dev/null +++ b/test/util-tests.c @@ -0,0 +1,256 @@ +/* + utils tests + Copyright (C) 2001-2003, Joe Orton <joe@manyfish.co.uk> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "config.h" + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif + +#include "ne_utils.h" +#include "ne_md5.h" +#include "ne_alloc.h" +#include "ne_dates.h" +#include "ne_string.h" + +#include "tests.h" + +static const struct { + const char *status; + int major, minor, code; + const char *rp; +} accept_sl[] = { + /* These are really valid. */ + { "HTTP/1.1 200 OK", 1, 1, 200, "OK" }, + { "HTTP/1.1000 200 OK", 1, 1000, 200, "OK" }, + { "HTTP/1000.1000 200 OK", 1000, 1000, 200, "OK" }, + { "HTTP/00001.1 200 OK", 1, 1, 200, "OK" }, + { "HTTP/1.00001 200 OK", 1, 1, 200, "OK" }, + { "HTTP/99.99 999 99999", 99, 99, 999, "99999" }, + { "HTTP/1.1 100 ", 1, 1, 100, "" }, + + /* these aren't really valid but we should be able to parse them. */ + { "HTTP/1.1 100", 1, 1, 100, "" }, + { "HTTP/1.1 200 OK", 1, 1, 200, "OK" }, + { "HTTP/1.1 200 \t OK", 1, 1, 200, "OK" }, + { " HTTP/1.1 200 OK", 1, 1, 200, "OK" }, + { "Norman is a dog HTTP/1.1 200 OK", 1, 1, 200, "OK" }, + { NULL } +}; + +static const char *bad_sl[] = { + "", + "HTTP/1.1 1000 OK", + "HTTP/1.1 1000", + "HTTP/-1.1 100 OK", + "HTTP/1.1 -100 OK", + "HTTP/ 200 OK", + "HTTP/", + "HTTP/1.1A 100 OK", + "HTTP/1.", + "HTTP/1.1 1", + "Fish/1.1 100 OK", + "HTTP/1.1 10", + "HTTP", + "H\0TP/1.1 100 OK", + NULL +}; + +static int status_lines(void) +{ + ne_status s; + int n; + + for (n = 0; accept_sl[n].status != NULL; n++) { + ONV(ne_parse_statusline(accept_sl[n].status, &s), + ("valid #%d: parse", n)); + ONV(accept_sl[n].major != s.major_version, ("valid #%d: major", n)); + ONV(accept_sl[n].minor != s.minor_version, ("valid #%d: minor", n)); + ONV(accept_sl[n].code != s.code, ("valid #%d: code", n)); + ONV(strcmp(accept_sl[n].rp, s.reason_phrase), + ("valid #%d: reason phrase", n)); + ne_free(s.reason_phrase); + } + + for (n = 0; bad_sl[n] != NULL; n++) { + ONV(ne_parse_statusline(bad_sl[n], &s) == 0, + ("invalid #%d", n)); + } + + return OK; +} + +/* Write MD5 of 'len' bytes of 'str' to 'digest' */ +static unsigned char *digest_md5(const char *data, size_t len, unsigned char digest[16]) +{ + struct ne_md5_ctx ctx; + +#define CHUNK 100 + ne_md5_init_ctx(&ctx); + /* exercise the buffering interface */ + while (len > CHUNK) { + ne_md5_process_bytes(data, CHUNK, &ctx); + len -= CHUNK; + data += CHUNK; + } + ne_md5_process_bytes(data, len, &ctx); + ne_md5_finish_ctx(&ctx, digest); + + return digest; +} + +static int md5(void) +{ + unsigned char buf[17] = {0}, buf2[17] = {0}; + char ascii[33] = {0}; + char zzzs[500]; + + ne_md5_to_ascii(digest_md5("", 0, buf), ascii); + ONN("MD5(null)", strcmp(ascii, "d41d8cd98f00b204e9800998ecf8427e")); + + ne_md5_to_ascii(digest_md5("foobar", 7, buf), ascii); + ONN("MD5(foobar)", strcmp(ascii, "b4258860eea29e875e2ee4019763b2bb")); + + /* $ perl -e 'printf "z"x500' | md5sum + * 8b9323bd72250ea7f1b2b3fb5046391a - */ + memset(zzzs, 'z', sizeof zzzs); + ne_md5_to_ascii(digest_md5(zzzs, sizeof zzzs, buf), ascii); + ONN("MD5(\"z\"x512)", strcmp(ascii, "8b9323bd72250ea7f1b2b3fb5046391a")); + + ne_ascii_to_md5(ascii, buf2); + ON(memcmp(buf, buf2, 16)); + + return OK; +} + +static int md5_alignment(void) +{ + char *bb = ne_malloc(66); + struct ne_md5_ctx ctx; + + /* regression test for a bug in md5.c in <0.15.0 on SPARC, where + * the process_bytes function would SIGBUS if the buffer argument + * isn't 32-bit aligned. Won't trigger on x86 though. */ + ne_md5_init_ctx(&ctx); + ne_md5_process_bytes(bb + 1, 65, &ctx); + ne_free(bb); + + return OK; +} + +static const struct { + const char *str; + time_t time; + enum { d_rfc1123, d_iso8601, d_rfc1036 } type; +} good_dates[] = { + { "Fri, 08 Jun 2001 22:59:46 GMT", 992041186, d_rfc1123 }, + { "Friday, 08-Jun-01 22:59:46 GMT", 992041186, d_rfc1036 }, + /* some different types of ISO8601 dates. */ + { "2001-06-08T22:59:46Z", 992041186, d_iso8601 }, + { "2001-06-08T22:59:46.9Z", 992041186, d_iso8601 }, + { "2001-06-08T26:00:46+03:01", 992041186, d_iso8601 }, + { "2001-06-08T20:58:46-02:01", 992041186, d_iso8601 }, + { NULL } +}; + +static int parse_dates(void) +{ + int n; + + for (n = 0; good_dates[n].str != NULL; n++) { + time_t res; + const char *str = good_dates[n].str; + + switch (good_dates[n].type) { + case d_rfc1036: res = ne_rfc1036_parse(str); break; + case d_iso8601: res = ne_iso8601_parse(str); break; + case d_rfc1123: res = ne_rfc1123_parse(str); break; + default: res = -1; break; + } + + ONV(res == -1, ("date %d parse", n)); + +#define FT "%" NE_FMT_TIME_T + ONV(res != good_dates[n].time, ( + "date %d incorrect (" FT " not " FT ")", n, + res, good_dates[n].time)); + } + + return OK; +} + +static int versioning(void) +{ +#define GOOD(n,m,msg) ONV(ne_version_match(n,m), \ +("match of " msg " failed (%d.%d)", n, m)) +#define BAD(n,m,msg) ONV(ne_version_match(n,m) == 0, \ +("match of " msg " succeeded (%d.%d)", n, m)) + GOOD(NEON_VERSION_MAJOR, NEON_VERSION_MINOR, "current version"); + BAD(NEON_VERSION_MAJOR, NEON_VERSION_MINOR + 1, "later minor"); + BAD(NEON_VERSION_MAJOR + 1, 0, "later major"); +#if NEON_VERSION_MINOR > 0 + GOOD(NEON_VERSION_MAJOR, NEON_VERSION_MINOR - 1, "earlier minor"); +#endif +#if NEON_VERSION_MAJOR > 0 + BAD(NEON_VERSION_MAJOR - 1, 0, "earlier major"); +#endif +#undef GOOD +#undef BAD + return OK; +} + +/* basic ne_version_string() sanity tests */ +static int version_string(void) +{ + char buf[1024]; + + ne_snprintf(buf, sizeof buf, "%s", ne_version_string()); + + NE_DEBUG(NE_DBG_HTTP, "Version string: %s\n", buf); + + ONN("version string too long", strlen(buf) > 200); + ONN("version string contained newline", strchr(buf, '\n') != NULL); + + return OK; +} + +static int support(void) +{ +#ifdef NEON_SSL + ONN("SSL support not advertised", !ne_supports_ssl()); +#else + ONN("SSL support advertised", ne_supports_ssl()); +#endif + return OK; +} + +ne_test tests[] = { + T(status_lines), + T(md5), + T(md5_alignment), + T(parse_dates), + T(versioning), + T(version_string), + T(support), + T(NULL) +}; diff --git a/test/utils.c b/test/utils.c new file mode 100644 index 0000000..47e1410 --- /dev/null +++ b/test/utils.c @@ -0,0 +1,107 @@ +/* + Utility functions for HTTP client tests + Copyright (C) 2001-2003, Joe Orton <joe@manyfish.co.uk> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "config.h" + +#ifdef HAVE_UNISTD_H +#include <unistd.h> /* for sleep() */ +#endif +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif + +#include "ne_session.h" + +#include "child.h" +#include "tests.h" +#include "utils.h" + +int make_session(ne_session **sess, server_fn fn, void *ud) +{ + *sess = ne_session_create("http", "localhost", 7777); + return spawn_server(7777, fn, ud); +} + +static int serve_response(ne_socket *s, const char *response) +{ + CALL(discard_request(s)); + CALL(discard_body(s)); + ONN("failed to send response", SEND_STRING(s, response)); + return OK; +} + +int single_serve_string(ne_socket *s, void *userdata) +{ + const char *str = userdata; + return serve_response(s, str); +} + +int sleepy_server(ne_socket *sock, void *userdata) +{ + sleep(10); + return 0; +} + +int many_serve_string(ne_socket *s, void *userdata) +{ + int n; + struct many_serve_args *args = userdata; + + for (n = 0; n < args->count; n++) { + NE_DEBUG(NE_DBG_HTTP, "Serving response %d\n", n); + CALL(serve_response(s, args->str)); + } + + return OK; +} + +int any_request(ne_session *sess, const char *uri) +{ + ne_request *req = ne_request_create(sess, "GET", uri); + int ret = ne_request_dispatch(req); + ne_request_destroy(req); + return ret; +} + +int any_2xx_request(ne_session *sess, const char *uri) +{ + ne_request *req = ne_request_create(sess, "GET", uri); + int ret = ne_request_dispatch(req); + ONV(ret != NE_OK || ne_get_status(req)->klass != 2, + ("request failed: %s\n", ne_get_error(sess))); + ne_request_destroy(req); + return ret; +} + +int any_2xx_request_body(ne_session *sess, const char *uri) +{ + ne_request *req = ne_request_create(sess, "GET", uri); +#define BSIZE 5000 + char *body = memset(ne_malloc(BSIZE), 'A', BSIZE); + int ret; + ne_set_request_body_buffer(req, body, BSIZE); + ret = ne_request_dispatch(req); + ne_free(body); + ONV(ret != NE_OK || ne_get_status(req)->klass != 2, + ("request failed: %s\n", ne_get_error(sess))); + ne_request_destroy(req); + return ret; +} + diff --git a/test/utils.h b/test/utils.h new file mode 100644 index 0000000..55c61ab --- /dev/null +++ b/test/utils.h @@ -0,0 +1,57 @@ +/* + neon-specific test utils + Copyright (C) 2001-2003, Joe Orton <joe@manyfish.co.uk> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef UTILS_H +#define UTILS_H 1 + +#include "ne_request.h" + +#define ONREQ(x) do { int _ret = (x); if (_ret) { t_context("line %d: HTTP error:\n%s", __LINE__, ne_get_error(sess)); return FAIL; } } while (0); + +int single_serve_string(ne_socket *s, void *userdata); + +struct many_serve_args { + int count; + const char *str; +}; + +/* Serves args->str response args->count times down a single + * connection. */ +int many_serve_string(ne_socket *s, void *userdata); + +/* Run a request using URI on the session. */ +int any_request(ne_session *sess, const char *uri); + +/* Run a request using URI on the session; fail on a non-2xx response. + */ +int any_2xx_request(ne_session *sess, const char *uri); + +/* As above but with a request body. */ +int any_2xx_request_body(ne_session *sess, const char *uri); + +/* makes *session, spawns server which will run 'fn(userdata, + * socket)'. sets error context if returns non-zero, i.e use like: + * CALL(make_session(...)); */ +int make_session(ne_session **sess, server_fn fn, void *userdata); + +/* Server which sleeps for 10 seconds then closes the socket. */ +int sleepy_server(ne_socket *sock, void *userdata); + +#endif /* UTILS_H */ diff --git a/test/xml.c b/test/xml.c new file mode 100644 index 0000000..9d76215 --- /dev/null +++ b/test/xml.c @@ -0,0 +1,444 @@ +/* + neon test suite + Copyright (C) 2002-2003, Joe Orton <joe@manyfish.co.uk> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "config.h" + +#include <sys/types.h> + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "ne_xml.h" + +#include "tests.h" +#include "child.h" +#include "utils.h" + +/* A set of SAX handlers which serialize SAX events back into a + * pseudo-XML-like string. */ +static int startelm(void *userdata, int state, + const char *nspace, const char *name, + const char **atts) +{ + ne_buffer *buf = userdata; + int n; + + if (strcmp(name, "decline") == 0) + return NE_XML_DECLINE; + + ne_buffer_concat(buf, "<", "{", nspace, "}", name, NULL); + for (n = 0; atts && atts[n] != NULL; n+=2) { + ne_buffer_concat(buf, " ", atts[n], "='", atts[n+1], "'", NULL); + } + ne_buffer_zappend(buf, ">"); + + return state + 1; +} + +static int chardata(void *userdata, int state, const char *cdata, size_t len) +{ + ne_buffer *buf = userdata; + ne_buffer_append(buf, cdata, len); + return !strncmp(cdata, "!ABORT!", len); +} + +static int endelm(void *userdata, int state, + const char *nspace, const char *name) +{ + ne_buffer *buf = userdata; + ne_buffer_concat(buf, "</{", nspace, "}", name, ">", NULL); + return 0; +} + +/* A set of SAX handlers which do as above, but change some element + * names; used to check nested SAX handling is working properly. */ +static int startelm_xform(void *userdata, int state, + const char *nspace, const char *name, + const char **atts) +{ + if (strcmp(nspace, "two") == 0) + return startelm(userdata, state, nspace, "xform", atts); + else + return NE_XML_DECLINE; +} + +static int endelm_xform(void *userdata, int state, + const char *nspace, const char *name) +{ + if (strcmp(nspace, "two") == 0) + return endelm(userdata, state, nspace, "xform"); + else + return NE_XML_DECLINE; +} + +/* A set of SAX handlers which verify that state handling is working + * correctly. */ +static int startelm_state(void *userdata, int parent, + const char *nspace, const char *name, + const char **atts) +{ + int n; + + if (strcmp(nspace, "state") != 0) + return NE_XML_DECLINE; + + for (n = 0; atts[n]; n += 2) { + if (strcmp(atts[n], "parent") == 0) { + int expected = atoi(atts[n+1]); + + if (expected != parent) { + char err[50]; + sprintf(err, "parent state of %s was %d not %d", name, parent, + expected); + ne_buffer_zappend(userdata, err); + } + } + } + + return atoi(name+1); +} + +static int endelm_state(void *userdata, int state, + const char *nspace, const char *name) +{ + int expected = atoi(name + 1); + ne_buffer *buf = userdata; + + if (state != expected) + ne_buffer_concat(buf, "wrong state in endelm of ", name, NULL); + + return 0; +} + +/* A set of SAX handlers which verify that abort handling is working + * correctly. */ +static int startelm_abort(void *buf, int parent, + const char *nspace, const char *name, + const char **atts) +{ + if (strcmp(name, "abort-start") == 0) { + ne_buffer_zappend(buf, "ABORT"); + return NE_XML_ABORT; + } else + return startelm(buf, parent, nspace, name, atts); +} + +static int endelm_abort(void *buf, int state, + const char *nspace, const char *name) +{ + if (strcmp(name, "abort-end") == 0) { + ne_buffer_zappend(buf, "ABORT"); + return -1; + } else + return 0; +} + +enum match_type { + match_valid = 0, + match_invalid, + match_nohands, + match_encoding +}; + +static int parse_match(const char *doc, const char *result, enum match_type t) +{ + ne_xml_parser *p = ne_xml_create(); + ne_buffer *buf = ne_buffer_create(); + + if (t == match_invalid) + ne_xml_push_handler(p, startelm_abort, chardata, endelm_abort, buf); + if (t != match_encoding && t != match_nohands) { + ne_xml_push_handler(p, startelm_state, NULL, endelm_state, buf); + ne_xml_push_handler(p, startelm, chardata, endelm, buf); + ne_xml_push_handler(p, startelm_xform, chardata, endelm_xform, buf); + } + + ne_xml_parse(p, doc, strlen(doc)); + ne_xml_parse(p, "", 0); + + if (t == match_invalid) + ONV(ne_xml_valid(p), ("parse did not fail: %s", buf->data)); + else + ONV(!ne_xml_valid(p), ("parse failed: %s", ne_xml_get_error(p))); + + if (t == match_encoding) { + const char *enc = ne_xml_doc_encoding(p); + ONV(strcmp(enc, result), ("encoding was `%s' not `%s'", enc, result)); + } else if (t == match_valid) + ONV(strcmp(result, buf->data), + ("result mismatch: %s not %s", buf->data, result)); + + ne_xml_destroy(p); + ne_buffer_destroy(buf); + + return OK; +} + +static int matches(void) +{ +#define PFX "<?xml version='1.0'?>\r\n" +#define E(ns, n) "<{" ns "}" n "></{" ns "}" n ">" + static const struct { + const char *in, *out; + enum match_type invalid; + } ms[] = { + + /*** Simplest tests ***/ + { PFX "<hello/>", "<{}hello></{}hello>"}, + { PFX "<hello foo='bar'/>", + "<{}hello foo='bar'></{}hello>"}, + + /*** Tests for character data handling. ***/ + { PFX "<hello> world</hello>", "<{}hello> world</{}hello>"}, + /* test for cdata between elements. */ + { PFX "<hello>\r\n<wide> world</wide></hello>", + "<{}hello>\n<{}wide> world</{}wide></{}hello>"}, + + /*** Tests for namespace handling. ***/ +#define NSA "xmlns:foo='bar'" + { PFX "<foo:widget " NSA "/>", + "<{bar}widget " NSA ">" + "</{bar}widget>" }, + /* inherited namespace expansion. */ + { PFX "<widget " NSA "><foo:norman/></widget>", + "<{}widget " NSA ">" E("bar", "norman") "</{}widget>"}, + { PFX "<widget " NSA " xmlns:abc='def' xmlns:g='z'>" + "<foo:norman/></widget>", + "<{}widget " NSA " xmlns:abc='def' xmlns:g='z'>" + E("bar", "norman") "</{}widget>"}, + /* empty namespace default takes precedence. */ + { PFX "<widget xmlns='foo'><smidgen xmlns=''><norman/>" + "</smidgen></widget>", + "<{foo}widget xmlns='foo'><{}smidgen xmlns=''>" + E("", "norman") + "</{}smidgen></{foo}widget>" }, + /* inherited empty namespace default */ + { PFX "<bar xmlns='foo'><grok xmlns=''><fish/></grok></bar>", + "<{foo}bar xmlns='foo'><{}grok xmlns=''>" + E("", "fish") "</{}grok></{foo}bar>" }, + + /* regression test for neon <= 0.23.5 with libxml2, where the + * "dereference entities" flag was not set by default. */ + { PFX "<widget foo=\"no&body\"/>", + "<{}widget foo='no&body'></{}widget>" }, + { PFX "<widget foo=\"no body\"/>", + "<{}widget foo='no body'></{}widget>" }, + + /* tests for declined branches */ + { PFX "<hello><decline>fish</decline>" + "<world><decline/>yes</world>goodbye<decline/></hello>", + "<{}hello><{}world>yes</{}world>goodbye</{}hello>" }, + { PFX + "<hello><decline><nested>fish</nested>bar</decline><fish/></hello>", + "<{}hello>" E("", "fish") "</{}hello>" }, + /* tests for nested SAX handlers */ + { PFX "<hello xmlns='two'><decline/></hello>", + "<{two}hello xmlns='two'>" E("two", "xform") "</{two}hello>"}, + + /* tests for state handling */ + { PFX "<a55 xmlns='state'/>", "" }, + { PFX "<a777 xmlns='state' parent='0'/>", "" }, + { PFX "<a99 xmlns='state'><f77 parent='99'/>blah</a99>", "" }, + + /* tests for abort handling */ + { PFX "<hello><merry><abort-start/></merry></hello>", + "<{}hello><{}merry>ABORT", match_invalid }, + { PFX "<hello><merry><abort-end/>fish</merry></hello>", + "<{}hello><{}merry><{}abort-end>ABORT", match_invalid }, + { PFX "<hello>!ABORT!</hello>", "<{}hello>!ABORT!", match_invalid }, + { PFX "<hello>!ABORT!<foo/></hello>", "<{}hello>!ABORT!", match_invalid }, + { PFX "<hello>!ABORT!</fish>", "<{}hello>!ABORT!", match_invalid }, + + /* tests for encodings */ + { "<?xml version='1.0' encoding='ISO-8859-1'?><hello/>", + "ISO-8859-1", match_encoding }, + + { "<?xml version='1.0' encoding='UTF-8'?><hello/>", + "UTF-8", match_encoding }, + + /* test that parse is valid even with no handlers registered. */ + { PFX "<hello><old>world</old></hello>", "", match_nohands }, + + /* regression test for prefix matching bug fixed in 0.18.0 */ +#define THENS "xmlns:d='foo' xmlns:dd='bar'" + { PFX "<d:hello " THENS "/>", + "<{foo}hello " THENS "></{foo}hello>" }, + + /**** end of list ****/ { NULL, NULL } + }; + int n; + + for (n = 0; ms[n].in != NULL; n++) { + CALL(parse_match(ms[n].in, ms[n].out, ms[n].invalid)); + } + + return OK; +} + +static int mapping(void) +{ + static const struct ne_xml_idmap map[] = { + { "fee", "bar", 1 }, + { "foo", "bar", 2 }, + { "bar", "foo", 3 }, + { "", "bob", 4 }, + { "balloon", "buffoon", 5}, + { NULL, NULL, 0} + }; + int n; + + for (n = 0; map[n].id; n++) { + int id = ne_xml_mapid(map, NE_XML_MAPLEN(map) - 1, + map[n].nspace, map[n].name); + ONV(id != map[n].id, ("mapped to id %d not %d", id, map[n].id)); + } + + n = ne_xml_mapid(map, NE_XML_MAPLEN(map) - 1, "no-such", "element"); + ONV(n != 0, ("unknown element got id %d not zero", n)); + + return OK; +} + +/* Test for some parse failures */ +static int fail_parse(void) +{ + static const char *docs[] = { + "foo", + PFX "<bar:foo/>", + /* malformed namespace declarations */ + PFX "<foo xmlns:D=''/>", + PFX "<foo xmlns:='fish'/>", + PFX "<foo: xmlns:foo='bar'/>", + NULL + }; + int n; + + for (n = 0; docs[n]; n++) { + ne_xml_parser *p = ne_xml_create(); + const char *err; + + ne_xml_parse(p, docs[n], strlen(docs[n])); + ne_xml_parse(p, "", 0); + ONV(ne_xml_valid(p), ("`%s' was valid", docs[n])); + + err = ne_xml_get_error(p); + ONV(strstr(err, "parse error") == NULL, + ("bad error %s", err)); + + ne_xml_destroy(p); + } + + return OK; +} + +static int check_attrib(ne_xml_parser *p, const char **atts, + const char *nspace, const char *name, + const char *value) +{ + const char *act = ne_xml_get_attr(p, atts, nspace, name); + char err[50]; + int ret = 0; + + if (value == NULL) { + if (act != NULL) { + sprintf(err, "attribute %s was set to %s", name, act); + ret = NE_XML_ABORT; + } + } else { + if (act == NULL) { + sprintf(err, "attribute %s not found", name); + ret = NE_XML_ABORT; + } else if (strcmp(act, value) != 0) { + sprintf(err, "attribute %s was %s not %s", name, act, value); + ret = NE_XML_ABORT; + } + } + if (ret == NE_XML_ABORT) ne_xml_set_error(p, err); + return ret; +} + +static int startelm_attrib(void *userdata, int state, + const char *nspace, const char *name, + const char **atts) +{ + ne_xml_parser *p = userdata; + + if (strcmp(name, "hello") == 0) { + CALL(check_attrib(p, atts, NULL, "first", "second")); + CALL(check_attrib(p, atts, NULL, "third", "")); + CALL(check_attrib(p, atts, "garth", "bar", "asda")); + CALL(check_attrib(p, atts, "giraffe", "bar", NULL)); + CALL(check_attrib(p, atts, "hot", "dog", NULL)); + CALL(check_attrib(p, atts, NULL, "nonesuch", NULL)); + } else if (strcmp(name, "goodbye") == 0) { + if (atts[0] != NULL) { + ne_xml_set_error(p, "non-empty attrib array"); + return 1; + } + } + + return 1; +} + +static int attributes(void) +{ + ne_xml_parser *p = ne_xml_create(); + static const char doc[] = PFX + "<hello xmlns:foo='garth' first='second' third='' " + "foo:bar='asda' goof:bar='jar'><goodbye/></hello>"; + + ne_xml_push_handler(p, startelm_attrib, NULL, NULL, p); + + ne_xml_parse_v(p, doc, strlen(doc)); + + ONV(!ne_xml_valid(p), ("parse error: %s", ne_xml_get_error(p))); + + ne_xml_destroy(p); + return OK; +} + +/* Test for the get/set error interface */ +static int errors(void) +{ + ne_xml_parser *p = ne_xml_create(); + const char *err; + + ne_xml_set_error(p, "Fish food"); + err = ne_xml_get_error(p); + + ONV(strcmp(err, "Fish food"), ("wrong error %s!", err)); + + ne_xml_destroy(p); + return 0; +} + +ne_test tests[] = { + T(matches), + T(mapping), + T(fail_parse), + T(attributes), + T(errors), + T(NULL) +}; + |