summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/.cvsignore41
-rw-r--r--test/COPYING339
-rw-r--r--test/ChangeLog1329
-rw-r--r--test/Makefile.in171
-rw-r--r--test/README39
-rw-r--r--test/STATUS74
-rw-r--r--test/acl.c115
-rw-r--r--test/auth.c315
-rw-r--r--test/basic.c228
-rw-r--r--test/common/ChangeLog222
-rw-r--r--test/common/README4
-rw-r--r--test/common/child.c454
-rw-r--r--test/common/child.h113
-rwxr-xr-xtest/common/run.sh19
-rw-r--r--test/common/tests.c318
-rw-r--r--test/common/tests.h125
-rw-r--r--test/compress.c216
-rw-r--r--test/cookies.c140
-rw-r--r--test/expired.pem20
-rw-r--r--test/htdocs/plain1
-rw-r--r--test/lock.c540
-rwxr-xr-xtest/makekeys.sh151
-rw-r--r--test/notvalid.pem20
-rw-r--r--test/openssl.conf77
-rw-r--r--test/props.c508
-rw-r--r--test/redirect.c189
-rw-r--r--test/request.c1649
-rw-r--r--test/resolve.c59
-rw-r--r--test/run.sh24
-rw-r--r--test/server.key9
-rw-r--r--test/session.c158
-rw-r--r--test/skeleton.c51
-rw-r--r--test/socket.c918
-rw-r--r--test/ssl.c1476
-rw-r--r--test/string-tests.c492
-rw-r--r--test/stubs.c171
-rw-r--r--test/uri-tests.c377
-rw-r--r--test/util-tests.c256
-rw-r--r--test/utils.c107
-rw-r--r--test/utils.h57
-rw-r--r--test/xml.c444
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&amp;body\"/>",
+ "<{}widget foo='no&body'></{}widget>" },
+ { PFX "<widget foo=\"no&#x20;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)
+};
+