summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsandmann <sandmann>2004-05-18 07:48:50 +0000
committersandmann <sandmann>2004-05-18 07:48:50 +0000
commitcc477a23dc9d7fb4b534d67265b3e3b2a442bb0a (patch)
tree291c79830282b84f849615afcd009684a62ce794
Initial revision
-rwxr-xr-xAUTHORS1
-rw-r--r--CODINGSTYLE23
-rwxr-xr-xCOPYING482
-rw-r--r--ChangeLog0
-rwxr-xr-xINSTALL182
-rwxr-xr-xMakefile.am10
-rw-r--r--NEWS24
-rwxr-xr-xREADME32
-rwxr-xr-xTODO804
-rwxr-xr-xautogen.sh135
-rwxr-xr-xconfigure.in178
-rw-r--r--lac-1.0.pc.in11
-rw-r--r--src/Makefile.am39
-rw-r--r--src/copyrightheader20
-rw-r--r--src/lac.h505
-rw-r--r--src/lacactivity.c131
-rw-r--r--src/lacaddress.c740
-rw-r--r--src/lacbytequeue.c78
-rw-r--r--src/lacconnection.c535
-rw-r--r--src/lacdebug.c36
-rw-r--r--src/lacdns-cache.c617
-rw-r--r--src/lacdns-cache.h59
-rw-r--r--src/lacdns-config.c346
-rw-r--r--src/lacdns-config.h37
-rw-r--r--src/lacdns-messages.c2098
-rw-r--r--src/lacdns-messages.h275
-rw-r--r--src/lacdns-nameserver.c555
-rw-r--r--src/lacdns-nameserver.h47
-rw-r--r--src/lacdns-query.c808
-rw-r--r--src/lacdns-query.h78
-rw-r--r--src/lachttp.c2588
-rw-r--r--src/lacinternals.h50
-rw-r--r--src/lacprimitives.c690
-rw-r--r--src/lacuri.c1016
-rw-r--r--src/lacwatch.c328
-rwxr-xr-xsrc/makecopyright128
-rwxr-xr-xtests/Makefile.am42
-rw-r--r--tests/activity.c124
-rw-r--r--tests/addr-test.c41
-rw-r--r--tests/cname-test.c42
-rw-r--r--tests/connection-test.c127
-rw-r--r--tests/connection2-test.c70
-rw-r--r--tests/dns-test-async.c65
-rw-r--r--tests/dns-test.c123
-rw-r--r--tests/dns-test2.c122
-rw-r--r--tests/dns-wait-test.c55
-rw-r--r--tests/dns2-test.c56
-rw-r--r--tests/htmlparser.c171
-rw-r--r--tests/htmlparser.h74
-rw-r--r--tests/http-test.c190
-rw-r--r--tests/lacwget.c672
-rw-r--r--tests/newdns-test.c117
-rw-r--r--tests/rdns-test-async.c78
-rw-r--r--tests/rdns-test.c56
-rw-r--r--tests/simple-test.c59
-rwxr-xr-xtests/trouble-sites.sh8
-rw-r--r--tests/udp-test.c88
-rw-r--r--tests/uri-test.c254
-rw-r--r--tests/watch-test.c108
59 files changed, 16428 insertions, 0 deletions
diff --git a/AUTHORS b/AUTHORS
new file mode 100755
index 0000000..b4d84b0
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1 @@
+Søren Sandmann (sandmann@daimi.au.dk)
diff --git a/CODINGSTYLE b/CODINGSTYLE
new file mode 100644
index 0000000..720629e
--- /dev/null
+++ b/CODINGSTYLE
@@ -0,0 +1,23 @@
+Indent:
+
+ -
+
+
+Rules for braces
+================
+
+ - if one branch of an if statement has braces, then both brances must
+ have braces
+
+if (foo)
+{
+ something();
+ something_else();
+}
+else
+{
+ blah();
+}
+
+ - if a branch of an if statement spans more than one line, then it must
+ have braces. \ No newline at end of file
diff --git a/COPYING b/COPYING
new file mode 100755
index 0000000..bf50f20
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,482 @@
+ GNU LIBRARY GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1991 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the library GPL. It is
+ numbered 2 because it goes with version 2 of the ordinary GPL.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Library General Public License, applies to some
+specially designated Free Software Foundation software, and to any
+other libraries whose authors decide to use it. You can use it for
+your libraries, 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 library, or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link a program with the library, you must provide
+complete object files to the recipients so that they can relink them
+with the library, after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ Our method of protecting your rights has two steps: (1) copyright
+the library, and (2) offer you this license which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ Also, for each distributor's protection, we want to make certain
+that everyone understands that there is no warranty for this free
+library. If the library is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original
+version, 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 companies distributing free
+software will individually obtain patent licenses, thus in effect
+transforming the program into proprietary software. To prevent this,
+we have made it clear that any patent must be licensed for everyone's
+free use or not licensed at all.
+
+ Most GNU software, including some libraries, is covered by the ordinary
+GNU General Public License, which was designed for utility programs. This
+license, the GNU Library General Public License, applies to certain
+designated libraries. This license is quite different from the ordinary
+one; be sure to read it in full, and don't assume that anything in it is
+the same as in the ordinary license.
+
+ The reason we have a separate public license for some libraries is that
+they blur the distinction we usually make between modifying or adding to a
+program and simply using it. Linking a program with a library, without
+changing the library, is in some sense simply using the library, and is
+analogous to running a utility program or application program. However, in
+a textual and legal sense, the linked executable is a combined work, a
+derivative of the original library, and the ordinary General Public License
+treats it as such.
+
+ Because of this blurred distinction, using the ordinary General
+Public License for libraries did not effectively promote software
+sharing, because most developers did not use the libraries. We
+concluded that weaker conditions might promote sharing better.
+
+ However, unrestricted linking of non-free programs would deprive the
+users of those programs of all benefit from the free status of the
+libraries themselves. This Library General Public License is intended to
+permit developers of non-free programs to use free libraries, while
+preserving your freedom as a user of such programs to change the free
+libraries that are incorporated in them. (We have not seen how to achieve
+this as regards changes in header files, but we have achieved it as regards
+changes in the actual functions of the Library.) The hope is that this
+will lead to faster development of free libraries.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, while the latter only
+works together with the library.
+
+ Note that it is possible for a library to be covered by the ordinary
+General Public License rather than by this special one.
+
+ GNU LIBRARY GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library which
+contains a notice placed by the copyright holder or other authorized
+party saying it may be distributed under the terms of this Library
+General Public License (also called "this License"). Each licensee is
+addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, 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 library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete 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 distribute a copy of this License along with the
+Library.
+
+ 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 Library or any portion
+of it, thus forming a work based on the Library, 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) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+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 Library, 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 Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you 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.
+
+ If distribution of 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 satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+ 6. As an exception to the Sections above, you may also compile or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Accompany the work with a written offer, valid for at
+ least three years, to give the same user the materials
+ specified in Subsection 6a, above, for a charge no more
+ than the cost of performing this distribution.
+
+ c) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ d) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. 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.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library 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.
+
+ 9. 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 Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+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.
+
+ 11. 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 Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library 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 Library.
+
+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.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library 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.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Library 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 Library
+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 Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+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
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "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
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. 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 LIBRARY 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
+LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Libraries
+
+ If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change. You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+ To apply these terms, attach the following notices to the library. 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 library's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ 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.
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+ library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+ <signature of Ty Coon>, 1 April 1990
+ Ty Coon, President of Vice
+
+That's all there is to it!
diff --git a/ChangeLog b/ChangeLog
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/ChangeLog
diff --git a/INSTALL b/INSTALL
new file mode 100755
index 0000000..b42a17a
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,182 @@
+Basic Installation
+==================
+
+ These are generic installation instructions.
+
+ The `configure' shell script attempts to guess correct values for
+various system-dependent variables used during compilation. It uses
+those values to create a `Makefile' in each directory of the package.
+It may also create one or more `.h' files containing system-dependent
+definitions. Finally, it creates a shell script `config.status' that
+you can run in the future to recreate the current configuration, a file
+`config.cache' that saves the results of its tests to speed up
+reconfiguring, and a file `config.log' containing compiler output
+(useful mainly for debugging `configure').
+
+ If you need to do unusual things to compile the package, please try
+to figure out how `configure' could check whether to do them, and mail
+diffs or instructions to the address given in the `README' so they can
+be considered for the next release. If at some point `config.cache'
+contains results you don't want to keep, you may remove or edit it.
+
+ The file `configure.in' is used to create `configure' by a program
+called `autoconf'. You only need `configure.in' if you want to change
+it or regenerate `configure' using a newer version of `autoconf'.
+
+The simplest way to compile this package is:
+
+ 1. `cd' to the directory containing the package's source code and type
+ `./configure' to configure the package for your system. If you're
+ using `csh' on an old version of System V, you might need to type
+ `sh ./configure' instead to prevent `csh' from trying to execute
+ `configure' itself.
+
+ Running `configure' takes awhile. While running, it prints some
+ messages telling which features it is checking for.
+
+ 2. Type `make' to compile the package.
+
+ 3. Optionally, type `make check' to run any self-tests that come with
+ the package.
+
+ 4. Type `make install' to install the programs and any data files and
+ documentation.
+
+ 5. You can remove the program binaries and object files from the
+ source code directory by typing `make clean'. To also remove the
+ files that `configure' created (so you can compile the package for
+ a different kind of computer), type `make distclean'. There is
+ also a `make maintainer-clean' target, but that is intended mainly
+ for the package's developers. If you use it, you may have to get
+ all sorts of other programs in order to regenerate files that came
+ with the distribution.
+
+Compilers and Options
+=====================
+
+ Some systems require unusual options for compilation or linking that
+the `configure' script does not know about. You can give `configure'
+initial values for variables by setting them in the environment. Using
+a Bourne-compatible shell, you can do that on the command line like
+this:
+ CC=c89 CFLAGS=-O2 LIBS=-lposix ./configure
+
+Or on systems that have the `env' program, you can do it like this:
+ env CPPFLAGS=-I/usr/local/include LDFLAGS=-s ./configure
+
+Compiling For Multiple Architectures
+====================================
+
+ You can compile the package for more than one kind of computer at the
+same time, by placing the object files for each architecture in their
+own directory. To do this, you must use a version of `make' that
+supports the `VPATH' variable, such as GNU `make'. `cd' to the
+directory where you want the object files and executables to go and run
+the `configure' script. `configure' automatically checks for the
+source code in the directory that `configure' is in and in `..'.
+
+ If you have to use a `make' that does not supports the `VPATH'
+variable, you have to compile the package for one architecture at a time
+in the source code directory. After you have installed the package for
+one architecture, use `make distclean' before reconfiguring for another
+architecture.
+
+Installation Names
+==================
+
+ By default, `make install' will install the package's files in
+`/usr/local/bin', `/usr/local/man', etc. You can specify an
+installation prefix other than `/usr/local' by giving `configure' the
+option `--prefix=PATH'.
+
+ You can specify separate installation prefixes for
+architecture-specific files and architecture-independent files. If you
+give `configure' the option `--exec-prefix=PATH', the package will use
+PATH as the prefix for installing programs and libraries.
+Documentation and other data files will still use the regular prefix.
+
+ In addition, if you use an unusual directory layout you can give
+options like `--bindir=PATH' to specify different values for particular
+kinds of files. Run `configure --help' for a list of the directories
+you can set and what kinds of files go in them.
+
+ If the package supports it, you can cause programs to be installed
+with an extra prefix or suffix on their names by giving `configure' the
+option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'.
+
+Optional Features
+=================
+
+ Some packages pay attention to `--enable-FEATURE' options to
+`configure', where FEATURE indicates an optional part of the package.
+They may also pay attention to `--with-PACKAGE' options, where PACKAGE
+is something like `gnu-as' or `x' (for the X Window System). The
+`README' should mention any `--enable-' and `--with-' options that the
+package recognizes.
+
+ For packages that use the X Window System, `configure' can usually
+find the X include and library files automatically, but if it doesn't,
+you can use the `configure' options `--x-includes=DIR' and
+`--x-libraries=DIR' to specify their locations.
+
+Specifying the System Type
+==========================
+
+ There may be some features `configure' can not figure out
+automatically, but needs to determine by the type of host the package
+will run on. Usually `configure' can figure that out, but if it prints
+a message saying it can not guess the host type, give it the
+`--host=TYPE' option. TYPE can either be a short name for the system
+type, such as `sun4', or a canonical name with three fields:
+ CPU-COMPANY-SYSTEM
+
+See the file `config.sub' for the possible values of each field. If
+`config.sub' isn't included in this package, then this package doesn't
+need to know the host type.
+
+ If you are building compiler tools for cross-compiling, you can also
+use the `--target=TYPE' option to select the type of system they will
+produce code for and the `--build=TYPE' option to select the type of
+system on which you are compiling the package.
+
+Sharing Defaults
+================
+
+ If you want to set default values for `configure' scripts to share,
+you can create a site shell script called `config.site' that gives
+default values for variables like `CC', `cache_file', and `prefix'.
+`configure' looks for `PREFIX/share/config.site' if it exists, then
+`PREFIX/etc/config.site' if it exists. Or, you can set the
+`CONFIG_SITE' environment variable to the location of the site script.
+A warning: not all `configure' scripts look for a site script.
+
+Operation Controls
+==================
+
+ `configure' recognizes the following options to control how it
+operates.
+
+`--cache-file=FILE'
+ Use and save the results of the tests in FILE instead of
+ `./config.cache'. Set FILE to `/dev/null' to disable caching, for
+ debugging `configure'.
+
+`--help'
+ Print a summary of the options to `configure', and exit.
+
+`--quiet'
+`--silent'
+`-q'
+ Do not print messages saying which checks are being made. To
+ suppress all normal output, redirect it to `/dev/null' (any error
+ messages will still be shown).
+
+`--srcdir=DIR'
+ Look for the package's source code in directory DIR. Usually
+ `configure' can determine that directory automatically.
+
+`--version'
+ Print the version of Autoconf used to generate the `configure'
+ script, and exit.
+
+`configure' also accepts some other, not widely useful, options.
diff --git a/Makefile.am b/Makefile.am
new file mode 100755
index 0000000..cbb3ece
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,10 @@
+## Process this file with automake to produce Makefile.in
+
+SUBDIRS = src tests
+
+EXTRA_DIST = TODO
+
+pkgconfigdir=$(libdir)/pkgconfig
+pkgconfig_DATA=lac-1.0.pc
+
+$(pkgconfig_DATA): config.status lac-1.0.pc.in
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..60536c4
--- /dev/null
+++ b/NEWS
@@ -0,0 +1,24 @@
+Version 0.6:
+
+ * Improved robustness against broken servers.
+
+ - Thanks to
+
+ www.adobe.com
+ art.gnome.org
+ www.sgi.com
+ www.jp.dk
+
+ for putting up broken web servers.
+
+ * DNS queries can now be canceled
+
+ * New API calls to do "blocking" DNS lookups
+
+ - "blocking" here means that the call will not return until the
+ lookup is finished, but that the main loop will still run so
+ that a GUI will stil update etc.
+
+ * Support for relative URI (courtesy of GnomeVFS).
+
+ \ No newline at end of file
diff --git a/README b/README
new file mode 100755
index 0000000..86665c6
--- /dev/null
+++ b/README
@@ -0,0 +1,32 @@
+"Lac" expands to "Library for Asynchronous Communication". It is a
+network abstraction library like a zillion others, but with a few
+features rarely found in other network libraries:
+
+ - Works well with the GLib main loop.
+ - Asynchronous, caching DNS resolver.
+ - Pipelining HTTP 1.1 implementation
+
+These features haven't been tested much, so they will contain bugs and
+missing pieces here and there. They should be basically working,
+though.
+
+Features that would be nice to have, but aren't in the libary yet:
+
+ - HTTP cache
+ - HTTP cookies
+ - HTTP Proxy support
+ - Support for SSL
+ - FTP implementation
+ - Documentation
+
+You can help adding these, of course.
+
+You can download the library at
+
+ http://www.daimi.au.dk/~sandmann/lac-0.6.tar.gz
+
+Lac is free software; you can modify and distribute it under the terms
+of the LGPL (the GNU Library General Public License). See the file
+COPYING for details.
+
+Soeren Sandmann (sandmann@daimi.au.dk)
diff --git a/TODO b/TODO
new file mode 100755
index 0000000..2f2c890
--- /dev/null
+++ b/TODO
@@ -0,0 +1,804 @@
+to do:
+==========
+correctness:
+ * SIGPIPE?
+
+ - ignore it around calls to send()?
+
+ Problem: Other threads might not want the signal
+ ignored. This race condition may be acceptable
+
+ * Fix problem where we go into infinite loop if the server
+ closes the connection before we send anything. (showstopper
+ for 1.0)
+
+ - www.skolekom.dk used to be like that (problem is in
+ transport_handle_close)
+
+ * thread-safeness (big lock?) (showstopper for 1.0). Note:
+ lac_activity_cancel() from another thread. This could get
+ hairy :-/
+
+ * consider
+
+ activity = lac_begin_activity (timeout);
+ ...;
+ lac_activity_wait(activity)/lac_activity_cancel();
+
+ lac_end_activity(activity);
+
+ within "..." you can call lac_activity_cancel();
+
+ * relative URI's
+ - showstopper for 1.0
+
+ The code from GnomeVFS is probably not good enough (it
+ doesn't handle relative uri's that start with "..". Such
+ uri's are broken, but fairly common)
+
+ * we don't interprete resolv.conf correctly. Some options are
+ simply ignored.
+
+ * Do proper URI escaping. (There is an escape_spaces() hack in
+ lachttp.c, but we should do better).
+
+ * A server has been spotted that returns "HTTP/00 Ok" as the
+ status line. What do we do?
+
+ - Declare it broken and retry.
+
+ - Accept it based on "Ok".
+
+misc:
+ * never emit events from a constructor. apps will get confused
+ if they get an event before the constructor returns. add an idle
+ handler instead. [May already be done, need to check]
+
+ * need a real regression test suite
+ * get rid of CacheRecord->type
+ * Cleanup of the DNS module
+
+ - "_LacDns" prefix internal-but-exported symbols
+
+ * #ifdef LAC_DEBUG_ENABLED around debug code
+ * debug messaging framework
+ * GObjectify stuff
+ - Is this worth it?
+ * some functions in lacprivate.c report errors with g_warning
+ * error messages should not be \n-terminated
+ * Consider making the LacActivity cookies out parameters
+
+performance:
+ * figure out when exactly the write buffer should be flushed in the
+ http module.
+
+ - use "LoadGroup" and ask the application to call flush()
+ when it won't send anymore requests for a while
+
+ - add a timeout of say 200 ms so that a request wont
+ stay longer than this timeout in the send queue
+
+ * When an answer for one DNS query arrives, go through all
+ outstanding queries to see if the information was useful
+ there?
+
+ This could be expensive if we have many outstanding queries.
+ Note that this will not get rid of the "use existing" code,
+ because we still want to avoid sending any messages to the
+ server if we can.
+
+consistency:
+ * be consistent about "str" vs. "string"
+ - what does glib do?
+
+ * generally:
+
+ - foo_get_bar() means "whatever is returned is owned by
+ the library - you must copy or ref it".
+
+ This is used both with return values and with
+ return-by-callback.
+
+ However with an out parameter, the user is generally
+ responsible for freeing.
+
+ - foo_wait_for_bar() means "lac will start a main loop"
+
+ This is used only with return values
+
+ - foo_lookup_bar() means whatever is returned is owned by
+ the library - you must copy or ref it.
+
+ Used with return-by-callback.
+
+ - foo_new() means "you must free whatever is returned"
+
+error handling:
+ * make the err->messages more helpful
+ * better and more error codes
+ socket():
+ Irix:
+ EPROTONOSUPPORT, EMFILE, ENFILE, EACCES, ENOBUFS
+
+ Solaris:
+ EPROTONOSUPPORT, EMFILE, ENOMEM, ENOSR, EACCES
+
+ Glibc:
+ EPROTONOSUPPORT, EMFILE, ENFILE, EACCES, ENOBUFS,
+ ENOMEM, EINVAL
+
+ HP-UX:
+ EAFNOSUPPORT, EHOSTDOWN, EINVAL, EMFILE, ENFILE,
+ ENOBUFS, EPROTONOSUPPORT, EPROTOTYPE, ESOCKTNOSUPPORT,
+ ETIMEDOUT
+
+ connect:
+ Solaris:
+ EACCES, EADDRINUSE, EADDRNOTAVIL, EAFNOSUPPORT,
+ EALREADY, EBADF, ECONNREFUSED, EINPROGRESS, EINTR,
+ EINVAL, EIO, EISCONN, ELOOP, ENETUNREACH, ENOENT,
+ ENOSR, ENXIO, ETIMEDOUT, EWOULDBLOCK, ENOTDIR
+ ENOTSOCK, EPROTOTYPE
+
+ Irix:
+ EBADF, ENOTSOCK, EADDRNOTAVAIL, EAFNOSUPPORT,
+ EISCONN, ETIMEDOUT, ECONNREFUSED, ENETUNREACH,
+ EADDRINUSE, EFAULT, EINPROGRESS, EALREADY
+
+ HP-UX:
+ EADDRINUSE, EADDRNOTAVAIL, EAFNOSUPPORT, EALREADY,
+ EBADF, ECONNREFUSED, EFAULT, EINPROGRESS, EINTR,
+ EINVAL, EISCONN, ENETDOWN, ENETUNREACH, ENOBUFS,
+ ENOBUFS, ENODEV, ENOSPC, ENOTSOCK, EOPNOTSUPP,
+ ETIMEDOUT
+
+ Glibc:
+ EBADF, EFAULT, ENOTSOCK, EISCONN, ECONNREFUSED,
+ ETIMEDOUT, ENETUNREACH, EADDRINUSE, EINPROGRESS,
+ EALREADY, EAFNOSUPPORT, EACCES
+
+features:
+ * We should handle HTTP redirects
+ - Probably add a new "RedirectNotify" event
+ - Be able to turn it off for a request. An app. may
+ want to handle it differently
+
+ * IPv6
+ - not for 1.0, but must make sure it can be added
+
+ tentative api:
+
+ bool lac_address_is_ip6();
+
+ Assumption: the LacAddress will never need to cover
+ more than IP4 and IP6.
+
+ - consider for 1.0 to make the ipv4 specific API conditional
+ on !LAC_IPV6_UNSAFE
+
+ * https/ssl support, at least make is possible later
+ * proxy support in http module
+ - At least make sure the API doesn't prevent us
+ adding it naturally later
+
+ maybe we just need
+
+ lac_http_request_set_proxy (...);
+
+ * http cache
+ - We should probably virtualize the read() and write() so
+ file/persistent operations can be changed to use eg.
+ async io on linux.
+
+ possible api:
+
+ we already have lac_request_dispatch()
+
+ lac_http_cache_new (directory, read, write, ...);
+ lac_http_cache_dispatch_request (LacHttpRequest *req,
+ gboolean reload);
+
+ - How abstract should the read() and write() be?
+
+ - On the other hand, maybe the library should know
+ about different ways of structuring i/o
+
+ * cookie support (see RFC 2109)
+ * LacCowString (?)
+ - do a simple copy-on-write string on contiguous memory
+
+ pro: simple, easy to get c-string
+ con: insert in the middle of it is inefficient
+
+ - how much data in an xml/html document is markup and
+ how much is character data?
+ - we risk doing a lot if copying during parsing of xml
+ if eg a begin tag spans more than one chunk of data
+ - even if we get the cow string into glib and get
+ gdkpixbuf to use it, images may need to a lot of
+ uncompressing anyway so that all of the data will be
+ copied anyway.
+ - actually, memcpy() is probably not really a bottleneck
+ if do aggressive caching and reuse images whenever
+ possible.
+ - since we have "const char *" and a dummy reserved pointer
+ in events, we can do this later without breaking anything.
+
+ possible API:
+
+ /*
+ * Copy-on-write string
+ */
+ typedef struct _LacCowString LacCowString;
+ LacCowString *lac_cow_string_new (const gchar *init,
+ gsize len);
+ LacCowString *lac_cow_string_copy (const LacCowString *cow_string);
+ void lac_cow_string_free (LacCowString *cow_string);
+ gboolean lac_cow_string_equal (const LacCowString *cow_string1,
+ const LacCowString *cow_string2);
+ guint lac_cow_string_hash (const LacCowString *cow_string);
+ gint lac_cow_string_compare (const LacCowString *cow_string1,
+ const LacCowString *cow_string2);
+ LacCowString *lac_cow_string_truncate (LacCowString *cow_string,
+ gsize size);
+ LacCowString *lac_cow_string_prepend (LacCowString *cow_string,
+ const gchar *val,
+ gsize len);
+ LacCowString *lac_cow_string_insert (LacCowString *cow_string,
+ gsize pos,
+ const gchar *val,
+ gsize len);
+ LacCowString *lac_cow_string_append (LacCowString *cow_string,
+ const gchar *val,
+ gsize len);
+ LacCowString *lac_cow_string_erase (LacCowString *cow_string,
+ gsize pos,
+ gsize len);
+ LacCowString *lac_cow_string_concatenate (LacCowString *cow_string1, ...);
+
+ * add a timeout implementation?
+
+ - better scalablity
+ - perhaps more convenient
+ - initial implementation can ve very simple in terms
+ of g_timeout_add()
+
+ API:
+
+ /*
+ * Timeout
+ */
+ typedef struct _LacTimeout LacTimeout;
+
+ LacTimeout *lac_timeout_new (guint interval,
+ GSourceFunc callback,
+ gpointer data);
+ void lac_timeout_set_interval (LacTimeout *timeout,
+ guint interval);
+ guint lac_timeout_get_interval (LacTimeout *timeout);
+ void lac_timeout_disable (LacTimeout *timeout);
+ void lac_timeout_enable (LacTimeout *timeout);
+ void lac_timeout_destroy (LacTimeout *timeout);
+
+activity:
+ Use cases:
+ 1 Dialog with a timeout and a cancel button and several
+ network activities that all must be completed before the
+ timeout.
+ 2 Just waiting for a DNS lookup (can't go on without it)
+ 3 Tell me when the lookup arrives
+
+ ad 1:
+
+ boolean
+ timeout (void)
+ {
+ update progressbar;
+ if (timed out)
+ {
+ cancel all activityies;
+ destroy_dialog;
+ timeout_id = 0;
+ return FALSE;
+ }
+ }
+
+ activity1 = start_activity_1 (..., f1, data1);
+ ...;
+ activity_k = start_activity_k (..., f_k, data_k);
+
+ struct { activities, dialog, dialog->progressbar } data;
+
+ lac_timeout (100, timeout, &data);
+
+ if (gtk_dialog_show (dialog) == CANCEL)
+ {
+ lac_activity_cancel (activity1, ..., activityk);
+ }
+
+ if (timeout_id)
+ g_timeout_remove (timeout_id);
+
+ ad 2:
+ lac_gethostbyname();
+
+
+*** different scheme ****
+
+ activity = lac_create_activity ();
+
+ lac_various_stuff (.., activity);
+ lac_more_stuff (, ... activity);
+ <do various stuff>
+
+ lac_activity_cancel (activity); // cancels the whole thing
+ lac_activity_wait (activity, timeout); // wait until they all
+ // have completed
+
+ drawback here is that all calls must take an activity parameter.
+ Programs that don't know about "activity" will have to pass NULL.
+
+Another possibility is
+
+ LacFuture lac_address_new_from_name_future ();
+
+ lac_future_free (LacFuture);
+ lac_future_resolve_string()
+ lac_future_resolve_address()
+ lac_future_resolve_pointer()
+ gboolean lac_future_in_progress()
+ gboolean lac_future_available()
+ gboolean lac_future_canceled()
+ lac_future_wait_for_one (timeout, future, ..); // wait for at least one of the futures
+ // to become canceled, available or destroyed
+ lac_future_wait_for_all (timeout, ...);
+ lac_future_set_notification (future, callback, data);
+ /* possibly */
+ Future lac_future_combine_or (future1, future2, ...); // create new future that becomes
+ // available when one of the futures
+ // are available
+ Future lac_future_comibne_and (future1, future2, ...);
+
+ enum FutureStates
+ {
+ FUTURE_IN_PROGRESS,
+ FUTURE_CANCELED,
+ FUTURE_AVAILABLE,
+ }
+
+ My current thinking is:
+
+ get rid of all the "Activity" stuff, then (maybe post 1.0):
+ add the LacFuture stuff and reimplement the existing
+ methods with that.
+
+ LacFuture implementation note:
+
+ - it is illegal to destroy a future that you are waiting on
+ The idiom is
+
+ create futures
+ install timeout that possibly cancels futures
+ future = show_dialog ();
+ wait for futures
+ if (future_is_available (future1))
+ { // you can assume everything is available
+ }
+ destroy futures;
+
+ It should probably be possible to create futures yourself.
+ And they should probably just return a gpointer.
+
+ lac_future_new ();
+ lac_future_make_available (future, gpointer);
+ lac_future_set_cancel_notify (future, cancel_func, data);
+
+ response ()
+ {
+ Future *future;
+
+ lac_future_make_available (future, GTK_RESPONSE_NO);
+ }
+
+ Future
+ show_dialog ()
+ {
+ ...;
+ future = future_new();
+ gtk_widget_show();
+ connect (response, yada, yada, future);
+ }
+
+
+
+
+documentation:
+ * write it
+
+ * always have ref()/unref() pairs and perhaps a close()/shutdown()
+ on objects.
+
+ - easier bindings for languages with gc
+ - it is CRACK to use ref_count as any indicator of how
+ many clients are interested in events.
+
+ * LacActivity:
+
+ This is perfectly okay and guaranteed to work:
+
+ hmm, it doesn't work: what if the returned activity is NULL?
+
+ typedef struct LookupData LookupData;
+ struct LookupData {
+ int timeout_id;
+ LacActivity activity;
+ gboolean canceled;
+ };
+
+ static gboolean
+ timeout_handler (gpointer data)
+ {
+ LookupData *lookup_data = data;
+
+ lookup_data->canceled = TRUE;
+ g_source_remove (lookup_data->timeout_id);
+ lac_activity_cancel (lookup_data->activity);
+ return FALSE;
+ }
+
+ int
+ main()
+ {
+ LookupData data;
+ LacAddress *addr;
+
+ data.canceled = FALSE;
+ data.timeout = g_timeout_add (1000, timeout_handler, &data)
+ addr = lac_address_new_from_name_wait (
+ "www.cocacola.com", &data.activity, NULL);
+ if (data.canceled)
+ {
+ ...;
+ }
+ else if (addr)
+ {
+ ...;
+ }
+ }
+
+Already done:
+==============
+
+misc:
+ * move a lot of #includes from lac.h to lacprivate.h
+ * rename lacdns-manager.[ch] -> lacdns-query.[ch].
+ * format functions correctly
+ * Major cleanup of the DNS module
+
+ - Split into modules: main, cache, server, messages
+ - separate header files.
+ - Figure out exactly what needs to be exported from
+ each module.
+ - one big header-file (not done)
+
+ Probably do this one module at a time.
+ * make lac_address_new_from_name() use lac_dns_get_addresses
+ * don't expose of address internals. Use a _lac_get_internals()
+ function instead.
+ * get rid of UNIX sockets completely?
+
+ Rationale: they will probably complicate the API
+ significantly, and it can be argued that they do not
+ belong in a network library since they are really a
+ form of IPC on the same machine.
+ If we are going to include them, it may make
+ sense to consider a more object-oriented approach to
+ the API.
+
+ On the other hand, getting rid of them will probably mean
+ they can't be added later.
+
+correctness:
+
+ * We get 'broken server' too often
+ * adobe.com is not completely fixed
+
+ - answer_timeout is apparently somewhat broken. It
+ should only be installed when the transport is
+ actually pipelining, and it should only fire once.
+ Also, hosts should ignore broken notifies when the
+ host is already broken.
+
+ - There is a similar problem with art.gnome.org, only
+ here the server sends an RST when it sees a pipeline
+
+ * We don't work with www.adobe.com/main.html
+ also, art.gnome.org sends RST when it sees a pipeline
+
+ Problems (alle of these only happens with pipelining).
+
+ - Sometimes we get malformed replies to the very first
+ request. strace confirms that these are genuinely
+ malformed. There is not much we can do about that
+ except report the error. (This happens rarely, and
+ only immediately after a pipelined attempt).
+
+ - Sometimes the server just stops responding in the
+ middle of a request. We can detect this with the
+ answer_timeout.
+
+ When that timeout fires, there _is_ a current
+ request and a lot of requests in progress. We can
+ reschedule the in-progress requests and emit a
+ "broken_server_notify" , but what about the
+ "current" request?
+
+ - add a new BROKEN_SERVER_PLEASE_RETRY error
+ message.
+
+ - Apps will probably get it wrong
+
+ - Add a new RESTARTING event meaning that the
+ app is supposed to throw away what is has
+ read so far
+
+ - Apps will probably get it wrong.
+
+ - let it be
+
+ - The *user*/app will eventually
+ cancel it.
+
+ - Definitely suboptimal
+
+ - silently retry, and ignore as many bytes as we
+ have already seen so far.
+
+ - It sucks.
+ - It is broken (because stuff may have
+ been cached, and the next request
+ might return something different)
+ - But it is probably the best we can do
+
+ * Strategy for dealing with broken servers in general.
+ - We are optimistic. When we see a broken server,
+ resend everything on separate connections
+
+ - what if server is so broken that it *never*
+ responds to any requests in a pipeline? (jp.dk)
+
+ - just don't work with these?
+
+ - don't hear anything for k * RTT,
+ close the connection and retry?
+
+ Also maintain a list of servers known to be broken.
+ That way we will be able to disable pipelining for
+ those in the case where we initially send only one
+ request.
+
+ - We are pessimistic. Only pipeline when we *know* the
+ server is not broken.
+
+ - significantly reduce the utility of pipelining
+
+ * Fix problem with www.jp.dk. Apparently it has an http 1.0
+ cache in front of it that accepts http 1.0 keep-alive, but
+ doesn't understand pipelining. It seems that when it gets
+ more than one request, it ignores all of them.
+
+ Find out if this cache (CacheFlow 725?) is special, or if
+ many http 1.0 implementations behave like this. Note,
+ http://www.cocacola.com/ is an HTTP 1.0 server that fully
+ understands pipelining with connection: keep-alive.
+
+ * Fix problems with Enterprise 3.6 and the http module.
+ eg. www.sgi.com runs enterprise 3.6.
+
+ (Enterprise 3.6 is known to be broken. See
+ http://www.research.att.com/~bala/papers/usits01.ps.gz
+
+ It is probably best to just not pipeline with E3.6. There are
+ very few of them anyway).
+
+ * it is definitely wrong that domain_name_hash() is case sensitive
+ while domain_name_equal() isn't. Making domain_name_hash() case
+ insensitive would solve it, but rethink case-sensitivity instead.
+
+ - assume domain name are case sensitive.
+ - would probably mean a lot extra lookups if the user
+ actually asks for the same name in different
+ capitalizations.
+ - some extra entries in the cache.
+ - probably broken:
+ (If we ask for "blah.com", we may get the
+ answer "BLAH.COM". What will we do about
+ that?)
+ - would make UTF-8 just work (?)
+
+ - just make domain_name_hash() case insensitive
+ - slightly unclean, I think.
+ - utf-8 problem. Is utf-8 actually going to be
+ standard?
+ [Ended up just making domain_name_hash() case insensitive]
+
+ * make sure we don't emit any events after the user has called
+ lac_connection_close()
+ * www.cocacola.com is an http 1.0 server. if we pipeline a lot
+ of requests to it, it will respond to them, so when we decide
+ that a server can't pipeline due to being 1.0, we should wait
+ until it _actually_ closes the connection before reporting
+ to the host.
+ * well, if we get a "Connection: keep-alive", then we should
+ keep the connection alive
+ * a response from an 1.0 server is generally terminated by
+ closing the connection, unless we have reason to believe
+ otherwise
+ * validate all input to the dns implentation as UTF8 (- right?)
+ - probably not. No, the dns system allows arbitrary
+ data for domain names
+ * "http-test http:/cmdrtaco.net" produces something with "localhost"
+ * use lac_dns_get_name() in lac_address_get_hostname()
+
+ * in lac_address_new_from_new(), if dns_query() calls back
+ immediately, the activity will be invalid. What do we do??
+
+ * It seems sometimes the answer timeout fires after the
+ transport has been destroyed.
+
+
+performance:
+ * limit number of outstanding dns queries? Yes.
+ * sockets are almost always writable, so in general we should avoid
+ poll()ing just to write. Instead, just send(), and only call
+ poll() if we get a WOULDBLOCK.
+ - This is mosly done, I think
+
+ * dispatch functions, like blah_do_read() and blah_do_write() should
+ not run for more than, say, 10 ms. This should normally be enough
+ to empty the buffers while not starving the rest of the application
+ - this is probably not worth it. Generally, a while(read())
+ will complete in << 10 ms.
+ - When COWStrings are there, maybe simply read into one big
+ cow string and then return that. If the cow string starts
+ its life at size 8192, then we'll almost never have to
+ expand it.
+ - Or generate a list of cowstrings as we read(), and then
+ call the callback several times.
+
+consistency:
+ * be consistent about whether len comes before data or vice versa
+ - data comes first followed by length. This is consistent
+ with unix, glib and everything else
+
+ * be consistent about whether stuff returned through callbacks is
+ owned by the user or the library
+ - probably best to make it be owned by the library,
+ because in some cases we have to return compound
+ structures that are difficult for the app to free
+ - probably best to make addresses copyable by value
+ and get rid of ref counting on them
+ * fix instances of
+
+ if (err->code == ...)
+
+features:
+ * re-add gethostby{name,addr}() with a recursive main loop.
+ * LacCowString
+
+ - do a complicated "smart string"
+
+ pro: insert in the middle will be more efficient
+ con: more complicated, get_c_string is difficult
+ there is considerable overhead for the rest
+
+ libxml may need to do some rewriting, ie. to
+ convert to utf8. This rewriting will have to be done
+ under any circumstances.
+
+ the "smart string" is definitely not worth it.
+ the simple _might_ be worth it, but
+
+error handling:
+ * Use g_error_matches() everywhere.
+ * rename lacprivate.c -> lacprimitives.c?
+ * should we make sure *everything* is emitted from the main thread.
+ ie., idle_add() stuff that we would otherwise do directly?
+
+ - Probably not, because the application is in a much
+ better position to make scheduling decisions
+
+ * we should never call a callback when the we are in an
+ inconsistent state.
+
+ Strategy for dealing with reentrancy: Queue up callbacks,
+ then run the queue at end of all public entries. For the
+ purposes of reentrancy, functions that we added to the
+ mainloop should be considered public.
+
+ * There is a reentrancy problem in lacdns-query.
+
+ The current strategy of putting uncachable records in the cache, then
+ deleting them later doesn't work because the uncachable records
+ are not removed when we call the user.
+
+ * the resend timeout should be shorter than the QUERY timeout, so
+ that if the answer to a previous query arrives after we have
+ sent a new query, then we should use it.
+
+ * DNS API
+
+ - Lose lac_dns_* functions?
+
+ Fold them into the lac_address_* namespace?
+
+ That would probably make the API smaller and more
+ consistent. OTOH, the lac_address_* names would become
+ longer.
+
+ It would be possible to add lac_dns_* functions
+ again later, eg. for mx and other dns stuff.
+
+ Is there an IPv6 issue with this?
+
+ /* address_new_from_name_wait */
+ /* address_wait_for_name */
+ /* address_lookup_name */
+ /* address_new_from_name_all */
+
+ - Do we need user settable timeouts on a request?
+
+ Does Windows/.NET have this?
+
+ - cancelation of DNS lookups?
+
+ make them return an int and have a new call
+ lac_async_cancel (int async_id);
+
+ Both could be handled by
+
+ int lac_address_get_from_name (name,
+ func,
+ data);
+ int lac_address_get_all_from_name (name,
+ func,
+ data);
+ void lac_address_cancel_lookup (int id);
+ LacAddress *lac_address_new_from_name_wait (name);
+ void lac_address_nwe_all_from_name (name);
+
+ * adobe.com is not completely fixed
+
+ sometimes it returns malformed responses after we have detected
+ that it choked. The answer here is probably "Well, tough." There
+ is only so much brokenness we can deal with.
+
+ * Improve HTTP/0.9 recognitiion.
+
+ Algorithm:
+
+ - if first four letters are not HTTP,
+ 0.9
+ - else if next letters are correct 1.0 or 1.1
+ 1.0 or 1.1
+ - else
+ 1.0
+
+ * We send out broken DNS messages if we are called with a very
+ long name
+
+ * In general when we use callbacks, always let the main loop
+ call back, if necessarily by using an idle hander. (It is
+ important that the user can count on the callback not having
+ been called when the function returns)
+
+ - more precisely: functions with both a return value
+ and a callback must call callback in idle (ie. after the
+ function has returned)
+
+ - No, actually not. It is even more important that
+ things like dns callbacks are processed immediately,
+ because the processing will often just consist in
+ "socket(); connect()", which will make *someone else*
+ do work, which is good.
+
diff --git a/autogen.sh b/autogen.sh
new file mode 100755
index 0000000..c627997
--- /dev/null
+++ b/autogen.sh
@@ -0,0 +1,135 @@
+#!/bin/sh
+# Run this to generate all the initial makefiles, etc.
+
+srcdir=`pwd`
+DIE=0
+
+(autoconf --version) < /dev/null > /dev/null 2>&1 || {
+ echo
+ echo "**Error**: You must have \`autoconf' installed to compile GtkEditor."
+ echo "Download the appropriate package for your distribution,"
+ echo "or get the source tarball at ftp://ftp.gnu.org/pub/gnu/"
+ DIE=1
+}
+
+(grep "^AM_PROG_LIBTOOL" $srcdir/configure.in >/dev/null) && {
+ (libtool --version) < /dev/null > /dev/null 2>&1 || {
+ echo
+ echo "**Error**: You must have \`libtool' installed to compile GtkEditor."
+ echo "Get ftp://ftp.gnu.org/pub/gnu/libtool-1.2.tar.gz"
+ echo "(or a newer version if it is available)"
+ DIE=1
+ }
+}
+
+grep "^AM_GNU_GETTEXT" $srcdir/configure.in >/dev/null && {
+ grep "sed.*POTFILES" $srcdir/configure.in >/dev/null || \
+ (gettext --version) < /dev/null > /dev/null 2>&1 || {
+ echo
+ echo "**Error**: You must have \`gettext' installed to compile GtkEditor."
+ echo "Get ftp://alpha.gnu.org/gnu/gettext-0.10.35.tar.gz"
+ echo "(or a newer version if it is available)"
+ DIE=1
+ }
+}
+
+(automake --version) < /dev/null > /dev/null 2>&1 || {
+ echo
+ echo "**Error**: You must have \`automake' installed to compile GtkEditor."
+ echo "Get ftp://ftp.gnu.org/pub/gnu/automake-1.3.tar.gz"
+ echo "(or a newer version if it is available)"
+ DIE=1
+ NO_AUTOMAKE=yes
+}
+
+
+# if no automake, don't bother testing for aclocal
+test -n "$NO_AUTOMAKE" || (aclocal --version) < /dev/null > /dev/null 2>&1 || {
+ echo
+ echo "**Error**: Missing \`aclocal'. The version of \`automake'"
+ echo "installed doesn't appear recent enough."
+ echo "Get ftp://ftp.gnu.org/pub/gnu/automake-1.3.tar.gz"
+ echo "(or a newer version if it is available)"
+ DIE=1
+}
+
+if test "$DIE" -eq 1; then
+ exit 1
+fi
+
+if test -z "$*"; then
+ echo "**Warning**: I am going to run \`configure' with no arguments."
+ echo "If you wish to pass any to it, please specify them on the"
+ echo \`$0\'" command line."
+ echo
+fi
+
+case $CC in
+xlc )
+ am_opt=--include-deps;;
+esac
+
+for coin in `find $srcdir -name configure.in -print`
+do
+ dr=`dirname $coin`
+ if test -f $dr/NO-AUTO-GEN; then
+ echo skipping $dr -- flagged as no auto-gen
+ else
+ echo processing $dr
+ macrodirs=`sed -n -e 's,AM_ACLOCAL_INCLUDE(\(.*\)),\1,gp' < $coin`
+ ( cd $dr
+ aclocalinclude="$ACLOCAL_FLAGS"
+ for k in $macrodirs; do
+ if test -d $k; then
+ aclocalinclude="$aclocalinclude -I $k"
+ ##else
+ ## echo "**Warning**: No such directory \`$k'. Ignored."
+ fi
+ done
+ if grep "^AM_GNU_GETTEXT" configure.in >/dev/null; then
+ if grep "sed.*POTFILES" configure.in >/dev/null; then
+ : do nothing -- we still have an old unmodified configure.in
+ else
+ echo "Creating $dr/aclocal.m4 ..."
+ test -r $dr/aclocal.m4 || touch $dr/aclocal.m4
+ echo "Running gettextize... Ignore non-fatal messages."
+ echo "no" | gettextize --force --copy
+ echo "Making $dr/aclocal.m4 writable ..."
+ test -r $dr/aclocal.m4 && chmod u+w $dr/aclocal.m4
+ fi
+ fi
+ if grep "^AM_GNOME_GETTEXT" configure.in >/dev/null; then
+ echo "Creating $dr/aclocal.m4 ..."
+ test -r $dr/aclocal.m4 || touch $dr/aclocal.m4
+ echo "Running gettextize... Ignore non-fatal messages."
+ echo "no" | gettextize --force --copy
+ echo "Making $dr/aclocal.m4 writable ..."
+ test -r $dr/aclocal.m4 && chmod u+w $dr/aclocal.m4
+ fi
+ if grep "^AM_PROG_LIBTOOL" configure.in >/dev/null; then
+ echo "Running libtoolize..."
+ libtoolize --force --copy
+ fi
+ echo "Running aclocal $aclocalinclude ..."
+ aclocal $aclocalinclude
+ if grep "^AM_CONFIG_HEADER" configure.in >/dev/null; then
+ echo "Running autoheader..."
+ autoheader
+ fi
+ echo "Running automake --gnu $am_opt ..."
+ automake --add-missing --gnu $am_opt
+ echo "Running autoconf ..."
+ autoconf
+ )
+ fi
+done
+
+conf_flags="--enable-maintainer-mode --enable-compile-warnings" #--enable-iso-c
+
+if test x$NOCONFIGURE = x; then
+ echo Running $srcdir/configure $conf_flags "$@" ...
+ $srcdir/configure $conf_flags "$@" \
+ && echo Now type \`make\' to compile $PKG_NAME
+else
+ echo Skipping configure process.
+fi
diff --git a/configure.in b/configure.in
new file mode 100755
index 0000000..f523151
--- /dev/null
+++ b/configure.in
@@ -0,0 +1,178 @@
+dnl Process this file with autoconf to produce a configure script.
+AC_INIT(README)
+LACMAJOR_VERSION=0
+LACMINOR_VERSION=6
+LACVERSION=$LACMAJOR_VERSION.$LACMINOR_VERSION
+dnl
+AC_SUBST(LACMAJOR_VERSION)
+AC_SUBST(LACMINOR_VERSION)
+AC_SUBST(LACVERSION)
+
+VERSION=$LACVERSION
+#VERSION=0.1
+PACKAGE=lac
+
+AM_INIT_AUTOMAKE($PACKAGE, $VERSION, no-define)
+AM_CONFIG_HEADER(src/config.h)
+
+dnl Checks for programs.
+AC_PROG_CC
+AC_PROG_INSTALL
+AC_PROG_AWK
+AC_PROG_CPP
+AC_PROG_LN_S
+AC_PROG_MAKE_SET
+
+AM_PROG_LIBTOOL
+
+#AWK="$AWK -W lint"
+
+dnl Only use -Wall if we have gcc
+if test "x$GCC" = "xyes"; then
+ if test -z "`echo "$CFLAGS" | grep "\-Wall" 2> /dev/null`" ; then
+ CFLAGS="$CFLAGS -Wall"
+ fi
+ if test -z "`echo "$CFLAGS" | grep "\-Wmissing\-declarations" 2> /dev/null`"; then
+ CFLAGS="$CFLAGS -Wmissing-declarations"
+ fi
+fi
+
+#LIBS="$LIBS `glib-config --libs`"
+CFLAGS="$CFLAGS -D_REENTRANT"
+
+AC_CHECK_FUNC(connect,,AC_CHECK_LIB(socket, connect))
+AC_CHECK_FUNC(inet_aton,,AC_CHECK_LIB(resolv, inet_aton))
+AC_CHECK_FUNC(gethostbyname,,AC_CHECK_LIB(nsl, gethostbyname))
+AC_CHECK_FUNC(hstrerror, AC_DEFINE(HAVE_HSTRERROR, 1,
+ [Define if we have hstrerror]))
+
+PKG_CHECK_MODULES(MODULES_XML, libxml-2.0 >= 2.0.8)
+AC_SUBST(MODULES_XML_CFLAGS)
+AC_SUBST(MODULES_XML_LIBS)
+
+# ****************************************
+# Checks for library functions.
+
+# Check if we have gethostbyname_r (we assume that if the user has
+# gethostbyname_r, he also has a similar gethostbyaddr_r).
+
+AC_CHECK_FUNC(gethostbyname_r,
+ [
+ AC_DEFINE(HAVE_GETHOSTBYNAME_R, 1,
+ [Define if we have gethostbyname_r])
+ dnl if (gethostbyname with six args.)
+ dnl ================================
+ AC_MSG_CHECKING(for gethostbyname_r() with six arguments)
+ AC_TRY_LINK([
+ #include <netdb.h>],[
+ struct hostent result_buf;
+ char buf[1024];
+ struct hostent* result;
+ int h_errnop;
+
+ gethostbyname_r("localhost", &result_buf, buf, sizeof(buf),
+ &result, &h_errnop);
+ ],
+ dnl then
+ dnl ====
+ [
+
+ dnl Have glibc gethostbyname_r
+
+ AC_MSG_RESULT(yes)
+ AC_DEFINE(HAVE_GETHOSTBYNAME_R_SIX_ARGS, 1,
+ [Define if gethostbyname_r() takes six arguments])
+ HAVE_GETHOSTBYNAME_R=yes
+
+ ],
+ dnl else if (gethostbyname with five args)
+ dnl ======================================
+ [
+ AC_MSG_RESULT(no)
+ AC_MSG_CHECKING(for gethostbyname_r() with five arguments)
+ AC_TRY_LINK([
+ #include <netdb.h>],[
+ struct hostent result;
+ char buf[1024];
+ int h_errnop;
+
+ gethostbyname_r("localhost", &result, buf, sizeof(buf), &h_errnop);
+
+ ],
+ dnl then
+ dnl ====
+ [
+ AC_MSG_RESULT(yes)
+ AC_DEFINE(HAVE_GETHOSTBYNAME_R_FIVE_ARGS, 1,
+ [Define if gethostbyname_r() takes five arguments])
+ HAVE_GETHOSTBYNAME_R=yes
+
+ ],
+ dnl else if (gethostbyname with three args.)
+ dnl ========================================
+ [
+ AC_MSG_RESULT(no)
+ AC_MSG_CHECKING(for gethostbyname_r() with three arguments)
+ AC_TRY_LINK([ #include <netdb.h>],[
+ struct hostent result;
+ char buf[1024];
+ gethostbyname_r("localhost", &result, buf);
+ ],
+ dnl then
+ dnl ====
+ [
+ AC_MSG_RESULT(yes)
+ AC_DEFINE(HAVE_GETHOSTBYNAME_R_THREE_ARGS, 1,
+ [Define if gethostbyname_r() takes three arguments])
+ HAVE_GETHOSTBYNAME_R=yes
+ ],
+ dnl else
+ dnl ====
+ [
+ AC_MSG_RESULT(no)
+ AC_MSG_WARN(Emulating gethostbyname_r() with gethostbyname() and a mutex)
+ ]
+ )]
+ )]
+)],
+[
+ AC_MSG_WARN(Emulating gethostbyname_r() with gethostbyname() and a mutex)
+])
+
+dnl Checks for libraries.
+AM_PATH_GLIB_2_0(2.0.0,
+ [LIBS="$LIBS $GLIB_LIBS" CFLAGS="$CFLAGS $GLIB_CFLAGS"],
+ AC_MSG_ERROR([
+*** GLIB 1.3.1 or better is required. The latest version of GLIB
+*** is always available from ftp://ftp.gtk.org/.]),
+)
+
+dnl Checks for header files.
+AC_HEADER_STDC
+AC_CHECK_HEADERS(strings.h)
+AC_CHECK_HEADERS(sys/types.h)
+AC_CHECK_HEADERS(sys/socket.h)
+AC_CHECK_HEADERS(signal.h)
+AC_CHECK_HEADERS(netinet/in.h)
+AC_CHECK_HEADERS(netdb.h)
+AC_CHECK_HEADERS(arpa/inet.h)
+AC_CHECK_HEADERS(signal.h)
+AC_CHECK_HEADERS(unistd.h)
+AC_CHECK_HEADERS(sys/utsname.h)
+AC_CHECK_HEADERS(sys/time.h)
+AC_CHECK_HEADERS(netinet/tcp.h)
+AC_CHECK_HEADERS(fcntl.h)
+
+dnl Checks for typedefs, structures, and compiler characteristics.
+
+dnl inline
+AC_C_INLINE
+
+dnl Checks for library functions.
+
+AC_OUTPUT([
+Makefile
+src/Makefile
+tests/Makefile
+lac-1.0.pc
+])
diff --git a/lac-1.0.pc.in b/lac-1.0.pc.in
new file mode 100644
index 0000000..d1550ce
--- /dev/null
+++ b/lac-1.0.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: lac
+Description: Library for asynchronous communication
+Version: @VERSION@
+Libs: -L${libdir} -llac-1
+Requires: glib-2.0
+Cflags: -I${includedir}/lac-1.0
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..f5f7cf2
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,39 @@
+## Process this file with automake to produce Makefile.in
+
+lib_LTLIBRARIES = liblac-1.la
+
+lacincludedir=$(includedir)/lac-1.0
+
+lacinclude_HEADERS= \
+ lac.h
+
+liblac_1_la_SOURCES = \
+ lacprimitives.c \
+ lacwatch.c \
+ lacdebug.c \
+ lacdns-config.c \
+ lacdns-query.c \
+ lacdns-messages.c \
+ lacdns-cache.c \
+ lacdns-nameserver.c \
+ lacactivity.c \
+ lacaddress.c \
+ lacuri.c \
+ lacconnection.c \
+ lachttp.c \
+ \
+ lacinternals.h \
+ lacdns-messages.h \
+ lacdns-cache.h \
+ lacdns-nameserver.h \
+ lacdns-query.h \
+ lacdns-config.h \
+ \
+ lac.h
+
+include_HEADERS = \
+ lac.h
+
+INCLUDES = \
+ -DG_LOG_DOMAIN=\"Lac\" \
+ -DG_DISABLE_DEPRECATED
diff --git a/src/copyrightheader b/src/copyrightheader
new file mode 100644
index 0000000..f4536dc
--- /dev/null
+++ b/src/copyrightheader
@@ -0,0 +1,20 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- */
+
+/* Lac - Library for asynchronous communication
+ * Copyright (C) 2002 Søren Sandmann (sandmann@daimi.au.dk)
+ *
+ * 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.
+ */
diff --git a/src/lac.h b/src/lac.h
new file mode 100644
index 0000000..08aaba2
--- /dev/null
+++ b/src/lac.h
@@ -0,0 +1,505 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- */
+
+/* Lac - Library for asynchronous communication
+ * Copyright (C) 2000, 2001, 2002, 2003 Søren Sandmann (sandmann@daimi.au.dk)
+ *
+ * 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 LAC_H
+#define LAC_H
+
+#include <glib.h>
+#include <sys/types.h>
+
+G_BEGIN_DECLS
+
+/*
+ * Activity
+ */
+typedef struct LacActivityData *LacActivity;
+
+#if 0
+LacActivity *lac_activity_new (void);
+LacActivity *lac_activity_ref (LacActivity *activity);
+void lac_activity_unref (LacActivity *activity);
+void lac_activity_set_timeout (LacActivity *activity,
+ gint timeout);
+gint lac_activity_get_timeout (LacActivity *activity);
+gboolean lac_activity_wait (LacActivity *activity);
+void lac_activity_continue (LacActivity *activity);
+void lac_activity_stop (LacActivity *activity);
+
+gboolean lac_activity_running (LacActivity *activity);
+gboolean lac_activity_timed_out (LacActivity *activity);
+gboolean lac_activity_canceled (LacActivity *activity);
+#endif
+void lac_activity_cancel (LacActivity activity);
+
+/*
+ * Addresses
+ */
+#define LAC_DNS_ERROR lac_dns_error_quark ()
+
+GQuark lac_dns_error_quark (void);
+
+typedef enum
+{
+ LAC_DNS_ERROR_NO_SUCH_NAME,
+ LAC_DNS_ERROR_NO_DATA,
+ LAC_DNS_ERROR_CNAME_CYCLE,
+ LAC_DNS_ERROR_TIMEOUT,
+ LAC_DNS_ERROR_SERVER_FAILED,
+ LAC_DNS_ERROR_NO_SERVERS_CONFIGURED,
+ LAC_DNS_ERROR_MALFORMED_NAME,
+
+ LAC_DNS_ERROR_FAILED
+} LacDnsError;
+
+typedef struct LacAddress LacAddress;
+
+typedef void (* LacAddressFunc) (const LacAddress *address,
+ gpointer data,
+ const GError *err);
+typedef void (* LacAddressesFunc) (const GPtrArray *addresses,
+ gpointer data,
+ const GError *err);
+typedef void (* LacNameFunc) (const gchar *name,
+ gpointer data,
+ const GError *err);
+
+LacAddress *lac_address_new_from_a_b_c_d (gchar a,
+ gchar b,
+ gchar c,
+ gchar d);
+LacAddress *lac_address_new_from_string (const gchar *str);
+LacActivity lac_address_new_lookup_from_name (const gchar *name,
+ LacAddressFunc f,
+ gpointer data);
+LacActivity lac_address_new_lookup_all_from_name (const gchar *name,
+ LacAddressesFunc f,
+ gpointer data);
+LacAddress *lac_address_new_from_name_wait (const gchar *name,
+ LacActivity *activity,
+ GError **err);
+LacActivity lac_address_new_lookup_from_localhost (LacAddressFunc f,
+ gpointer data);
+LacAddress *lac_address_new_from_localhost_wait (LacActivity *activity,
+ GError **err);
+LacActivity lac_address_lookup_name (const LacAddress *addr,
+ LacNameFunc f,
+ gpointer data);
+gchar * lac_address_lookup_name_wait (const LacAddress *addr,
+ LacActivity *activity,
+ GError **err);
+gchar * lac_address_to_string (const LacAddress *addr);
+guint lac_address_hash (gconstpointer addr);
+gint lac_address_compare (gconstpointer addr1,
+ gconstpointer addr2);
+gboolean lac_address_equal (gconstpointer addr1,
+ gconstpointer addr2);
+LacAddress *lac_address_copy (const LacAddress *addr);
+void lac_address_free (LacAddress *addr);
+void lac_address_get_a_b_c_d (const LacAddress *addr,
+ gint *a,
+ gint *b,
+ gint *c,
+ gint *d);
+
+/*
+ * Primitives
+ */
+
+#define LAC_SOCKET_ERROR lac_socket_error_quark ()
+
+typedef enum
+{
+ LAC_SOCKET_ERROR_IN_PROGRESS,
+ LAC_SOCKET_ERROR_CONNECTION_REFUSED,
+ LAC_SOCKET_ERROR_NET_UNREACHABLE,
+ LAC_SOCKET_ERROR_TIMEOUT,
+ LAC_SOCKET_ERROR_CONNECTION_RESET,
+
+ LAC_SOCKET_ERROR_NO_RESOURCES,
+ LAC_SOCKET_ERROR_AGAIN, /* also known as WOULDBLOCK */
+
+ LAC_SOCKET_ERROR_FAILED
+} LacError;
+
+GQuark lac_socket_error_quark (void);
+
+/* General primitives */
+typedef enum {
+ LAC_SHUTDOWN_READ = 0,
+ LAC_SHUTDOWN_WRITE = 1,
+ LAC_SHUTDOWN_NEITHER = 2
+} LacShutdownMethod;
+
+void lac_sigpipe_ignore (void);
+void lac_sigpipe_default (void);
+gboolean lac_close (gint fd,
+ GError **err);
+gboolean lac_shutdown (gint fd,
+ LacShutdownMethod how,
+ GError **err);
+gboolean lac_set_blocking (gint fd,
+ gboolean blocking,
+ GError **err);
+gboolean lac_listen (gint fd,
+ guint backlog,
+ GError **err);
+gint lac_send (gint fd,
+ const gchar *msg,
+ guint len,
+ GError **err);
+gint lac_recv (gint fd,
+ gchar *buf,
+ guint len,
+ GError **err);
+gint lac_sendto (gint fd,
+ gchar *buf,
+ guint len,
+ const LacAddress *address,
+ gint port,
+ GError **err);
+gint lac_recvfrom (gint fd,
+ gchar *buf,
+ guint len,
+ LacAddress **address,
+ gint *port,
+ GError **err);
+gint lac_socket_tcp (GError **err);
+gint lac_socket_udp (GError **err);
+gboolean lac_set_nagle (gint fd,
+ gboolean use_nagle,
+ GError **err);
+gboolean lac_bind (gint fd,
+ const LacAddress *address,
+ gint port,
+ GError **err);
+gint lac_accept (gint fd,
+ LacAddress **addr,
+ gint *port,
+ GError **err);
+gboolean lac_connect (gint fd,
+ const LacAddress *address,
+ gint port,
+ GError **err);
+gboolean lac_getpeername (gint fd,
+ LacAddress **addr,
+ gint *port,
+ GError **err);
+gchar * lac_gethostname (void);
+
+
+/*
+ * Watching file descriptors
+ */
+typedef void (* LacWatchCallback) (gpointer data);
+
+void lac_fd_add_watch (gint fd,
+ gpointer data);
+void lac_fd_set_read_callback (gint fd,
+ LacWatchCallback read_cb);
+void lac_fd_set_write_callback (gint fd,
+ LacWatchCallback write_cb);
+void lac_fd_set_hangup_callback (gint fd,
+ LacWatchCallback hangup_cb);
+void lac_fd_set_error_callback (gint fd,
+ LacWatchCallback error_cb);
+void lac_fd_set_priority_callback (gint fd,
+ LacWatchCallback priority_cb);
+void lac_fd_remove_watch (gint fd);
+gboolean lac_fd_is_watched (gint fd);
+
+/*
+ * Connection
+ */
+typedef struct _LacConnection LacConnection;
+typedef struct _LacConnectionOptions LacConnectionOptions;
+typedef union _LacConnectionEvent LacConnectionEvent;
+
+typedef enum {
+ LAC_CONNECTION_EVENT_CONNECT, /* connect successful */
+ LAC_CONNECTION_EVENT_READ, /* data to read */
+ LAC_CONNECTION_EVENT_CLOSE, /* Connection closed */
+ LAC_CONNECTION_EVENT_ERROR, /* error */
+} LacConnectionEventType;
+
+typedef struct {
+ LacConnectionEventType type;
+} LacConnectionConnectEvent;
+
+typedef struct {
+ LacConnectionEventType type;
+ gpointer _lac_reserved0;
+ gpointer _lac_reserved1;
+ const guint8 *data;
+ guint len;
+} LacConnectionReadEvent;
+
+typedef struct {
+ LacConnectionEventType type;
+ gboolean remote_closed; /* true: remote closed.
+ false: local closed */
+} LacConnectionCloseEvent;
+
+typedef struct {
+ LacConnectionEventType type;
+ const GError *err;
+} LacConnectionErrorEvent;
+
+union _LacConnectionEvent {
+ LacConnectionEventType type;
+ LacConnectionConnectEvent connect;
+ LacConnectionReadEvent read;
+ LacConnectionCloseEvent close;
+ LacConnectionErrorEvent error;
+};
+
+typedef void (* LacConnectionFunc) (LacConnection *connection,
+ const LacConnectionEvent *event);
+
+LacConnection * lac_connection_new (const LacAddress *address,
+ gint port,
+ LacConnectionFunc callback,
+ gpointer data);
+gpointer lac_connection_get_data (LacConnection *connection);
+void lac_connection_write (LacConnection *connection,
+ const guint8 *data,
+ guint len);
+void lac_connection_write_cstr (LacConnection *connection,
+ const gchar *data);
+void lac_connection_shutdown_write (LacConnection *connection);
+LacConnection * lac_connection_ref (LacConnection *connection);
+void lac_connection_unref (LacConnection *connection);
+G_CONST_RETURN LacAddress *lac_connection_get_address (LacConnection *connection);
+gint lac_connection_get_port (LacConnection *connection);
+void lac_connection_close (LacConnection *connection);
+gboolean lac_connection_is_connected (LacConnection *connection);
+void lac_connection_flush (LacConnection *connection);
+
+
+/*
+ * URI
+ */
+typedef enum {
+ LAC_SCHEME_UNKNOWN,
+ LAC_SCHEME_HTTP,
+ LAC_SCHEME_FTP,
+} LacScheme;
+
+typedef enum {
+ LAC_FTP_TYPE_A,
+ LAC_FTP_TYPE_I,
+ LAC_FTP_TYPE_D,
+ LAC_FTP_TYPE_NONE,
+} LacFtpType;
+
+typedef struct _LacUri LacUri;
+struct _LacUri {
+ /* all fields are *read-only* */
+ LacScheme scheme;
+ union {
+ struct {
+ gchar *scheme;
+ gchar *authority;
+ gchar *path;
+ gchar *query;
+ } unknown;
+ struct {
+ gchar *host;
+ guint port;
+ gchar *path;
+ gchar *query;
+ } http;
+ struct {
+ gchar *host;
+ gchar *username;
+ gchar *password;
+ guint port;
+ gchar *path;
+ LacFtpType type;
+ } ftp;
+ } u;
+ gchar *fragment;
+
+ guint checksum; /* used internally */
+};
+
+LacUri * lac_uri_new_from_str (const LacUri *base, const gchar *str);
+LacUri * lac_uri_copy (const LacUri *uri);
+gchar * lac_uri_string (const LacUri *uri);
+void lac_uri_free (LacUri *uri);
+gboolean lac_uri_equal (const LacUri *uri1,
+ const LacUri *uri2);
+
+/*
+ *
+ gchar *lac_uri_get_scheme (const gchar *uri)
+ lac_uri_cd_up
+ */
+
+
+/*
+ * HTTP
+ */
+
+GQuark lac_http_error_quark (void);
+
+#define LAC_HTTP_ERROR lac_http_error_quark()
+
+typedef enum {
+ LAC_HTTP_ERROR_MALFORMED_RESPONSE, /* server sent an unparsable message */
+ LAC_HTTP_ERROR_PREMATURE_CLOSE, /* server closed connection before
+ * sending a complete response
+ */
+} LacHttpError;
+
+typedef struct _LacHttpRequest LacHttpRequest;
+typedef union _LacHttpEvent LacHttpEvent;
+
+typedef void (* LacHttpFunc) (LacHttpRequest *request,
+ const LacHttpEvent *event);
+
+LacHttpRequest *lac_http_request_new_get (const LacUri *uri,
+ LacHttpFunc func,
+ gpointer data);
+LacHttpRequest *lac_http_request_new_head (const LacUri *uri,
+ LacHttpFunc func,
+ gpointer data);
+LacHttpRequest *lac_http_request_new_post (const LacUri *uri,
+ LacHttpFunc func,
+ gpointer data);
+LacHttpRequest *lac_http_request_new_put (const LacUri *uri,
+ LacHttpFunc func,
+ gpointer data);
+LacHttpRequest *lac_http_request_new_delete (const LacUri *uri,
+ LacHttpFunc func,
+ gpointer data);
+void lac_http_request_add_content (LacHttpRequest *request,
+ const guint8 *content,
+ gsize length);
+void lac_http_request_add_header (LacHttpRequest *request,
+ const gchar *header,
+ const gchar *value);
+gpointer lac_http_request_get_data (LacHttpRequest *request);
+void lac_http_request_dispatch (LacHttpRequest *request);
+void lac_http_request_cancel (LacHttpRequest *request);
+LacHttpRequest *lac_http_request_ref (LacHttpRequest *request);
+void lac_http_request_unref (LacHttpRequest *request);
+
+typedef enum {
+ LAC_HTTP_EVENT_HOST_FOUND, /* dns lookup done */
+ LAC_HTTP_EVENT_CONNECTING, /* waiting for connect() */
+ LAC_HTTP_EVENT_SENT, /* request was sent (not pipelined) */
+ LAC_HTTP_EVENT_STATUS_LINE, /* received status line */
+ LAC_HTTP_EVENT_NO_STATUS_LINE, /* received HTTP/0.9 response */
+ LAC_HTTP_EVENT_HEADER, /* received header */
+ LAC_HTTP_EVENT_BEGIN_CONTENT, /* contents starts now */
+ LAC_HTTP_EVENT_CONTENT, /* a chunk of content */
+ LAC_HTTP_EVENT_END_CONTENT, /* end of response */
+ LAC_HTTP_EVENT_NO_CONTENT, /* response does not contain a body */
+ LAC_HTTP_EVENT_ERROR /* error */
+} LacHttpEventType;
+
+typedef struct {
+ LacHttpEventType type;
+ const LacAddress * address;
+} LacHttpHostFoundEvent;
+
+typedef struct {
+ LacHttpEventType type;
+ gint status_code;
+ gint major; /* major version number */
+ gint minor; /* minor version number */
+ const gchar * reason_phrase;
+} LacHttpStatusLineEvent;
+
+typedef struct {
+ LacHttpEventType type;
+} LacHttpNoStatusLineEvent;
+
+typedef struct {
+ LacHttpEventType type;
+ const gchar * header;
+ const gchar * value;
+} LacHttpHeaderEvent;
+
+typedef struct {
+ LacHttpEventType type;
+ gint length; /* total length of content in bytes,
+ * -1 if unknown
+ */
+} LacHttpBeginContentEvent;
+
+typedef struct {
+ LacHttpEventType type;
+ const guint8 * data;
+ gsize length;
+} LacHttpContentEvent;
+
+typedef struct {
+ LacHttpEventType type;
+ gint total;
+} LacHttpEndContentEvent;
+
+typedef struct {
+ LacHttpEventType type;
+ const GError * err;
+} LacHttpErrorEvent;
+
+union _LacHttpEvent {
+ LacHttpEventType type;
+ LacHttpHostFoundEvent host_found;
+ LacHttpStatusLineEvent status_line;
+ LacHttpNoStatusLineEvent no_status_line;
+ LacHttpHeaderEvent header;
+ LacHttpBeginContentEvent begin_content;
+ LacHttpContentEvent content;
+ LacHttpEndContentEvent end_content;
+ LacHttpErrorEvent error;
+};
+
+
+/*
+ * Debug spew
+ */
+#define lac_debug_out(format,args...) G_STMT_START{ \
+ char *lac_debug_temp_pointer; \
+ if (lac_is_verbose()) \
+ { \
+ lac_debug_temp_pointer = g_strdup_printf (format, args); \
+ g_log ("lac-debug", \
+ G_LOG_LEVEL_MESSAGE, \
+ "file %s: line %d (%s): %s", \
+ __FILE__, \
+ __LINE__, \
+ __PRETTY_FUNCTION__, \
+ lac_debug_temp_pointer); \
+ g_free (lac_debug_temp_pointer); \
+ } \
+} G_STMT_END
+
+/* make LAC write lots of spam to stdout */
+void lac_set_verbose (gboolean verbose);
+gboolean lac_is_verbose (void);
+
+#define lac_debug_checkpoint() lac_debug_out ("reached%s", "")
+
+G_END_DECLS
+
+#endif
diff --git a/src/lacactivity.c b/src/lacactivity.c
new file mode 100644
index 0000000..2ab5eb2
--- /dev/null
+++ b/src/lacactivity.c
@@ -0,0 +1,131 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- */
+
+/* Lac - Library for asynchronous communication
+ * Copyright (C) 2003 Søren Sandmann (sandmann@daimi.au.dk)
+ *
+ * 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.
+ */
+
+#include "lacinternals.h"
+
+typedef void (* Notify) (gpointer data);
+
+typedef struct LacActivityData LacActivityData;
+struct LacActivityData {
+ gboolean cancel_allowed;
+ gboolean canceled;
+
+ GMainLoop *main_loop;
+
+ gint ref_count;
+};
+
+static void
+lac_activity_destroy (LacActivity activity)
+{
+ if (activity->main_loop)
+ g_main_loop_unref (activity->main_loop);
+
+ g_free (activity);
+}
+
+gboolean
+lac_activity_unref (LacActivity activity)
+{
+ if (--activity->ref_count == 0)
+ {
+ lac_activity_destroy (activity);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+LacActivity
+lac_activity_ref (LacActivity activity)
+{
+ activity->ref_count++;
+ return activity;
+}
+
+void
+lac_activity_disable_cancel (LacActivity activity)
+{
+ activity->cancel_allowed = FALSE;
+}
+
+LacActivity
+lac_activity_new (void)
+{
+ LacActivity activity = g_new0 (LacActivityData, 1);
+
+ activity->cancel_allowed = TRUE;
+ activity->canceled = FALSE;
+ activity->main_loop = NULL;
+ activity->ref_count = 1;
+
+ return activity;
+}
+
+gboolean
+lac_activity_canceled (LacActivity activity)
+{
+ return activity->canceled;
+}
+
+gboolean
+lac_activity_wait (LacActivity activity)
+{
+ gboolean canceled;
+
+ activity->main_loop = g_main_loop_new (NULL, FALSE);
+
+ lac_activity_ref (activity);
+
+ g_main_loop_run (activity->main_loop);
+ canceled = lac_activity_canceled (activity);
+
+ lac_activity_unref (activity);
+
+ if (canceled)
+ return FALSE;
+
+ return TRUE;
+}
+
+void
+lac_activity_cancel (LacActivity activity)
+{
+ if (!activity->cancel_allowed)
+ {
+ g_warning ("You can't cancel an activity when its callback is running\n");
+ return;
+ }
+
+ if (activity->canceled)
+ {
+ g_warning ("Canceling an activity twice\n");
+ return;
+ }
+
+ activity->canceled = TRUE;
+
+ if (activity->main_loop &&
+ g_main_loop_is_running (activity->main_loop))
+ {
+ g_main_loop_quit (activity->main_loop);
+ }
+}
diff --git a/src/lacaddress.c b/src/lacaddress.c
new file mode 100644
index 0000000..fa040fa
--- /dev/null
+++ b/src/lacaddress.c
@@ -0,0 +1,740 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- */
+
+/* Lac - Library for asynchronous communication
+ * Copyright (C) 2000, 2001, 2002, 2003 Søren Sandmann
+ *
+ * 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.
+ */
+#include "lac.h"
+
+#include <string.h>
+
+#include "lacdns-messages.h"
+#include "lacdns-cache.h"
+#include "lacdns-nameserver.h"
+#include "lacdns-query.h"
+#include "lacdns-config.h"
+
+#include "lacinternals.h"
+
+#include <stdlib.h>
+
+#include "config.h"
+#ifdef HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif
+
+/*
+ * DNS RFC's:
+ *
+ * 1034: Domain names - concepts and facilities
+ * - standard
+ *
+ * 1035: Domain names - implementation and specification
+ * - standard
+ *
+ * 1101: DNS Encoding of Network Names and Other Types
+ * - textual representation for resource records
+ *
+ * 1183: New DNS RR Definitions
+ * - defines RR type 17-21
+ * - experimental
+ *
+ * 1637: DNS NSAP Resource Records
+ * - defines RR type 22
+ * - (Network Service Access Protocol)
+ * - experimental
+ *
+ * 1876: A Means for Expressing Location Information
+ * in the Domain Name System
+ * - defines RR type 29
+ * - longitude/altitude
+ * - experimental
+ *
+ * 1982: Serial Number Arithmetic
+ * - defines the meaning of the "serial" field in SOA
+ * records
+ * - proposed standard
+ *
+ * 1995: Incremental Zone Transfers in DNS
+ * - proposed standard
+ *
+ * 1996: A Mechanism for Prompt Notification of
+ * Zone Changes (DNS NOTIFY).
+ * - proposed standards
+ *
+ * 2136: Dynamic Updates in the Domain Name System (DNS UPDATE)
+ *
+ * 2137: Secure Domain Name System Dynamic Update
+ *
+ * 2181: Clarifications to the DNS Specification
+ * - IP packet header address usage from
+ * multi-homed servers,
+ * - TTLs in sets of records with the same name,
+ * class, and type,
+ * - correct handling of zone cuts,
+ * - three minor issues concerning SOA records and
+ * their use,
+ * - the precise definition of the Time to Live (TTL)
+ * - Use of the TC (truncated) header bit
+ * - the issue of what is an authoritative, or
+ * canonical, name,
+ * - and the issue of what makes a valid DNS label.
+ *
+ * 2308: Negative Caching
+ *
+ * 2535: Domain Name System Security Extensions
+ *
+ * 2845: Secret Key Transaction Authentication for DNS (TSIG)
+ */
+
+struct LacAddress
+{
+ struct in_addr in_address;
+};
+
+/* lac_dns_error_quark */
+GQuark
+lac_dns_error_quark (void)
+{
+ static GQuark q = 0;
+
+ if (q == 0)
+ q = g_quark_from_static_string ("lac-dns-error-quark");
+
+ return q;
+}
+
+/*
+ * lac_address_new_lookup_all_from_name ()
+ */
+typedef struct AddressesLookupInfo {
+ gchar * domain;
+ LacAddressesFunc f;
+ gpointer data;
+ LacActivity activity;
+} AddressesLookupInfo;
+
+static void
+get_addresses_callback (const DnsQueryEvent *event, gpointer data)
+{
+ GPtrArray *addresses = NULL;
+ GError *err = NULL;
+ AddressesLookupInfo *info = data;
+ const GList *list;
+
+ switch (event->type)
+ {
+ case DNS_QUERY_EVENT_RESULTS:
+ addresses = g_ptr_array_new ();
+
+ for (list = event->results.results; list; list = list->next)
+ {
+ RData *a = list->data;
+ LacAddress *addr =
+ lac_address_new_from_a_b_c_d (
+ a->u.a.a, a->u.a.b, a->u.a.c, a->u.a.d);
+
+ g_ptr_array_add (addresses, addr);
+ }
+ break;
+
+ case DNS_QUERY_EVENT_NO_SUCH_NAME:
+ err = g_error_new (
+ LAC_DNS_ERROR, LAC_DNS_ERROR_NO_SUCH_NAME,
+ "The name %s was not found", info->domain);
+ break;
+
+ case DNS_QUERY_EVENT_NO_DATA:
+ err = g_error_new (
+ LAC_DNS_ERROR, LAC_DNS_ERROR_NO_DATA,
+ "No addresses found for %s", info->domain);
+ break;
+
+ case DNS_QUERY_EVENT_ERROR:
+ err = g_error_copy (event->error.err);
+ break;
+ }
+
+ if (!lac_activity_canceled (info->activity))
+ {
+ lac_activity_disable_cancel (info->activity);
+
+ info->f (addresses, info->data, err);
+ }
+
+ lac_activity_unref (info->activity);
+ g_free (info->domain);
+ g_free (info);
+
+ if (addresses)
+ {
+ gint i;
+ for (i = 0; i < addresses->len; ++i)
+ lac_address_free (addresses->pdata[i]);
+ g_ptr_array_free (addresses, TRUE);
+ }
+
+ if (err)
+ g_error_free (err);
+}
+
+LacActivity
+lac_address_new_lookup_all_from_name (const gchar *name,
+ LacAddressesFunc f,
+ gpointer data)
+{
+ AddressesLookupInfo *info;
+ LacAddress *address;
+ GError *err = NULL;
+ LacActivity activity;
+
+ if (!dns_config_initialize (&err))
+ {
+ f (NULL, data, err);
+
+ g_error_free (err);
+ return NULL;
+ }
+
+ /* check if name is on the "a.b.c.d" form */
+ address = lac_address_new_from_string (name);
+ if (address)
+ {
+ GPtrArray *addresses;
+
+ addresses = g_ptr_array_new ();
+ g_ptr_array_add (addresses, address);
+
+ f (addresses, data, NULL);
+
+ lac_address_free (address);
+ g_ptr_array_free (addresses, TRUE);
+
+ return NULL;
+ }
+
+ info = g_new (AddressesLookupInfo, 1);
+ info->domain = g_strdup (name);
+ info->f = f;
+ info->data = data;
+ info->activity = activity = lac_activity_new ();
+
+ lac_activity_ref (activity);
+
+ dns_query (name, A_TYPE, get_addresses_callback, info);
+
+ if (lac_activity_unref (activity))
+ return NULL;
+
+ return activity;
+}
+
+/*
+ * lac_address_lookup_from_name()
+ */
+typedef struct {
+ LacAddressFunc f;
+ gpointer data;
+ LacActivity activity;
+} GetAddressInfo;
+
+static void
+dns_addr_callback (const GPtrArray *addresses,
+ gpointer data,
+ const GError *err)
+{
+ GetAddressInfo *info = data;
+
+ if (!lac_activity_canceled (info->activity))
+ {
+ lac_activity_disable_cancel (info->activity);
+
+ if (err)
+ info->f (NULL, info->data, err);
+ else
+ info->f (addresses->pdata[0], info->data, err);
+ }
+
+ lac_activity_unref (info->activity);
+ g_free (info);
+}
+
+LacActivity
+lac_address_new_lookup_from_name (const gchar *name,
+ LacAddressFunc f,
+ gpointer data)
+{
+ GetAddressInfo *info;
+ LacActivity activity;
+
+ g_return_val_if_fail (name != NULL, NULL);
+ g_return_val_if_fail (f != NULL, NULL);
+
+ info = g_new (GetAddressInfo, 1);
+ info->f = f;
+ info->data = data;
+ info->activity = activity = lac_activity_new ();
+
+ lac_activity_ref (activity);
+
+ lac_address_new_lookup_all_from_name (name, dns_addr_callback, info);
+
+ if (lac_activity_unref (activity))
+ return NULL;
+
+ return activity;
+}
+
+/*
+ * lac_address_lookup_name()
+ */
+typedef struct NameLookupInfo {
+ LacAddress *address;
+ LacNameFunc f;
+ gpointer data;
+ LacActivity activity;
+} NameLookupInfo;
+
+static void
+get_name_callback (const DnsQueryEvent *event, gpointer data)
+{
+ NameLookupInfo *info = data;
+ gchar *name = NULL;
+ GError *err = NULL;
+ gchar *free_me = NULL;
+ RData *ptr;
+
+ switch (event->type)
+ {
+ case DNS_QUERY_EVENT_RESULTS:
+ ptr = event->results.results->data;
+ free_me = name = domain_name_to_string (ptr->u.ptr.ptr_name);
+ break;
+
+ case DNS_QUERY_EVENT_NO_SUCH_NAME:
+ free_me = lac_address_to_string (info->address);
+ err = g_error_new (
+ LAC_DNS_ERROR, LAC_DNS_ERROR_NO_SUCH_NAME,
+ "No names found for %s", free_me);
+ break;
+
+ case DNS_QUERY_EVENT_NO_DATA:
+ free_me = lac_address_to_string (info->address);
+ err = g_error_new (
+ LAC_DNS_ERROR, LAC_DNS_ERROR_NO_DATA,
+ "No names found for %s", free_me);
+ break;
+
+ case DNS_QUERY_EVENT_ERROR:
+ err = g_error_copy (event->error.err);
+ break;
+ }
+
+ if (!lac_activity_canceled (info->activity))
+ {
+ lac_activity_disable_cancel (info->activity);
+
+ info->f (name, info->data, err);
+ }
+
+ lac_address_free (info->address);
+ lac_activity_unref (info->activity);
+ g_free (info);
+
+ if (free_me)
+ g_free (free_me);
+}
+
+static gchar *
+address_to_in_addr_arpa (const LacAddress *addr)
+{
+ gint a, b, c, d;
+
+ g_return_val_if_fail (addr != NULL, NULL);
+
+ lac_address_get_a_b_c_d (addr, &a, &b, &c, &d);
+
+ return g_strdup_printf ("%d.%d.%d.%d.in-addr.arpa.", d, c, b, a);
+}
+
+LacActivity
+lac_address_lookup_name (const LacAddress *address,
+ LacNameFunc f,
+ gpointer data)
+{
+ NameLookupInfo *info;
+ gchar *domain_str;
+ GError *err = NULL;
+ LacActivity activity;
+
+ if (!dns_config_initialize (&err))
+ {
+ f (NULL, data, err);
+
+ g_error_free (err);
+ return NULL;
+ }
+
+ domain_str = address_to_in_addr_arpa (address);
+
+ info = g_new (NameLookupInfo, 1);
+ info->address = lac_address_copy (address);
+ info->f = f;
+ info->data = data;
+ info->activity = activity = lac_activity_new ();
+
+ lac_activity_ref (activity);
+
+ dns_query (domain_str, PTR_TYPE, get_name_callback, info);
+ g_free (domain_str);
+
+ if (lac_activity_unref (activity))
+ return NULL;
+
+ return activity;
+}
+
+/*
+ * "blocking" versions
+ */
+
+/*
+ * lac_address_new_from_name_wait()
+ */
+typedef struct {
+ LacAddress *result;
+ GError * err;
+} GetHostByNameInfo;
+
+static void
+ghbn_callback (const GPtrArray *addresses, gpointer data, const GError *err)
+{
+ GetHostByNameInfo *ghbn_info = data;
+
+ if (addresses)
+ {
+ ghbn_info->result = lac_address_copy (addresses->pdata[0]);
+ ghbn_info->err = NULL;
+ }
+ else
+ {
+ ghbn_info->result = NULL;
+ ghbn_info->err = g_error_copy (err);
+ }
+}
+
+LacAddress *
+lac_address_new_from_name_wait (const gchar *name,
+ LacActivity *activity_return,
+ GError **err)
+{
+ GetHostByNameInfo ghbn_info;
+ LacActivity activity;
+
+ activity = lac_address_new_lookup_all_from_name (
+ name, ghbn_callback, &ghbn_info);
+
+ if (activity_return)
+ *activity_return = activity;
+
+ if (activity && !lac_activity_wait (activity))
+ {
+ /* canceled */
+ return NULL;
+ }
+
+ if (ghbn_info.err)
+ {
+ g_propagate_error (err, ghbn_info.err);
+ return NULL;
+ }
+
+ return ghbn_info.result;
+}
+
+/*
+ * lac_address_lookup_name_wait
+ */
+typedef struct {
+ gchar * result;
+ GError * err;
+} GetHostByAddrInfo;
+
+static void
+ghba_callback (const gchar *name, gpointer data, const GError *err)
+{
+ GetHostByAddrInfo *ghba_info = data;
+
+ if (name)
+ {
+ ghba_info->result = g_strdup (name);
+ ghba_info->err = NULL;
+ }
+ else
+ {
+ ghba_info->result = NULL;
+ ghba_info->err = g_error_copy (err);
+ }
+}
+
+gchar *
+lac_address_lookup_name_wait (const LacAddress *addr,
+ LacActivity *activity_return,
+ GError **err)
+{
+ GetHostByAddrInfo ghba_info;
+ LacActivity activity;
+
+ activity = lac_address_lookup_name (addr, ghba_callback, &ghba_info);
+
+ if (activity_return)
+ *activity_return = activity;
+
+ if (activity && !lac_activity_wait (activity))
+ {
+ /* canceled */
+ return NULL;
+ }
+
+ if (ghba_info.err)
+ {
+ g_propagate_error (err, ghba_info.err);
+ return NULL;
+ }
+
+ return ghba_info.result;
+}
+
+LacAddress *
+lac_address_allocate (void)
+{
+ LacAddress *addr = g_new0 (LacAddress, 1);
+
+ return addr;
+}
+
+LacAddress *
+lac_address_copy (const LacAddress *addr)
+{
+ LacAddress *copy = lac_address_allocate ();
+ *copy = *addr;
+ return copy;
+}
+
+void
+lac_address_free (LacAddress *addr)
+{
+ g_return_if_fail (addr != NULL);
+
+ g_free (addr);
+}
+
+LacAddress *
+lac_address_new_from_a_b_c_d (gchar a,
+ gchar b,
+ gchar c,
+ gchar d)
+{
+ LacAddress *addr = lac_address_allocate ();
+ gchar *addr_bytes = (guchar *)&(addr->in_address.s_addr);
+
+ addr_bytes[0] = a;
+ addr_bytes[1] = b;
+ addr_bytes[2] = c;
+ addr_bytes[3] = d;
+
+ return addr;
+}
+
+/* FIXME: is this correct when there are more than three dots?
+ */
+LacAddress *
+lac_address_new_from_string (const gchar *str)
+{
+ LacAddress *addr = NULL;
+ gchar **strs;
+ gint numbers[4];
+ gint i;
+
+ g_return_val_if_fail (str != NULL, NULL);
+
+ strs = g_strsplit (str, ".", 4);
+
+ for (i = 0; i < 4; ++i)
+ {
+ gchar *endptr;
+
+ if (!strs[i])
+ goto err;
+ numbers[i] = strtol (strs[i], &endptr, 10);
+ if (*endptr != '\0' || endptr == strs[i])
+ goto err;
+ }
+ if (strs[4])
+ goto err;
+
+ addr = lac_address_new_from_a_b_c_d (
+ numbers[0], numbers[1], numbers[2], numbers[3]);
+
+ err:
+ g_strfreev (strs);
+
+ return addr;
+}
+
+LacActivity
+lac_address_new_lookup_from_localhost (LacAddressFunc f,
+ gpointer data)
+{
+ gchar *name;
+ LacActivity activity;
+
+ name = lac_gethostname ();
+ activity = lac_address_new_lookup_from_name (name, f, data);
+ g_free (name);
+
+ return activity;
+}
+
+typedef struct {
+ LacAddress *result;
+ GError *err;
+} NewFromLocalHostInfo;
+
+static void
+localhost_callback (const LacAddress *address, gpointer data, const GError *err)
+{
+ NewFromLocalHostInfo *info = data;
+
+ if (address)
+ {
+ info->result = lac_address_copy (address);
+ info->err = NULL;
+ }
+ else
+ {
+ info->result = NULL;
+ info->err = g_error_copy (err);
+ }
+}
+
+LacAddress *
+lac_address_new_from_localhost_wait (LacActivity *activity_return,
+ GError **err)
+{
+ NewFromLocalHostInfo info;
+ LacActivity activity;
+
+ activity =
+ lac_address_new_lookup_from_localhost (localhost_callback, &info);
+
+ if (!lac_activity_wait (activity))
+ {
+ /* canceled */
+ return NULL;
+ }
+
+ if (info.err)
+ {
+ g_propagate_error (err, info.err);
+ return NULL;
+ }
+
+ return info.result;
+}
+
+guint
+lac_address_hash (gconstpointer addr)
+{
+ return (guint)(((LacAddress *)addr)->in_address.s_addr);
+}
+
+gint
+lac_address_compare (gconstpointer addr1, gconstpointer addr2)
+{
+ const LacAddress *a1 = addr1;
+ const LacAddress *a2 = addr2;
+
+ if (a1->in_address.s_addr == a2->in_address.s_addr)
+ return 0;
+ if (a1->in_address.s_addr > a2->in_address.s_addr)
+ return 1;
+ return -1;
+}
+
+gboolean
+lac_address_equal (gconstpointer addr1, gconstpointer addr2)
+{
+ const LacAddress *a1 = addr1;
+ const LacAddress *a2 = addr2;
+
+ return a1->in_address.s_addr == a2->in_address.s_addr;
+}
+
+gchar *
+lac_address_to_string (const LacAddress *addr)
+{
+ guchar *addr_bytes;
+ struct in_addr in_addr;
+
+ g_return_val_if_fail (addr != NULL, NULL);
+
+ lac_address_get_in_addr (addr, &in_addr);
+
+ addr_bytes = (gchar *)&(in_addr.s_addr);
+
+ return g_strdup_printf ("%d.%d.%d.%d",
+ addr_bytes[0], addr_bytes[1],
+ addr_bytes[2], addr_bytes[3]);
+}
+
+void
+lac_address_get_a_b_c_d (const LacAddress *addr,
+ gint *a,
+ gint *b,
+ gint *c,
+ gint *d)
+{
+ const guchar *addr_bytes;
+
+ g_return_if_fail (addr != NULL);
+
+ addr_bytes = (const guchar *)&(addr->in_address.s_addr);
+
+ if (a)
+ *a = addr_bytes[0];
+ if (b)
+ *b = addr_bytes[1];
+ if (c)
+ *c = addr_bytes[2];
+ if (d)
+ *d = addr_bytes[3];
+}
+
+void
+lac_address_get_in_addr (const LacAddress *addr,
+ struct in_addr *in_addr)
+{
+ *in_addr = addr->in_address;
+}
+
+void
+lac_address_set_in_addr (LacAddress *addr,
+ struct in_addr *in_addr)
+{
+ addr->in_address = *in_addr;
+}
diff --git a/src/lacbytequeue.c b/src/lacbytequeue.c
new file mode 100644
index 0000000..0160e32
--- /dev/null
+++ b/src/lacbytequeue.c
@@ -0,0 +1,78 @@
+struct _LacByteQueue
+{
+ guint allocated;
+ guint8 *data;
+ guint8 *head;
+ guint8 *tail;
+};
+
+enum {
+ INITIAL_SIZE = 256
+};
+
+LacByteQueue *
+lac_byte_queue_new (void)
+{
+ LacByteQueue *byte_queue = g_new (LacByteQueue, 1);
+
+ byte_queue->allocated = INITIAL_SIZE;
+ byte_queue->data = g_new (guint8, INITIAL_SIZE);
+ byte_queue->head = &(byte_queue->data[0]);
+ byte_queue->tail = &(byte_queue->data[0]);
+
+ return byte_queue;
+}
+
+guint
+lac_byte_queue_get_length (LacByteQueue *byte_queue)
+{
+ guint8 *head = byte_queue->head;
+ guint8 *tail = byte_queue->tail;
+
+ if (head >= tail)
+ return head - tail;
+ else
+ return byte_queue->allocated - (tail - head);
+}
+
+static guint
+lac_byte_queue_get_capacity (LacByteQueue *byte_queue)
+{
+ return byte_queue->allocated - lac_byte_queue_get_length (byte_queue);
+}
+
+static void
+lac_byte_queue_ensure_capacity (LacByteQueue *byte_queue, guint capacity)
+{
+}
+
+void
+lac_byte_queue_append (LacByteQueue *byte_queue, guint8 *data, guint length)
+{
+ guint needed_capacity = lac_byte_queue_get_length (byte_queue) + length;
+
+ lac_byte_queue_ensure_capacity (byte_queue, needed_capacity);
+
+ if (byte_queue->head >= byte_queue->tail)
+ {
+ guint bytes_to_copy = MIN (
+ byte_queue->data + byte_queue->allocated - byte_queue->head,
+ length);
+
+ memcpy (byte_queue->head, data, bytes_to_copy);
+
+ byte_queue->head += bytes_to_copy;
+ length -= bytes_to_copy;
+ data += bytes_to_copy;
+
+ if (byte_queue->head == byte_queue->data + byte_queue->allocated)
+ byte_queue->head = byte_queue->data;
+ }
+
+ if (length > 0)
+ {
+ memcpy (byte_queue->head, data, length);
+
+ byte_queue->head += length;
+ }
+}
diff --git a/src/lacconnection.c b/src/lacconnection.c
new file mode 100644
index 0000000..81e42e4
--- /dev/null
+++ b/src/lacconnection.c
@@ -0,0 +1,535 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- */
+
+/* Lac - Library for asynchronous communication
+ * Copyright (C) 2000, 2001, 2002, 2003 Søren Sandmann
+ *
+ * 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.
+ */
+
+#include "lac.h"
+#include <string.h>
+
+typedef enum {
+ UNDEFINED,
+ CONNECT_IN_PROGRESS,
+ CONNECTED,
+ DISCONNECTED,
+} ConnectionState;
+
+struct _LacConnection {
+ gint fd;
+
+ LacAddress * address;
+ gint port;
+ LacConnectionFunc callback;
+ gpointer data;
+
+ GQueue * pending_events;
+
+ ConnectionState state;
+ GString * unwritten;
+
+ gboolean has_fd;
+ gboolean write_shutdown;
+ gboolean in_emit_events;
+ gboolean need_flush;
+ gint ref_count;
+};
+
+static void
+event_free (LacConnectionEvent *event)
+{
+ if (event->type == LAC_CONNECTION_EVENT_READ)
+ g_free ((gchar *)event->read.data);
+ else if (event->type == LAC_CONNECTION_EVENT_ERROR)
+ g_error_free ((GError *)event->error.err);
+
+ g_free (event);
+}
+
+static void
+queue_connect (LacConnection *connection)
+{
+ LacConnectionEvent *event = g_new (LacConnectionEvent, 1);
+
+ event->type = LAC_CONNECTION_EVENT_CONNECT;
+
+ g_queue_push_tail (connection->pending_events, event);
+}
+
+static void
+queue_read (LacConnection *connection, const guint8 *data, guint len)
+{
+ LacConnectionEvent *event = g_new (LacConnectionEvent, 1);
+
+ event->type = LAC_CONNECTION_EVENT_READ;
+ event->read.data = g_memdup (data, len);
+ event->read.len = len;
+
+ g_queue_push_tail (connection->pending_events, event);
+}
+
+static void
+queue_close (LacConnection *connection, gboolean remote)
+{
+ LacConnectionEvent *event = g_new (LacConnectionEvent, 1);
+
+ event->type = LAC_CONNECTION_EVENT_CLOSE;
+ event->close.remote_closed = remote;
+
+ g_queue_push_tail (connection->pending_events, event);
+}
+
+static void
+queue_error (LacConnection *connection, const GError *err)
+{
+ LacConnectionEvent *event = g_new (LacConnectionEvent, 1);
+
+ event->type = LAC_CONNECTION_EVENT_ERROR;
+ event->error.err = g_error_copy (err);
+
+ g_queue_push_tail (connection->pending_events, event);
+}
+
+static void
+emit_events (LacConnection *connection)
+{
+ LacConnectionEvent *event;
+
+ if (connection->in_emit_events)
+ return;
+
+ connection->in_emit_events = TRUE;
+ lac_connection_ref (connection);
+
+ while ((event = g_queue_pop_head (connection->pending_events)))
+ {
+ if (connection->state != DISCONNECTED)
+ {
+ if (event->type == LAC_CONNECTION_EVENT_CLOSE ||
+ event->type == LAC_CONNECTION_EVENT_ERROR)
+ {
+ connection->state = DISCONNECTED;
+
+ if (connection->has_fd)
+ {
+ lac_fd_remove_watch (connection->fd);
+ lac_close (connection->fd, NULL);
+ connection->has_fd = FALSE;
+ }
+ }
+
+ connection->callback (connection, event);
+ }
+
+ event_free (event);
+ }
+
+ connection->in_emit_events = FALSE;
+ lac_connection_unref (connection);
+}
+
+static gboolean
+emit_events_idle_handler (gpointer data)
+{
+ LacConnection *connection = data;
+ emit_events (connection);
+
+ lac_connection_unref (connection);
+
+ return FALSE; /* don't call me again */
+}
+
+static void
+emit_events_idle (LacConnection *connection)
+{
+ if (!g_queue_is_empty (connection->pending_events))
+ {
+ g_idle_add (
+ emit_events_idle_handler, lac_connection_ref (connection));
+ }
+}
+
+static void
+lac_connection_do_reads (gpointer data)
+{
+ LacConnection *connection = data;
+ gint len;
+
+ enum { BUF_SIZE = 8280 };
+
+ do
+ {
+ guint8 buf [BUF_SIZE];
+ GError *err = NULL;
+
+ len = lac_recv (connection->fd, buf, BUF_SIZE, &err);
+ if (len < 0)
+ {
+ g_assert (err);
+
+ if (!g_error_matches (
+ err, LAC_SOCKET_ERROR, LAC_SOCKET_ERROR_AGAIN))
+ {
+ queue_error (connection, err);
+ }
+
+ g_error_free (err);
+ }
+ else if (len == 0)
+ {
+ queue_close (connection, TRUE);
+ }
+ else
+ {
+ queue_read (connection, buf, len);
+ }
+
+ /* FIXME: check that we haven't used too much time? */
+ }
+ while (len == BUF_SIZE);
+}
+
+static void lac_connection_writable (gpointer data);
+
+static void
+lac_connection_do_writes (LacConnection *connection)
+{
+ if (connection->state != CONNECTED)
+ {
+ lac_fd_set_write_callback (connection->fd, lac_connection_writable);
+ return;
+ }
+
+ while (connection->unwritten->len > 0)
+ {
+ GError *err = NULL;
+
+ gsize sent = lac_send (
+ connection->fd,
+ connection->unwritten->str, connection->unwritten->len, &err);
+
+ if (err)
+ {
+ if (g_error_matches (
+ err, LAC_SOCKET_ERROR, LAC_SOCKET_ERROR_AGAIN))
+ {
+ lac_fd_set_write_callback (
+ connection->fd, lac_connection_writable);
+ }
+ else
+ {
+ queue_error (connection, err);
+ }
+
+ g_error_free (err);
+ return;
+ }
+
+ g_string_erase (connection->unwritten, 0, sent);
+
+ /* FIXME check that we haven't used too much time? */
+ }
+
+ lac_fd_set_write_callback (connection->fd, NULL);
+
+ if (connection->need_flush)
+ {
+ /* Turn Nagle off, then on to make the kernel send everything it
+ * has queued up.
+ */
+
+ lac_set_nagle (connection->fd, FALSE, NULL);
+ lac_set_nagle (connection->fd, TRUE, NULL);
+
+ connection->need_flush = FALSE;
+ }
+
+ if (connection->write_shutdown)
+ {
+ GError *err = NULL;
+
+ lac_shutdown (connection->fd, LAC_SHUTDOWN_WRITE, &err);
+ if (err)
+ {
+ queue_error (connection, err);
+ g_error_free (err);
+ }
+ }
+}
+
+static void
+lac_connection_readable (gpointer data)
+{
+ LacConnection *connection = data;
+
+ lac_connection_do_reads (connection);
+ emit_events (connection);
+}
+
+static void
+lac_connection_writable (gpointer data)
+{
+ LacConnection *connection = data;
+
+ if (connection->state == CONNECT_IN_PROGRESS)
+ {
+ connection->state = CONNECTED;
+ queue_connect (connection);
+ }
+
+ lac_connection_do_writes (connection);
+ emit_events (connection);
+}
+
+static void
+lac_connection_connect (LacConnection *connection)
+{
+ GError *err = NULL;
+
+ lac_connect (connection->fd,
+ connection->address, connection->port,
+ &err);
+
+ if (err)
+ {
+ if (g_error_matches (
+ err, LAC_SOCKET_ERROR, LAC_SOCKET_ERROR_IN_PROGRESS))
+ {
+ connection->state = CONNECT_IN_PROGRESS;
+ }
+ else
+ {
+ queue_error (connection, err);
+ }
+
+ g_error_free (err);
+ }
+ else
+ {
+ connection->state = CONNECTED;
+ queue_connect (connection);
+ lac_connection_do_writes (connection);
+ }
+}
+
+static void
+lac_connection_discard_pending_events (LacConnection *connection)
+{
+ LacConnectionEvent *event;
+
+ while ((event = g_queue_pop_head (connection->pending_events)))
+ event_free (event);
+}
+
+static void
+lac_connection_add_watch (LacConnection *connection)
+{
+ lac_fd_add_watch (connection->fd, connection);
+
+ lac_fd_set_read_callback (connection->fd, lac_connection_readable);
+ lac_fd_set_hangup_callback (connection->fd, lac_connection_readable);
+ lac_fd_set_error_callback (connection->fd, lac_connection_readable);
+ lac_fd_set_write_callback (connection->fd, lac_connection_writable);
+}
+
+LacConnection *
+lac_connection_new (const LacAddress *address,
+ gint port,
+ LacConnectionFunc callback,
+ gpointer data)
+{
+ LacConnection *connection;
+ GError *err = NULL;
+
+ g_return_val_if_fail (address != NULL, NULL);
+ g_return_val_if_fail (port > 0, NULL);
+ g_return_val_if_fail (callback != NULL, NULL);
+
+ connection = g_new (LacConnection, 1);
+
+ connection->ref_count = 1;
+
+ connection->address = lac_address_copy (address);
+ connection->port = port;
+ connection->callback = callback;
+ connection->data = data;
+
+ connection->pending_events = g_queue_new ();
+
+ connection->state = UNDEFINED;
+ connection->has_fd = FALSE;
+ connection->unwritten = g_string_new ("");
+
+ connection->write_shutdown = FALSE;
+ connection->in_emit_events = FALSE;
+ connection->need_flush = FALSE;
+
+ connection->fd = lac_socket_tcp (&err);
+ if (err)
+ {
+ queue_error (connection, err);
+ emit_events_idle (connection);
+ g_error_free (err);
+ return connection;
+ }
+
+ connection->has_fd = TRUE;
+
+ lac_set_blocking (connection->fd, FALSE, &err);
+ if (err)
+ {
+ queue_error (connection, err);
+ emit_events_idle (connection);
+ g_error_free (err);
+ return connection;
+ }
+
+ lac_connection_add_watch (connection);
+ lac_connection_connect (connection);
+
+ emit_events_idle (connection);
+
+ return connection;
+}
+
+gpointer
+lac_connection_get_data (LacConnection *connection)
+{
+ g_return_val_if_fail (connection != NULL, NULL);
+
+ return connection->data;
+}
+
+void
+lac_connection_write (LacConnection *connection,
+ const guint8 *data,
+ guint len)
+{
+ gboolean do_writes;
+
+ g_return_if_fail (connection != NULL);
+ g_return_if_fail (data != NULL);
+ g_return_if_fail (!connection->write_shutdown);
+ g_return_if_fail (connection->state != DISCONNECTED);
+
+ if (len == 0)
+ return;
+
+ do_writes = (connection->unwritten->len == 0);
+
+ g_string_append_len (connection->unwritten, data, len);
+
+ if (do_writes)
+ {
+ lac_connection_do_writes (connection);
+ emit_events (connection);
+ }
+}
+void
+lac_connection_write_cstr (LacConnection *connection,
+ const gchar *data)
+{
+ guint len;
+
+ g_return_if_fail (connection != NULL);
+ g_return_if_fail (data != NULL);
+ g_return_if_fail (!connection->write_shutdown);
+
+ len = strlen (data);
+
+ if (len > 0)
+ lac_connection_write (connection, (const guint8 *)data, len);
+}
+
+void
+lac_connection_shutdown_write (LacConnection *connection)
+{
+ g_return_if_fail (connection != NULL);
+
+ connection->write_shutdown = TRUE;
+
+ lac_connection_do_writes (connection);
+ emit_events (connection);
+}
+
+void
+lac_connection_close (LacConnection *connection)
+{
+ g_return_if_fail (connection != NULL);
+
+ lac_connection_discard_pending_events (connection);
+
+ queue_close (connection, FALSE);
+ emit_events (connection);
+}
+
+LacConnection *
+lac_connection_ref (LacConnection *connection)
+{
+ ++connection->ref_count;
+
+ return connection;
+}
+
+void
+lac_connection_unref (LacConnection *connection)
+{
+ if (--connection->ref_count == 0)
+ {
+ lac_address_free (connection->address);
+
+ lac_connection_discard_pending_events (connection);
+ g_queue_free (connection->pending_events);
+
+ g_string_free (connection->unwritten, TRUE);
+
+ if (connection->has_fd)
+ {
+ lac_fd_remove_watch (connection->fd);
+ lac_close (connection->fd, NULL);
+ }
+
+ g_free (connection);
+ }
+}
+
+G_CONST_RETURN LacAddress *
+lac_connection_get_address (LacConnection *connection)
+{
+ return connection->address;
+}
+
+gint
+lac_connection_get_port (LacConnection *connection)
+{
+ return connection->port;
+}
+
+gboolean
+lac_connection_is_connected (LacConnection *connection)
+{
+ return connection->state == CONNECTED;
+}
+
+void
+lac_connection_flush (LacConnection *connection)
+{
+ connection->need_flush = TRUE;
+
+ if (connection->unwritten->len == 0)
+ lac_connection_do_writes (connection);
+}
diff --git a/src/lacdebug.c b/src/lacdebug.c
new file mode 100644
index 0000000..94ce8b0
--- /dev/null
+++ b/src/lacdebug.c
@@ -0,0 +1,36 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- */
+
+/* Lac - Library for asynchronous communication
+ * Copyright (C) 2000, 2001 Søren Sandmann
+ *
+ * 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.
+ */
+
+#include "lac.h"
+
+static gboolean lac_verbose = FALSE;
+
+void
+lac_set_verbose (gboolean verbose)
+{
+ lac_verbose = !!verbose;
+}
+
+gboolean
+lac_is_verbose (void)
+{
+ return lac_verbose;
+}
diff --git a/src/lacdns-cache.c b/src/lacdns-cache.c
new file mode 100644
index 0000000..6588d80
--- /dev/null
+++ b/src/lacdns-cache.c
@@ -0,0 +1,617 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- */
+
+/* Lac - Library for asynchronous communication
+ * Copyright (C) 2002 Søren Sandmann (sandmann@daimi.au.dk)
+ *
+ * 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.
+ */
+
+#include "lacdns-cache.h"
+
+enum {
+ MAX_NEGATIVE_CACHING = 3600, /* max seconds to cache negative answers */
+};
+
+typedef struct CacheEntry CacheEntry;
+typedef struct CacheRecord CacheRecord;
+
+/* CacheEntry */
+typedef enum {
+ CACHE_ENTRY_DATA,
+ CACHE_ENTRY_NO_SUCH_NAME,
+} CacheEntryKind;
+
+struct CacheEntry {
+ CacheEntryKind kind;
+ DomainName * domain_name;
+
+ union {
+ struct {
+ GList * cache_records;
+ } data;
+ struct {
+ guint time_to_die;
+ } no_such_name;
+ } u;
+};
+
+static CacheEntry *cache_entry_new_data (const DomainName *name);
+static CacheEntry *cache_entry_new_no_such_name (const DomainName *name,
+ guint time_to_live);
+static void cache_entry_free (CacheEntry *entry);
+
+
+/* CacheRecord */
+typedef enum {
+ CACHE_RECORD_DATA,
+ CACHE_RECORD_NO_DATA,
+} CacheRecordKind;
+
+struct CacheRecord {
+ CacheRecordKind kind : 8;
+ QueryRRType type : 8;
+ guint time_to_die;
+ RData * rdata;
+};
+
+static CacheRecord *cache_record_new_no_data (RRType type,
+ guint time_to_live);
+static CacheRecord *cache_record_new_data (RRType type,
+ guint time_to_live,
+ const RData *rdata);
+static void cache_record_free (CacheRecord *record);
+
+static GHashTable *cache = NULL;
+
+
+static void
+cache_init (void)
+{
+ if (!cache)
+ {
+ cache = g_hash_table_new ((GHashFunc)domain_name_hash,
+ (GEqualFunc)domain_name_equal);
+ }
+}
+
+static guint
+now()
+{
+ static GTimer *timer = NULL;
+
+ if (!timer)
+ timer = g_timer_new ();
+
+ return (guint)g_timer_elapsed (timer, NULL);
+}
+
+static void
+cache_delete_entry (const DomainName *name)
+{
+ CacheEntry *entry;
+
+ entry = g_hash_table_lookup (cache, name);
+ if (entry)
+ {
+ g_hash_table_remove (cache, entry->domain_name);
+ cache_entry_free (entry);
+ }
+}
+
+static CacheEntry *
+cache_find_entry (const DomainName *name)
+{
+ CacheEntry *entry = g_hash_table_lookup (cache, name);
+
+ if (entry &&
+ entry->kind == CACHE_ENTRY_NO_SUCH_NAME &&
+ entry->u.no_such_name.time_to_die <= now())
+ {
+ cache_delete_entry (name);
+ return NULL;
+ }
+
+ return entry;
+}
+
+static GList *
+cache_entry_get_records_of_type (CacheEntry *entry, RRType type)
+{
+ GList *list;
+ GList *retval = NULL;
+ guint current_time = now();
+
+ if (entry->kind == CACHE_ENTRY_NO_SUCH_NAME)
+ return NULL;
+
+ list = entry->u.data.cache_records;
+ while (list != NULL)
+ {
+ GList *next = list->next;
+ CacheRecord *record = list->data;
+
+ if (record->time_to_die <= current_time)
+ {
+ cache_record_free (record);
+ entry->u.data.cache_records =
+ g_list_delete_link (entry->u.data.cache_records, list);
+ }
+ else
+ {
+ if (record->type == type)
+ retval = g_list_prepend (retval, record);
+ }
+ list = next;
+ }
+
+ return retval;
+}
+
+CacheResult *
+cache_lookup (const DomainName *name, RRType type)
+{
+ CacheResult *result;
+ CacheEntry *entry;
+
+ cache_init ();
+
+ result = g_new (CacheResult, 1);
+ result->rr_type = type;
+ result->records = NULL;
+
+ entry = cache_find_entry (name);
+ if (!entry)
+ {
+ result->type = CACHE_NOTHING_KNOWN;
+ }
+ else if (entry->kind == CACHE_ENTRY_NO_SUCH_NAME)
+ {
+ result->type = CACHE_NO_SUCH_NAME;
+ }
+ else
+ {
+ GList *cache_records = cache_entry_get_records_of_type (entry, type);
+
+ if (cache_records)
+ {
+ CacheRecord *cache_record = cache_records->data;
+
+ if (cache_record->kind == CACHE_RECORD_NO_DATA)
+ {
+ result->type = CACHE_NO_DATA;
+ }
+ else
+ {
+ GList *list;
+
+ result->type = CACHE_DATA_FOUND;
+ for (list = cache_records; list != NULL; list = list->next)
+ {
+ cache_record = list->data;
+
+ result->records = g_list_prepend (
+ result->records, rdata_copy (cache_record->rdata));
+ }
+ }
+
+ g_list_free (cache_records);
+ }
+ else
+ {
+ result->type = CACHE_NAME_EXISTS;
+ }
+ }
+
+ return result;
+}
+
+CacheResult *
+cache_lookup_recursive (const DomainName *name, RRType type)
+{
+ CacheResult *result = cache_lookup (name, type);
+
+ if (result->type == CACHE_NO_DATA ||
+ result->type == CACHE_NAME_EXISTS)
+ {
+ g_assert (result->records == NULL);
+
+ if (type != CNAME_TYPE)
+ {
+ RData *cname = cache_lookup_cname (name);
+
+ if (cname)
+ {
+ g_assert (cname->type == CNAME_TYPE);
+ cache_result_free (result);
+ result = cache_lookup_recursive (cname->u.cname.name, type);
+
+ rdata_free (cname);
+ }
+ }
+ }
+
+ return result;
+}
+
+RData *
+cache_lookup_cname (const DomainName *name)
+{
+ RData *cname = NULL;
+ CacheResult *result = cache_lookup (name, CNAME_TYPE);
+
+ if (result->type == CACHE_DATA_FOUND)
+ cname = rdata_copy (result->records->data);
+
+ cache_result_free (result);
+ return cname;
+}
+
+RData *
+cache_lookup_soa (const DomainName *name)
+{
+ RData *soa = NULL;
+ DomainName *nm = domain_name_copy (name);
+
+ while (domain_name_n_labels (nm) > 0)
+ {
+ CacheResult *result = cache_lookup (nm, SOA_TYPE);
+
+ if (result->type == CACHE_DATA_FOUND)
+ {
+ soa = rdata_copy (result->records->data);
+ cache_result_free (result);
+ break;
+ }
+
+ cache_result_free (result);
+ domain_name_strip_prefix (nm);
+ }
+
+ domain_name_free (nm);
+
+ return soa;
+}
+
+void
+cache_result_free (CacheResult *result)
+{
+ if (result->type == CACHE_DATA_FOUND)
+ {
+ GList *list;
+ for (list = result->records; list; list = list->next)
+ rdata_free (list->data);
+ g_list_free (result->records);
+ }
+ g_free (result);
+}
+
+static CacheEntry *
+cache_ensure_data_entry (const DomainName *name)
+{
+ CacheEntry *entry;
+
+ entry = cache_find_entry (name);
+
+ if (entry && entry->kind == CACHE_ENTRY_NO_SUCH_NAME)
+ {
+ cache_delete_entry (entry->domain_name);
+ entry = NULL;
+ }
+
+ if (!entry)
+ {
+ entry = cache_entry_new_data (name);
+ g_hash_table_insert (cache, entry->domain_name, entry);
+ }
+
+ return entry;
+}
+
+static gboolean
+cache_already_exists (const ResourceRecord *rr)
+{
+ CacheEntry *entry;
+
+ entry = cache_find_entry (rr->name);
+
+ if (entry && entry->kind == CACHE_ENTRY_DATA)
+ {
+ GList *list;
+ for (list = entry->u.data.cache_records; list; list = list->next)
+ {
+ CacheRecord *cache_record = list->data;
+
+ if (cache_record->kind == CACHE_RECORD_DATA &&
+ rdata_equal (cache_record->rdata, rr->rdata))
+ {
+ /* found an existing exact match in the cache. Just
+ * update its time_to_die.
+ */
+
+ cache_record->time_to_die =
+ rr->time_to_live + now();
+
+ return TRUE;
+ }
+ }
+ }
+
+ return FALSE;
+}
+
+void
+cache_insert_positive (const ResourceRecord *rr, gboolean *cname_cycle)
+{
+ CacheEntry *entry;
+ CacheRecord *cache_record;
+
+ cache_init ();
+
+ g_return_if_fail (rr != NULL);
+ g_return_if_fail (rr->rdata != NULL);
+
+ if (cache_already_exists (rr))
+ return;
+
+ if (cname_cycle)
+ *cname_cycle = FALSE;
+
+ if (rr->rdata->type == CNAME_TYPE)
+ {
+ /* We must make sure we are not generating CNAME cycles
+ * in the cache and that a name will never have more than
+ * one CNAME record associcated with it.
+ *
+ * When we want to insert a CNAME record that maps N to C,
+ * first check if N already has a CNAME. If so, delete it.
+ * Then follow the chain of CNAMES starting in C. If we end
+ * up in N, inserting the record would generate a CNAME
+ * cycle in the cache.
+ */
+
+ /* FIXME */
+ }
+
+ entry = cache_ensure_data_entry (rr->name);
+
+ cache_record =
+ cache_record_new_data (rr->rdata->type, rr->time_to_live, rr->rdata);
+ entry->u.data.cache_records =
+ g_list_prepend (entry->u.data.cache_records, cache_record);
+}
+
+void
+cache_insert_no_such_name (const DomainName *name,
+ guint time_to_live)
+{
+ CacheEntry *entry;
+
+ cache_init ();
+
+ time_to_live = MIN (MAX_NEGATIVE_CACHING, time_to_live);
+
+ cache_delete_entry (name);
+
+ entry = cache_entry_new_no_such_name (name, time_to_live);
+
+ g_assert (entry);
+
+ g_hash_table_insert (cache, entry->domain_name, entry);
+}
+
+void
+cache_insert_no_data (const DomainName *name,
+ RRType type,
+ guint32 time_to_live)
+{
+ CacheEntry *entry;
+ CacheRecord *new_record;
+
+ cache_init ();
+
+ time_to_live = MIN (MAX_NEGATIVE_CACHING, time_to_live);
+
+ cache_delete_by_type (name, type);
+ entry = cache_ensure_data_entry (name);
+
+ new_record = cache_record_new_no_data (type, time_to_live);
+ entry->u.data.cache_records =
+ g_list_prepend (entry->u.data.cache_records, new_record);
+}
+
+/* Deletes all records that matches type
+ */
+void
+cache_delete_by_type (const DomainName *name,
+ RRType type)
+{
+ CacheEntry *entry;
+
+ cache_init ();
+
+ entry = cache_find_entry (name);
+ if (entry && entry->kind == CACHE_ENTRY_DATA)
+ {
+ GList *list;
+
+ list = entry->u.data.cache_records;
+ while (list != NULL)
+ {
+ GList *next = list->next;
+ CacheRecord *old_record = list->data;
+
+ if (old_record->type == type)
+ {
+ cache_record_free (old_record);
+ entry->u.data.cache_records =
+ g_list_delete_link (entry->u.data.cache_records, list);
+ }
+
+ list = next;
+ }
+
+ if (entry->u.data.cache_records == NULL)
+ {
+ g_hash_table_remove (cache, entry->domain_name);
+ cache_entry_free (entry);
+ }
+ }
+}
+
+/* Deletes all records that rdata.
+ */
+void
+cache_delete_rdata (const DomainName *name,
+ const RData *rdata)
+{
+ CacheEntry *entry;
+
+ cache_init ();
+
+ entry = cache_find_entry (name);
+ if (entry && entry->kind == CACHE_ENTRY_DATA)
+ {
+ GList *list;
+
+ list = entry->u.data.cache_records;
+ while (list != NULL)
+ {
+ GList *next = list->next;
+ CacheRecord *old_record = list->data;
+
+ if (rdata_equal (rdata, old_record->rdata))
+ {
+ cache_record_free (old_record);
+ entry->u.data.cache_records =
+ g_list_delete_link (entry->u.data.cache_records, list);
+ }
+
+ list = next;
+ }
+
+ if (entry->u.data.cache_records == NULL)
+ {
+ g_hash_table_remove (cache, entry->domain_name);
+ cache_entry_free (entry);
+ }
+ }
+}
+
+/*
+ * CacheRecord implementation
+ */
+static CacheRecord *
+cache_record_new_no_data (RRType type, guint time_to_live)
+{
+ CacheRecord *record;
+
+ record = g_new (CacheRecord, 1);
+
+ record->type = type;
+ record->kind = CACHE_RECORD_NO_DATA;
+ record->time_to_die = now() + time_to_live;
+ record->rdata = NULL;
+
+ return record;
+}
+
+static CacheRecord *
+cache_record_new_data (RRType type, guint time_to_live, const RData *rdata)
+{
+ CacheRecord *record;
+
+ g_return_val_if_fail (rdata != NULL, NULL);
+
+ record = g_new (CacheRecord, 1);
+
+ record->type = type;
+ record->kind = CACHE_RECORD_DATA;
+ record->time_to_die = now() + time_to_live;
+ record->rdata = rdata_copy (rdata);
+
+ return record;
+}
+
+static void
+cache_record_free (CacheRecord *record)
+{
+ g_return_if_fail (record != NULL);
+
+ if (record->kind == CACHE_RECORD_DATA)
+ rdata_free (record->rdata);
+
+ g_free (record);
+}
+
+/*
+ * CacheEntry implementation
+ */
+static CacheEntry *
+cache_entry_new_data (const DomainName *name)
+{
+ CacheEntry *entry;
+
+ g_return_val_if_fail (name != NULL, NULL);
+
+ entry = g_new (CacheEntry, 1);
+ entry->domain_name = domain_name_copy (name);
+ entry->kind = CACHE_ENTRY_DATA;
+ entry->u.data.cache_records = NULL;
+
+ return entry;
+}
+
+static CacheEntry *
+cache_entry_new_no_such_name (const DomainName *name, guint time_to_live)
+{
+ CacheEntry *entry;
+
+ g_return_val_if_fail (name != NULL, NULL);
+
+ entry = g_new (CacheEntry, 1);
+ entry->kind = CACHE_ENTRY_NO_SUCH_NAME;
+ entry->domain_name = domain_name_copy (name);
+ entry->u.no_such_name.time_to_die = now() + time_to_live;
+
+ return entry;
+}
+
+static void
+cache_entry_free (CacheEntry *entry)
+{
+ GList *list;
+
+ g_return_if_fail (entry != NULL);
+ g_return_if_fail (entry->domain_name != NULL);
+
+ domain_name_free (entry->domain_name);
+ switch (entry->kind)
+ {
+ case CACHE_ENTRY_DATA:
+ for (list = entry->u.data.cache_records; list != NULL; list = list->next)
+ {
+ CacheRecord *record = list->data;
+
+ cache_record_free (record);
+ }
+ break;
+
+ case CACHE_ENTRY_NO_SUCH_NAME:
+ break;
+ }
+
+ g_free (entry);
+}
diff --git a/src/lacdns-cache.h b/src/lacdns-cache.h
new file mode 100644
index 0000000..e1d8683
--- /dev/null
+++ b/src/lacdns-cache.h
@@ -0,0 +1,59 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- */
+
+/* Lac - Library for asynchronous communication
+ * Copyright (C) 2002 Søren Sandmann (sandmann@daimi.au.dk)
+ *
+ * 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.
+ */
+
+#include "lac.h"
+#include "lacdns-messages.h"
+
+typedef enum {
+ CACHE_NOTHING_KNOWN, /* not known whether name even exists */
+ CACHE_NO_SUCH_NAME, /* name does not exist */
+ CACHE_NAME_EXISTS, /* name exists, but nothing known
+ * about this type of data
+ */
+ CACHE_NO_DATA, /* name exists and has no data of this type */
+ CACHE_DATA_FOUND, /* name exists and has data of this type */
+} CacheResultType;
+
+typedef struct CacheResult CacheResult;
+struct CacheResult {
+ CacheResultType type;
+ RRType rr_type;
+ GList * records; /* of pointer to const RData */
+};
+
+CacheResult *cache_lookup (const DomainName *name,
+ RRType type);
+CacheResult *cache_lookup_recursive (const DomainName *name,
+ RRType type);
+RData *cache_lookup_cname (const DomainName *name);
+RData *cache_lookup_soa (const DomainName *name);
+void cache_result_free (CacheResult *result);
+void cache_insert_positive (const ResourceRecord *rr,
+ gboolean *cname_cycle);
+void cache_insert_no_such_name (const DomainName *name,
+ guint time_to_live);
+void cache_insert_no_data (const DomainName *name,
+ RRType type,
+ guint32 time_to_live);
+void cache_delete_by_type (const DomainName *name,
+ RRType type);
+void cache_delete_rdata (const DomainName *name,
+ const RData *rdata);
diff --git a/src/lacdns-config.c b/src/lacdns-config.c
new file mode 100644
index 0000000..4392bd8
--- /dev/null
+++ b/src/lacdns-config.c
@@ -0,0 +1,346 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- */
+
+/* Lac - Library for asynchronous communication
+ * Copyright (C) 2002, 2003 Søren Sandmann (sandmann@daimi.au.dk)
+ *
+ * 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.
+ */
+
+#include "lacdns-config.h"
+
+#include <string.h>
+#include <stdlib.h>
+
+static GQueue * config_name_servers;
+static GQueue * config_search_list;
+static guint config_n_dots; /* A name with this number of dots will first
+ * be tried as an absolute namem, ie., before
+ * any search list domains are appended.
+ */
+static guint config_timeout;
+static guint config_attempts;
+static gboolean config_rotate;
+
+static void read_etc_resolv_conf (void);
+
+enum {
+ DEFAULT_N_DOTS = 1,
+ DEFAULT_TIMEOUT = 5000,
+ DEFAULT_ATTEMPTS = 2,
+ DEFAULT_ROTATE = FALSE,
+};
+
+static void
+initialize_search_list (void)
+{
+ gchar *hostname;
+ gchar *dot;
+ gchar *local_domain = NULL;
+
+ hostname = lac_gethostname ();
+ dot = strchr (hostname, '.');
+ if (dot)
+ local_domain = g_strdup (dot + 1);
+ g_free (hostname);
+
+ if (local_domain)
+ {
+ if (strlen (local_domain) > 0)
+ g_queue_push_tail (config_search_list, local_domain);
+ else
+ g_free (local_domain);
+ }
+}
+
+static gboolean initialized = FALSE;
+
+gboolean
+dns_config_initialize (GError **err)
+{
+ if (!initialized)
+ {
+ config_name_servers = g_queue_new ();
+ config_search_list = g_queue_new ();
+
+ initialize_search_list ();
+
+ config_n_dots = DEFAULT_N_DOTS;
+ config_timeout = DEFAULT_TIMEOUT;
+ config_attempts = DEFAULT_ATTEMPTS;
+ config_rotate = DEFAULT_ROTATE;
+
+ read_etc_resolv_conf ();
+
+ if (g_queue_is_empty (config_name_servers))
+ {
+ NameServer *ns;
+ LacAddress *loopback;
+
+ loopback = lac_address_new_from_a_b_c_d (127, 0, 0, 1);
+
+ ns = name_server_new (loopback, err);
+ if (ns)
+ g_queue_push_tail (config_name_servers, ns);
+
+ lac_address_free (loopback);
+ }
+
+ if (g_queue_is_empty (config_name_servers))
+ {
+ g_set_error (
+ err, LAC_DNS_ERROR, LAC_DNS_ERROR_NO_SERVERS_CONFIGURED,
+ "No name servers found in your /etc/resolv.conf file");
+ }
+ else
+ {
+ initialized = TRUE;
+ }
+ }
+
+ return initialized;
+}
+
+GQueue *
+dns_config_get_servers (void)
+{
+ GQueue *result;
+ GList *list;
+
+ g_return_val_if_fail (initialized, NULL);
+
+ result = g_queue_new ();
+ for (list = config_name_servers->head; list; list = list->next)
+ g_queue_push_tail (result, list->data);
+
+ if (config_rotate)
+ {
+ gpointer data;
+ if ((data = g_queue_pop_head (config_name_servers)))
+ g_queue_push_tail (config_name_servers, data);
+ }
+
+ if (g_queue_is_empty (result))
+ {
+ g_warning ("No configured name servers");
+ }
+
+ return result;
+}
+
+GQueue *
+dns_config_get_search_list (void)
+{
+ GQueue *result;
+ GList *list;
+
+ g_return_val_if_fail (initialized, NULL);
+
+ result = g_queue_new ();
+ for (list = config_search_list->head; list; list = list->next)
+ g_queue_push_tail (result, g_strdup (list->data));
+
+ return result;
+}
+
+gint
+dns_config_get_n_dots (void)
+{
+ g_return_val_if_fail (initialized, -1);
+ return config_n_dots;
+}
+
+gint
+dns_config_get_attempts (void)
+{
+ g_return_val_if_fail (initialized, -1);
+ return config_attempts;
+}
+
+gint
+dns_config_get_timeout (void)
+{
+ g_return_val_if_fail (initialized, -1);
+ return config_timeout;
+}
+
+static gchar **
+strtokenize (const gchar *input,
+ const gchar *delims,
+ gint max_tokens)
+{
+ const gchar *s;
+ gboolean delim_table[256];
+ GSList *token_list = NULL, *slist;
+ gint n = 0;
+ gchar **result;
+
+ g_return_val_if_fail (input != NULL, NULL);
+ g_return_val_if_fail (delims != NULL, NULL);
+
+ memset (delim_table, FALSE, sizeof (delim_table));
+ for (s = delims; *s != '\0'; ++s)
+ delim_table[(gint)*s] = TRUE;
+
+ while (*input != '\0')
+ {
+ for (s = input; *s != '\0' && delim_table[(gint)*s]; ++s)
+ ;
+ input = s;
+ for (; *s != '\0' && !delim_table[(gint)*s]; ++s)
+ ;
+ if (s != input)
+ {
+ if (++n == max_tokens)
+ {
+ token_list =
+ g_slist_prepend (token_list, g_strdup (input));
+ break;
+ }
+ else
+ {
+ token_list =
+ g_slist_prepend (token_list, g_strndup (input, s - input));
+ }
+ }
+ input = s;
+ }
+
+ result = g_new (gchar *, n + 1);
+ result[n] = NULL;
+ for (slist = token_list; slist != NULL; slist = slist->next)
+ result[--n] = slist->data;
+ g_slist_free (token_list);
+
+ return result;
+}
+
+static void
+parse_nameserver_line (gchar *const *words)
+{
+ int i;
+ for (i = 1; words[i] != NULL; ++i)
+ {
+ LacAddress *address = lac_address_new_from_string (words[i]);
+
+ if (address)
+ {
+ NameServer *ns = name_server_new (address, NULL);
+
+ if (ns)
+ g_queue_push_tail (config_name_servers, ns);
+
+ lac_address_free (address);
+ }
+ }
+}
+
+static void
+parse_domain_line (gchar *const *words)
+{
+ while (!g_queue_is_empty (config_search_list))
+ g_free (g_queue_pop_head (config_search_list));
+
+ if (words[1])
+ g_queue_push_tail (config_search_list, g_strdup (words[1]));
+}
+
+static void
+parse_search_line (gchar *const *words)
+{
+ int i;
+
+ while (!g_queue_is_empty (config_search_list))
+ g_free (g_queue_pop_head (config_search_list));
+
+ for (i = 1; words[i] != NULL; ++i)
+ g_queue_push_tail (config_search_list, g_strdup (words[i]));
+}
+
+static void
+parse_sortlist_line (gchar *const *words)
+{
+ /* ignore sortlist for now - FIXME */
+}
+
+static void
+parse_options_line (gchar *const *words)
+{
+ int i;
+
+ for (i = 0; words[i] != NULL; ++i)
+ {
+ char *colon = strchr (words[i], ':');
+ glong number = -1;
+
+ if (colon)
+ {
+ gchar *endptr;
+
+ *colon = '\0';
+
+ number = strtol (colon + 1, &endptr, 10);
+ if (*endptr != '\0')
+ number = -1;
+ }
+
+ if (strcmp (words[i], "ndots") == 0 && number >= 0)
+ config_n_dots = number;
+ else if (strcmp (words[i], "timeout") == 0 && number >= 0)
+ config_timeout = 1000 * number;
+ else if (strcmp (words[i], "attempts") && number >= 1)
+ config_attempts = number;
+ else if (strcmp (words[i], "rotate") && !colon)
+ config_rotate = TRUE;
+
+ /* FIXME: implement "no-check-names" and "inet6" */
+ }
+}
+
+static void
+read_etc_resolv_conf (void)
+{
+ gchar *contents;
+
+ if (g_file_get_contents ("/etc/resolv.conf", &contents, NULL, NULL))
+ {
+ int i;
+ gchar **lines = g_strsplit (contents, "\n", -1);
+
+ for (i = 0; lines[i] != NULL; i++)
+ {
+ gchar **words = strtokenize (lines[i], "\t\f\r\n ", -1);
+
+ if (words[0])
+ {
+ if (strcmp (words[0], "nameserver") == 0)
+ parse_nameserver_line (words);
+ else if (strcmp (words[0], "domain") == 0)
+ parse_domain_line (words);
+ else if (strcmp (words[0], "search") == 0)
+ parse_search_line (words);
+ else if (strcmp (words[0], "sortlist") == 0)
+ parse_sortlist_line (words);
+ else if (strcmp (words[0], "options") == 0)
+ parse_options_line (words);
+ }
+
+ g_strfreev (words);
+ }
+
+ g_strfreev (lines);
+ g_free (contents);
+ }
+}
diff --git a/src/lacdns-config.h b/src/lacdns-config.h
new file mode 100644
index 0000000..f1d51ee
--- /dev/null
+++ b/src/lacdns-config.h
@@ -0,0 +1,37 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- */
+
+/* Lac - Library for asynchronous communication
+ * Copyright (C) 2002 Søren Sandmann (sandmann@daimi.au.dk)
+ *
+ * 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.
+ */
+
+#include <lac.h>
+
+#include "lacdns-nameserver.h"
+
+gboolean dns_config_initialize (GError **err);
+
+/* returns a queue of NameServers. You must free the queue, but not
+ * the nameservers
+ */
+GQueue *dns_config_get_servers (void);
+/* returns a queue of strings. You must free both the strings and
+ * the queue */
+GQueue *dns_config_get_search_list (void);
+gint dns_config_get_n_dots (void);
+gint dns_config_get_attempts (void); /* FIXME rename to max_attempts */
+gint dns_config_get_timeout (void);
diff --git a/src/lacdns-messages.c b/src/lacdns-messages.c
new file mode 100644
index 0000000..480b8e7
--- /dev/null
+++ b/src/lacdns-messages.c
@@ -0,0 +1,2098 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- */
+
+/* Lac - Library for asynchronous communication
+ * Copyright (C) 2002 Søren Sandmann (sandmann@daimi.au.dk)
+ *
+ * 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.
+ */
+
+#include "lacdns-messages.h"
+#include <string.h>
+
+typedef void (* TracedFreeFunc) (gpointer p);
+
+static gpointer traced_allocate (gsize size,
+ GList **free_list);
+static GQueue * traced_queue_new (GList **free_list);
+static gchar * traced_strndup (const gchar *str,
+ gsize size,
+ GList **free_list);
+static void traced_add_allocation (gpointer allocation,
+ TracedFreeFunc free_func,
+ GList **free_list);
+
+#define traced_new(type, n, free_list) \
+ (type *)traced_allocate (sizeof (type) * (n), free_list)
+
+/*
+ * PascalString
+ */
+struct PascalString {
+ guint len;
+ guint8 * data;
+};
+
+static PascalString *pascal_string_traced_new (const guchar **input,
+ gsize *len,
+ GList **free_list);
+static PascalString *pascal_string_new_from_str (const gchar *str);
+static void pascal_string_serialize (PascalString *pascal_string,
+ GString *result);
+static gboolean pascal_string_equal (PascalString *s1,
+ PascalString *s2);
+static PascalString *pascal_string_copy (const PascalString *str);
+static void pascal_string_free (PascalString *str);
+
+/*
+ * DomainName
+ */
+static DomainName *domain_name_traced_new (const guchar **input,
+ gsize *len,
+ const guchar *msg_start,
+ GList **free_list);
+
+/*
+ * Question entry
+ */
+static QuestionEntry *question_entry_traced_new (const guchar **input,
+ gsize *len,
+ const guchar *msg_start,
+ GList **free_list);
+static void question_entry_serialize (const QuestionEntry *question_entry,
+ GString *result);
+/* DEBUG */
+static gchar * question_entry_dump (const QuestionEntry *question_entry);
+
+
+/*
+ * Resource record
+ */
+static ResourceRecord *resource_record_traced_new (const guchar **input,
+ gsize *len,
+ const guchar *msg_start,
+ GList **free_list);
+static void resource_record_serialize (const ResourceRecord *resource_record,
+ GString *result);
+
+/* DEBUG */
+static char * resource_record_dump (const ResourceRecord *resource_record);
+
+
+/*
+ * RData
+ */
+static RData * rdata_traced_new (const guchar **input,
+ gsize *len,
+ const guchar *msg_start,
+ RRType type,
+ gsize rdata_len,
+ GList **free_list);
+static void rdata_serialize (const RData *rdata,
+ GString *result);
+/*
+ * Utilities
+ */
+static gboolean
+bit_is_on (guchar c, guint bit_num)
+{
+ return (c & (1 << bit_num))? TRUE : FALSE;
+}
+
+static guint
+bit_range (guchar c, guint from_bit, guint to_bit)
+{
+ int i;
+
+ guchar mask = 0;
+
+ for (i = 0; i < (from_bit - to_bit + 1); ++i)
+ {
+ mask <<= 1;
+ mask |= 1;
+ }
+
+ return (c >> to_bit) & mask;
+}
+
+static guint
+parse_16_bit (const guchar **input,
+ gsize *len)
+{
+ guint result;
+
+ g_return_val_if_fail (input != NULL, 0);
+ g_return_val_if_fail (*input != NULL, 0);
+ g_return_val_if_fail (len != NULL, 0);
+ g_return_val_if_fail (*len >= 2, 0);
+
+ result = *(*input)++ << 8;
+ result |= *(*input)++;
+
+ (*len) -= 2;
+
+ return result;
+}
+
+static guint
+parse_32_bit (const guchar **input,
+ gsize *len)
+{
+ guint result;
+
+ g_return_val_if_fail (input != NULL, 0);
+ g_return_val_if_fail (*input != NULL, 0);
+ g_return_val_if_fail (len != NULL, 0);
+ g_return_val_if_fail (*len >= 4, 0);
+
+ result = *(*input)++ << 24;
+ result |= *(*input)++ << 16;
+ result |= *(*input)++ << 8;
+ result |= *(*input)++;
+
+ (*len) -= 4;
+
+ return result;
+}
+
+static gchar
+set_bit_range (gchar c,
+ guint from_bit,
+ guint to_bit,
+ guint value)
+{
+ int i;
+
+ guchar mask = 1;
+
+ for (i = 0; i < (from_bit - to_bit + 1); ++i)
+ {
+ mask <<= 1;
+ mask |= 1;
+ }
+
+ return c | ((value & mask) << to_bit);
+}
+
+static gchar
+set_bit_on (gchar c,
+ guint bit)
+{
+ return c | (1 << bit);
+}
+
+static gchar
+set_bit_off (gchar c,
+ guint bit)
+{
+ return c & ~(1 << bit);
+}
+
+static gchar
+set_bit (gchar c,
+ guint bitno,
+ gboolean value)
+{
+ if (value)
+ return set_bit_on (c, bitno);
+ else
+ return set_bit_off (c, bitno);
+}
+
+static void
+serialize_16_bit (GString *str,
+ guint n)
+{
+ g_string_append_c (str, (n & 0xFF00) >> 8);
+ g_string_append_c (str, (n & 0x00FF));
+}
+
+static void
+serialize_32_bit (GString *str,
+ guint n)
+{
+ serialize_16_bit (str, (n & 0xFFFF0000) >> 16);
+ serialize_16_bit (str, (n & 0x0000FFFF));
+}
+
+/*
+ * PascalString implementation
+ */
+static PascalString *
+pascal_string_traced_new (const guchar **input,
+ gsize *len,
+ GList **free_list)
+{
+ /* FIXME
+ *
+ * we are accepting embedded 0 bytes here. Is that
+ * correct?
+ */
+
+ PascalString *chstr = traced_new (PascalString, 1, free_list);
+
+ g_return_val_if_fail (*len > 0, NULL);
+ g_return_val_if_fail (input != NULL, NULL);
+ g_return_val_if_fail (*input != NULL, NULL);
+
+ chstr->len = *(*input)++;
+ (*len)--;
+
+ if (*len < chstr->len)
+ return NULL;
+
+ if (chstr->len == 0)
+ {
+ chstr->data = NULL;
+ return chstr;
+ }
+
+ chstr->data = traced_strndup (*input, chstr->len, free_list);
+
+ *input += chstr->len;
+ *len -= chstr->len;
+
+ return chstr;
+}
+
+static PascalString *
+pascal_string_new_from_str (const gchar *str)
+{
+ PascalString *pascal_string = g_new (PascalString, 1);
+
+ pascal_string->len = strlen (str);
+
+ if (pascal_string->len > 0)
+ pascal_string->data = g_strndup (str, pascal_string->len);
+ else
+ pascal_string->data = NULL;
+
+ return pascal_string;
+}
+
+static void
+pascal_string_serialize (PascalString *str,
+ GString *result)
+{
+ g_return_if_fail (str != (void *)0x03);
+ g_return_if_fail (str != NULL);
+ g_return_if_fail (result != NULL);
+ g_return_if_fail (str->len < 256);
+
+ if (str->len > 0)
+ {
+ g_string_append_c (result, str->len);
+ g_string_append_len (result, str->data, str->len);
+ }
+}
+
+/* Note:
+ * This function compares two pascal strings. It is not
+ * case sensitive wrt. the ascii letters. That is, the
+ * behaves as if it did a conversion A->a, ..., Z->z
+ * on both strings before comparing them. Bytes that
+ * are not representing ASCII letters must match exactly.
+ */
+static gboolean
+pascal_string_equal (PascalString *s1,
+ PascalString *s2)
+{
+ gint i;
+
+ /* we can't use g_strncasecmp() because that function
+ * stops at the first 0 byte it sees
+ */
+ g_return_val_if_fail (s1 != NULL, FALSE);
+ g_return_val_if_fail (s2 != NULL, FALSE);
+
+ if (s1->len != s2->len)
+ return FALSE;
+
+ for (i = 0; i < s1->len; ++i)
+ {
+ gchar c1 = g_ascii_tolower (s1->data[i]);
+ gchar c2 = g_ascii_tolower (s2->data[i]);
+
+ if (c1 != c2)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static PascalString *
+pascal_string_copy (const PascalString *str)
+{
+ PascalString *copy;
+
+ g_return_val_if_fail (str != NULL, NULL);
+
+ copy = g_new (PascalString, 1);
+ copy->len = str->len;
+
+ if (copy->len > 0)
+ {
+ copy->data = g_new (guint8, str->len);
+ g_memmove (copy->data, str->data, str->len);
+ }
+ else
+ {
+ copy->data = NULL;
+ }
+
+ return copy;
+}
+
+static void
+pascal_string_free (PascalString *pascal_string)
+{
+ if (pascal_string->len > 0)
+ g_free (pascal_string->data);
+
+ g_free (pascal_string);
+}
+
+
+/*
+ * DomainName implementation
+ */
+struct DomainName {
+ GQueue *labels;
+};
+
+static DomainName *
+domain_name_real_traced_new (const guchar **input,
+ gsize *len,
+ const guchar *msg_start,
+ guint n_bytes_used,
+ GList **free_list)
+{
+ DomainName *domain_name;
+
+ domain_name = traced_new (DomainName, 1, free_list);
+ domain_name->labels = traced_queue_new (free_list);
+
+ while (*len > 0)
+ {
+ /* RFC 1035:
+ *
+ * To simplify implementations, the total length of a domain
+ * name (i.e., label octets and label length octets) is
+ * restricted to 255 octets or less.
+ *
+ * We allow the terminating zero to be the 256'th byte
+ */
+ if (n_bytes_used >= 256)
+ return NULL;
+
+ if (**input == 0)
+ {
+ /* terminating zero */
+ (*input)++;
+ (*len)--;
+
+ return domain_name;
+ }
+ else if (**input < 64)
+ {
+ /* regular label */
+ PascalString *label;
+
+ label = pascal_string_traced_new (input, len, free_list);
+ if (!label)
+ return NULL;
+
+ g_queue_push_tail (domain_name->labels, label);
+
+ n_bytes_used += label->len + 1;
+ }
+ else
+ {
+ /* pointer */
+ DomainName *rest_of_name;
+ GList *list;
+ guint offset;
+ const guchar *tmp_input;
+ gssize tmp_len;
+
+ if (*len < 2)
+ return NULL;
+
+ offset = parse_16_bit (input, len);
+ offset &= ~(0x03 << 14); /* mask off two highest bits */
+
+ tmp_input = msg_start + offset;
+ tmp_len = *input - msg_start - offset;
+
+ if (tmp_input >= *input || tmp_input < msg_start || tmp_len <= 0)
+ {
+ /* RFC 1035:
+ *
+ * ... In this scheme, an entire domain name or a
+ * list of labels at the end of a domain name is
+ * replaced with a pointer to a prior occurance of
+ * the same name. ...
+ *
+ * Note the "prior occurrence". We reject packets that
+ * contain forward pointers. This way we won't have to deal
+ * with cycles in the pointer structure.
+ */
+
+ return NULL;
+ }
+
+ rest_of_name = domain_name_real_traced_new (
+ &tmp_input, &tmp_len, msg_start, n_bytes_used, free_list);
+
+ if (!rest_of_name)
+ return NULL;
+
+ for (list = rest_of_name->labels->head; list; list = list->next)
+ g_queue_push_tail (domain_name->labels, list->data);
+
+ return domain_name;
+ }
+ }
+
+ /* We ran out of input without seeing the end of the domain name */
+ return NULL;
+}
+
+static DomainName *
+domain_name_traced_new (const guchar **input, gsize *len,
+ const guchar *msg_start, GList **free_list)
+{
+ g_return_val_if_fail (input != NULL, NULL);
+ g_return_val_if_fail (*input != NULL, NULL);
+ g_return_val_if_fail (len != NULL, NULL);
+
+ return domain_name_real_traced_new (input, len, msg_start, 0, free_list);
+}
+
+/*
+ * FIXME: it would be cool to be able to do compression
+ */
+void
+domain_name_serialize (const DomainName *domain_name,
+ GString *result)
+{
+ GList *list;
+
+ for (list = domain_name->labels->head; list != NULL; list = list->next)
+ pascal_string_serialize (list->data, result);
+
+ g_string_append_c (result, 0);
+}
+
+static void
+remove_empty_labels (GQueue *labels)
+{
+ if (!g_queue_is_empty (labels))
+ {
+ PascalString *label = g_queue_pop_head (labels);
+
+ remove_empty_labels (labels);
+
+ if (label->len > 0)
+ g_queue_push_head (labels, label);
+ else
+ pascal_string_free (label);
+ }
+}
+
+static gboolean
+domain_name_is_sane (DomainName *dname)
+{
+ gsize total_length;
+ GList *list;
+
+ total_length = 0;
+ for (list = dname->labels->head; list != NULL; list = list->next)
+ {
+ PascalString *label = list->data;
+
+ if (label->len > 63)
+ return FALSE;
+
+ total_length += label->len + 1;
+ }
+
+ total_length += 1; /* zero termination */
+
+ if (total_length > 255)
+ return FALSE;
+
+ return TRUE;
+}
+
+DomainName *
+domain_name_new_from_str (const gchar *str)
+{
+ DomainName *dname;
+ gchar **labels;
+ gint i;
+
+ g_return_val_if_fail (str != NULL, NULL);
+ g_return_val_if_fail (*str != '\0', NULL);
+ g_return_val_if_fail (*str != '.', NULL);
+ g_return_val_if_fail (strstr (str, "..") == NULL, NULL);
+
+ dname = g_new (DomainName, 1);
+ labels = g_strsplit (str, ".", -1);
+
+ dname->labels = g_queue_new ();
+ for (i = 0; labels[i] != NULL; ++i)
+ {
+ PascalString *label = pascal_string_new_from_str (labels[i]);
+ g_queue_push_tail (dname->labels, label);
+ }
+
+ g_strfreev (labels);
+
+ remove_empty_labels (dname->labels);
+
+ if (!domain_name_is_sane (dname))
+ {
+ domain_name_free (dname);
+ return NULL;
+ }
+
+ return dname;
+}
+
+gchar *
+domain_name_to_string (const DomainName *dname)
+{
+ GString *result;
+ GList *list;
+
+ if (dname->labels->length == 0)
+ return g_strdup ("");
+
+ result = g_string_new ("");
+ for (list = dname->labels->head; list; list = list->next)
+ {
+ PascalString *pst = list->data;
+ gint i;
+
+ for (i = 0; i < pst->len; ++i)
+ {
+ if (pst->data[i] != 0)
+ g_string_append_c (result, pst->data[i]);
+ }
+
+ if (list->next)
+ g_string_append_c (result, '.');
+ }
+
+ return g_string_free (result, FALSE);
+}
+
+guint
+domain_name_hash (const DomainName *dname)
+{
+ guint hash = 0;
+ GList *list;
+
+ for (list = dname->labels->head; list; list = list->next)
+ {
+ PascalString *label = list->data;
+ gint i;
+
+ for (i = 0; i < label->len; ++i)
+ hash = (hash << 5) - hash + g_ascii_tolower (label->data[i]);
+ }
+
+ return hash;
+}
+
+gboolean
+domain_name_equal (const DomainName *dname1,
+ const DomainName *dname2)
+{
+ GList *list1, *list2;
+
+ g_return_val_if_fail (dname1 != NULL, FALSE);
+ g_return_val_if_fail (dname2 != NULL, FALSE);
+
+ if (dname1->labels->length != dname2->labels->length)
+ return FALSE;
+
+ list1 = dname1->labels->head;
+ list2 = dname2->labels->head;
+ while (list1 && list2)
+ {
+ PascalString *label1 = list1->data;
+ PascalString *label2 = list2->data;
+
+ if (!pascal_string_equal (label1, label2))
+ return FALSE;
+
+ list1 = list1->next;
+ list2 = list2->next;
+ }
+
+ return TRUE;
+}
+
+DomainName *
+domain_name_copy (const DomainName *dname)
+{
+ DomainName *copy;
+ GList *list;
+
+ g_return_val_if_fail (dname != NULL, NULL);
+
+ copy = g_new (DomainName, 1);
+ copy->labels = g_queue_new ();
+
+ for (list = dname->labels->head; list != NULL; list = list->next)
+ {
+ PascalString *label = list->data;
+
+ g_queue_push_tail (copy->labels, pascal_string_copy (label));
+ }
+
+ return copy;
+}
+
+static DomainName *
+domain_name_traced_copy (const DomainName *dname,
+ GList **free_list)
+{
+ DomainName *copy = domain_name_copy (dname);
+ traced_add_allocation (copy, (TracedFreeFunc)domain_name_free, free_list);
+ return copy;
+}
+
+void
+domain_name_free (DomainName *domain_name)
+{
+ GList *list;
+
+ g_return_if_fail (domain_name != NULL);
+
+ for (list = domain_name->labels->head; list != NULL; list = list->next)
+ pascal_string_free (list->data);
+
+ g_queue_free (domain_name->labels);
+ g_free (domain_name);
+}
+
+void
+domain_name_strip_prefix (DomainName *name)
+{
+ g_return_if_fail (name != NULL);
+
+ if (name->labels->length >= 1)
+ {
+ PascalString *label = g_queue_pop_head (name->labels);
+ pascal_string_free (label);
+ }
+}
+
+guint
+domain_name_n_labels (const DomainName *name)
+{
+ g_return_val_if_fail (name != NULL, 0);
+
+ return name->labels->length;
+}
+
+/*
+ * QuestionEntry implementation
+ */
+static QuestionEntry *
+question_entry_traced_new (const guchar **input,
+ gsize *len,
+ const guchar *msg_start,
+ GList **free_list)
+{
+ QuestionEntry *question_entry;
+
+ g_return_val_if_fail (input != NULL, NULL);
+ g_return_val_if_fail (*input != NULL, NULL);
+ g_return_val_if_fail (len != NULL, NULL);
+
+ question_entry = traced_new (QuestionEntry, 1, free_list);
+ question_entry->domain_name =
+ domain_name_traced_new (input, len, msg_start, free_list);
+
+ if (!question_entry->domain_name)
+ return NULL;
+
+ if (*len < 4)
+ return NULL;
+
+ question_entry->query_type = parse_16_bit (input, len);
+ question_entry->query_class = parse_16_bit (input, len);
+
+ return question_entry;
+}
+
+static void
+question_entry_serialize (const QuestionEntry *qentry,
+ GString *result)
+{
+ domain_name_serialize (qentry->domain_name, result);
+ serialize_16_bit (result, qentry->query_type);
+ serialize_16_bit (result, qentry->query_class);
+}
+
+static gchar *
+question_entry_dump (const QuestionEntry *qentry)
+{
+ GString *s = g_string_new ("");
+ gchar *tmp = domain_name_to_string (qentry->domain_name);
+
+ g_string_append_printf (s, tmp);
+
+ g_free (tmp);
+
+ switch (qentry->query_type)
+ {
+ case A_QTYPE:
+ g_string_append_printf (s, " A ");
+ break;
+
+ case NS_QTYPE:
+ g_string_append_printf (s, " NS ");
+ break;
+
+ case CNAME_QTYPE:
+ g_string_append_printf (s, " CNAME ");
+ break;
+
+ case SOA_QTYPE:
+ g_string_append_printf (s, " SOA ");
+ break;
+
+ case MB_QTYPE:
+ g_string_append_printf (s, " MB ");
+ break;
+
+ case MG_QTYPE:
+ g_string_append_printf (s, " MG ");
+ break;
+
+ case MR_QTYPE:
+ g_string_append_printf (s, " MR ");
+ break;
+
+ case NULL_QTYPE:
+ g_string_append_printf (s, " NULL ");
+ break;
+
+ case WKS_QTYPE:
+ g_string_append_printf (s, " WKS ");
+ break;
+
+ case PTR_QTYPE:
+ g_string_append_printf (s, " PTR ");
+ break;
+
+ case HINFO_QTYPE:
+ g_string_append_printf (s, " HINFO ");
+ break;
+
+ case MINFO_QTYPE:
+ g_string_append_printf (s, " MINFO ");
+ break;
+
+ case MX_QTYPE:
+ g_string_append_printf (s, " MX ");
+ break;
+
+ case TXT_QTYPE:
+ g_string_append_printf (s, " TXT ");
+ break;
+
+ case AXFR_QTYPE:
+ g_string_append_printf (s, " AXFR ");
+ break;
+
+ case MAILB_QTYPE:
+ g_string_append_printf (s, " MAILB ");
+ break;
+
+ case ANY_QTYPE:
+ g_string_append_printf (s, " ANY ");
+ break;
+
+ default:
+ g_string_append_printf (s, " INVALID (%d) ", qentry->query_type);
+ break;
+ }
+ g_string_append_printf (s, "... more ...\n");
+
+ return g_string_free (s, FALSE);
+}
+
+
+/*
+ * RData implementation
+ */
+static RData *
+rdata_traced_new (const guchar **input,
+ gsize *len,
+ const guchar *msg_start,
+ RRType type,
+ gsize rdata_len,
+ GList **free_list)
+{
+ RData *rdata = traced_new (RData, 1, free_list);
+ if (*len < rdata_len)
+ return NULL;
+
+ rdata->type = type;
+
+ switch (type)
+ {
+ case A_TYPE:
+ if (*len < 4)
+ return NULL;
+
+ rdata->u.a.a = *(*input)++;
+ rdata->u.a.b = *(*input)++;
+ rdata->u.a.c = *(*input)++;
+ rdata->u.a.d = *(*input)++;
+
+ *len -= 4;
+ break;
+
+ case NS_TYPE:
+ rdata->u.ns.name_server =
+ domain_name_traced_new (input, len, msg_start, free_list);
+ if (!rdata->u.ns.name_server)
+ return NULL;
+ break;
+
+ case CNAME_TYPE:
+ rdata->u.cname.name =
+ domain_name_traced_new (input, len, msg_start, free_list);
+ if (!rdata->u.cname.name)
+ return NULL;
+ break;
+
+ case SOA_TYPE:
+ rdata->u.soa.server =
+ domain_name_traced_new (input, len, msg_start, free_list);
+ if (!rdata->u.soa.server)
+ return NULL;
+
+ rdata->u.soa.mailbox =
+ domain_name_traced_new (input, len, msg_start, free_list);
+ if (!rdata->u.soa.mailbox)
+ return NULL;
+
+ if (*len < 20)
+ return NULL;
+
+ rdata->u.soa.serial = parse_32_bit (input, len);
+ rdata->u.soa.refresh = parse_32_bit (input, len);
+ rdata->u.soa.retry = parse_32_bit (input, len);
+ rdata->u.soa.expire = parse_32_bit (input, len);
+ rdata->u.soa.minimum = parse_32_bit (input, len);
+ break;
+
+ case MB_TYPE:
+ rdata->u.mb.mad_name =
+ domain_name_traced_new (input, len, msg_start, free_list);
+ if (!rdata->u.mb.mad_name)
+ return NULL;
+ break;
+
+ case MG_TYPE:
+ rdata->u.mg.mgm_name =
+ domain_name_traced_new (input, len, msg_start, free_list);
+ if (!rdata->u.mg.mgm_name)
+ return NULL;
+ break;
+
+ case MR_TYPE:
+ rdata->u.mr.new_name =
+ domain_name_traced_new (input, len, msg_start, free_list);
+ if (!rdata->u.mr.new_name)
+ return NULL;
+ break;
+
+ case NULL_TYPE:
+ rdata->u.null.len = rdata_len;
+ rdata->u.null.data = traced_allocate (rdata_len, free_list);
+ g_memmove (rdata->u.null.data, *input, rdata_len);
+ break;
+
+ case WKS_TYPE:
+ if (*len < 5)
+ return NULL;
+ rdata->u.wks.address_a = *(*input)++;
+ rdata->u.wks.address_b = *(*input)++;
+ rdata->u.wks.address_c = *(*input)++;
+ rdata->u.wks.address_d = *(*input)++;
+ *len -= 4;
+
+ rdata->u.wks.protocol_number = *(*input)++;
+ (*len)--;
+
+ rdata->u.wks.bitmap_len = rdata_len - 5;
+ rdata->u.wks.bitmap = traced_new (guint8, rdata_len - 5, free_list);
+ g_memmove (rdata->u.wks.bitmap, *input, rdata_len - 5);
+
+ *input += rdata_len - 5;
+ *len -= rdata_len - 5;
+ break;
+
+ case PTR_TYPE:
+ rdata->u.ptr.ptr_name =
+ domain_name_traced_new (input, len, msg_start, free_list);
+ if (!rdata->u.ptr.ptr_name)
+ return NULL;
+ break;
+
+ case HINFO_TYPE:
+ if (*len < 2)
+ return NULL;
+
+ rdata->u.hinfo.cpu = pascal_string_traced_new (input, len, free_list);
+ if (!rdata->u.hinfo.cpu)
+ return NULL;
+
+ rdata->u.hinfo.os = pascal_string_traced_new (input, len, free_list);
+ if (!rdata->u.hinfo.os)
+ return NULL;
+ break;
+
+ case MINFO_TYPE:
+ rdata->u.minfo.r_mailbox =
+ domain_name_traced_new (input, len, msg_start, free_list);
+ if (!rdata->u.minfo.r_mailbox)
+ return NULL;
+
+ rdata->u.minfo.e_mailbox =
+ domain_name_traced_new (input, len, msg_start, free_list);
+ if (!rdata->u.minfo.e_mailbox)
+ return NULL;
+ break;
+
+ case MX_TYPE:
+ if (*len < 2)
+ return NULL;
+
+ rdata->u.mx.preference = parse_16_bit (input, len);
+ rdata->u.mx.exchanger =
+ domain_name_traced_new (input, len, msg_start, free_list);
+ if (!rdata->u.mx.exchanger)
+ return NULL;
+
+ break;
+
+ case TXT_TYPE:
+ rdata->u.txt.strings = traced_queue_new (free_list);
+
+ while (rdata_len > 0)
+ {
+ PascalString *string;
+
+ string = pascal_string_traced_new (input, len, free_list);
+ if (!string)
+ return NULL;
+
+ g_queue_push_tail (rdata->u.txt.strings, string);
+ rdata_len -= string->len + 1;
+ }
+ break;
+
+ default:
+ if (*len < rdata_len)
+ return NULL;
+
+ rdata->u.unknown.len = rdata_len;
+ rdata->u.unknown.data = traced_allocate (rdata_len, free_list);
+ g_memmove (rdata->u.unknown.data, *input, rdata_len);
+
+ *input += rdata_len;
+ *len -= rdata_len;
+ break;
+ }
+ return rdata;
+}
+
+static void
+rdata_serialize (const RData *rdata,
+ GString *result)
+{
+ /* FIXME */
+ g_error (G_STRLOC ": not implemented\n");
+ switch (rdata->type)
+ {
+ case A_TYPE:
+ break;
+
+ case NS_TYPE:
+ break;
+
+ case CNAME_TYPE:
+ break;
+
+ case SOA_TYPE:
+ break;
+
+ case MB_TYPE:
+ break;
+
+ case MG_TYPE:
+ break;
+
+ case MR_TYPE:
+ break;
+
+ case NULL_TYPE:
+ break;
+
+ case WKS_TYPE:
+ break;
+
+ case PTR_TYPE:
+ break;
+
+ case HINFO_TYPE:
+ break;
+
+ case MINFO_TYPE:
+ break;
+
+ case MX_TYPE:
+ break;
+
+ case TXT_TYPE:
+ break;
+
+ default:
+ break;
+ }
+}
+
+void
+rdata_free (RData *rdata)
+{
+ GList *list;
+
+ switch (rdata->type)
+ {
+ case A_TYPE:
+ break;
+
+ case NS_TYPE:
+ domain_name_free (rdata->u.ns.name_server);
+ break;
+
+ case CNAME_TYPE:
+ domain_name_free (rdata->u.cname.name);
+ break;
+
+ case SOA_TYPE:
+ domain_name_free (rdata->u.soa.server);
+ domain_name_free (rdata->u.soa.mailbox);
+ break;
+
+ case MB_TYPE:
+ domain_name_free (rdata->u.mb.mad_name);
+ break;
+
+ case MG_TYPE:
+ domain_name_free (rdata->u.mg.mgm_name);
+ break;
+
+ case MR_TYPE:
+ domain_name_free (rdata->u.mr.new_name);
+ break;
+
+ case NULL_TYPE:
+ g_free (rdata->u.null.data);
+ break;
+
+ case WKS_TYPE:
+ g_free (rdata->u.wks.bitmap);
+ break;
+
+ case PTR_TYPE:
+ domain_name_free (rdata->u.ptr.ptr_name);
+ break;
+
+ case HINFO_TYPE:
+ pascal_string_free (rdata->u.hinfo.os);
+ pascal_string_free (rdata->u.hinfo.cpu);
+ break;
+
+ case MINFO_TYPE:
+ domain_name_free (rdata->u.minfo.r_mailbox);
+ domain_name_free (rdata->u.minfo.e_mailbox);
+ break;
+
+ case MX_TYPE:
+ domain_name_free (rdata->u.mx.exchanger);
+ break;
+
+ case TXT_TYPE:
+ for (list = rdata->u.txt.strings->head; list != NULL; list = list->next)
+ {
+ PascalString *str = list->data;
+ pascal_string_free (str);
+ }
+ g_queue_free (rdata->u.txt.strings);
+ break;
+
+ default:
+ g_free (rdata->u.unknown.data);
+ break;
+ }
+
+ g_free (rdata);
+}
+
+RData *
+rdata_copy (const RData *rdata)
+{
+ GList *list;
+ RData *result = g_new (RData, 1);
+
+ g_return_val_if_fail (rdata != NULL, NULL);
+
+ result->type = rdata->type;
+ switch (rdata->type)
+ {
+ case A_TYPE:
+ result->u.a.a = rdata->u.a.a;
+ result->u.a.b = rdata->u.a.b;
+ result->u.a.c = rdata->u.a.c;
+ result->u.a.d = rdata->u.a.d;
+ break;
+
+ case NS_TYPE:
+ result->u.ns.name_server = domain_name_copy (rdata->u.ns.name_server);
+ break;
+
+ case CNAME_TYPE:
+ result->u.cname.name = domain_name_copy (rdata->u.cname.name);
+ break;
+
+ case SOA_TYPE:
+ result->u.soa.server = domain_name_copy (rdata->u.soa.server);
+ result->u.soa.mailbox = domain_name_copy (rdata->u.soa.mailbox);
+ result->u.soa.serial = rdata->u.soa.serial;
+ result->u.soa.refresh = rdata->u.soa.refresh;
+ result->u.soa.retry = rdata->u.soa.retry;
+ result->u.soa.expire = rdata->u.soa.expire;
+ result->u.soa.minimum = rdata->u.soa.minimum;
+ break;
+
+ case MB_TYPE:
+ result->u.mb.mad_name = domain_name_copy (rdata->u.mb.mad_name);
+ break;
+
+ case MG_TYPE:
+ result->u.mg.mgm_name = domain_name_copy (rdata->u.mg.mgm_name);
+ break;
+
+ case MR_TYPE:
+ result->u.mr.new_name = domain_name_copy (rdata->u.mr.new_name);
+ break;
+
+ case NULL_TYPE:
+ result->u.null.len = rdata->u.null.len;
+ result->u.null.data = g_malloc (rdata->u.null.len);
+ g_memmove (result->u.null.data, rdata->u.null.data, rdata->u.null.len);
+ break;
+
+ case WKS_TYPE:
+ result->u.wks.address_a = rdata->u.wks.address_a;
+ result->u.wks.address_b = rdata->u.wks.address_b;
+ result->u.wks.address_c = rdata->u.wks.address_c;
+ result->u.wks.address_d = rdata->u.wks.address_d;
+ result->u.wks.protocol_number = rdata->u.wks.protocol_number;
+ result->u.wks.bitmap_len = rdata->u.wks.bitmap_len;
+ g_memmove (
+ result->u.wks.bitmap, rdata->u.wks.bitmap, rdata->u.wks.bitmap_len);
+ break;
+
+ case PTR_TYPE:
+ result->u.ptr.ptr_name = domain_name_copy (rdata->u.ptr.ptr_name);
+ break;
+
+ case HINFO_TYPE:
+ result->u.hinfo.cpu = pascal_string_copy (rdata->u.hinfo.cpu);
+ result->u.hinfo.os = pascal_string_copy (rdata->u.hinfo.os);
+ break;
+
+ case MINFO_TYPE:
+ result->u.minfo.r_mailbox = domain_name_copy (rdata->u.minfo.r_mailbox);
+ result->u.minfo.e_mailbox = domain_name_copy (rdata->u.minfo.e_mailbox);
+ break;
+
+ case MX_TYPE:
+ result->u.mx.preference = rdata->u.mx.preference;
+ result->u.mx.exchanger = domain_name_copy (rdata->u.mx.exchanger);
+ break;
+
+ case TXT_TYPE:
+ result->u.txt.strings = g_queue_new ();
+ for (list = rdata->u.txt.strings->head;
+ list != NULL; list = list->next)
+ {
+ PascalString *str = list->data;
+
+ g_queue_push_tail (result->u.txt.strings, str);
+ }
+ break;
+
+ default:
+ result->u.unknown.len = rdata->u.unknown.len;
+ result->u.unknown.data = g_malloc (result->u.unknown.len);
+ g_memmove (
+ result->u.unknown.data, rdata->u.unknown.data,
+ rdata->u.unknown.len);
+ break;
+ }
+
+ return result;
+}
+
+gboolean
+rdata_equal (const RData *rdata1,
+ const RData *rdata2)
+{
+ GList *list1, *list2;
+
+ g_return_val_if_fail (rdata1 != NULL, FALSE);
+ g_return_val_if_fail (rdata2 != NULL, FALSE);
+
+ if (rdata1->type != rdata2->type)
+ return FALSE;
+
+ switch (rdata1->type)
+ {
+ case A_TYPE:
+ if (rdata1->u.a.a != rdata2->u.a.a ||
+ rdata1->u.a.b != rdata2->u.a.b ||
+ rdata1->u.a.c != rdata2->u.a.c ||
+ rdata1->u.a.d != rdata2->u.a.d)
+ {
+ return FALSE;
+ }
+ break;
+
+ case NS_TYPE:
+ if (!domain_name_equal (rdata1->u.ns.name_server, rdata2->u.ns.name_server))
+ return FALSE;
+ break;
+
+ case CNAME_TYPE:
+ if (!domain_name_equal (rdata1->u.cname.name, rdata2->u.cname.name))
+ return FALSE;
+ break;
+
+ case SOA_TYPE:
+ if (!domain_name_equal (rdata1->u.soa.server, rdata2->u.soa.server))
+ return FALSE;
+ if (!domain_name_equal (rdata1->u.soa.mailbox, rdata2->u.soa.mailbox))
+ return FALSE;
+ if (rdata1->u.soa.serial != rdata2->u.soa.serial)
+ return FALSE;
+ if (rdata1->u.soa.refresh != rdata2->u.soa.refresh)
+ return FALSE;
+ if (rdata1->u.soa.retry != rdata2->u.soa.retry)
+ return FALSE;
+ if (rdata1->u.soa.expire != rdata2->u.soa.expire)
+ return FALSE;
+ if (rdata1->u.soa.minimum != rdata2->u.soa.minimum)
+ return FALSE;
+ break;
+
+ case MB_TYPE:
+ if (!domain_name_equal (rdata1->u.mb.mad_name, rdata2->u.mb.mad_name))
+ return FALSE;
+ break;
+
+ case MG_TYPE:
+ if (!domain_name_equal (rdata1->u.mg.mgm_name, rdata2->u.mg.mgm_name))
+ return FALSE;
+ break;
+
+ case MR_TYPE:
+ if (!domain_name_equal (rdata1->u.mr.new_name, rdata2->u.mr.new_name))
+ return FALSE;
+ break;
+
+ case NULL_TYPE:
+ if (rdata1->u.null.len != rdata2->u.null.len)
+ return FALSE;
+
+ if (memcmp (rdata1->u.null.data,
+ rdata2->u.null.data,
+ rdata1->u.null.len) != 0)
+ {
+ return FALSE;
+ }
+
+ break;
+
+ case WKS_TYPE:
+ if (rdata1->u.wks.address_a != rdata2->u.wks.address_a)
+ return FALSE;
+ if (rdata1->u.wks.address_b != rdata2->u.wks.address_b)
+ return FALSE;
+ if (rdata1->u.wks.address_c != rdata2->u.wks.address_c)
+ return FALSE;
+ if (rdata1->u.wks.address_d != rdata2->u.wks.address_d)
+ return FALSE;
+ if (rdata1->u.wks.protocol_number != rdata2->u.wks.protocol_number)
+ return FALSE;
+ if (rdata1->u.wks.bitmap_len != rdata2->u.wks.bitmap_len)
+ return FALSE;
+
+ if (memcmp (rdata1->u.wks.bitmap,
+ rdata2->u.wks.bitmap,
+ rdata1->u.wks.bitmap_len) != 0)
+ {
+ return FALSE;
+ }
+ break;
+
+ case PTR_TYPE:
+ if (!domain_name_equal (rdata1->u.ptr.ptr_name, rdata2->u.ptr.ptr_name))
+ return FALSE;
+ break;
+
+ case HINFO_TYPE:
+ if (!pascal_string_equal (rdata1->u.hinfo.cpu, rdata2->u.hinfo.cpu))
+ return FALSE;
+ if (!pascal_string_equal (rdata1->u.hinfo.os, rdata2->u.hinfo.os))
+ return FALSE;
+ break;
+
+ case MINFO_TYPE:
+ if (!domain_name_equal (rdata1->u.minfo.r_mailbox,
+ rdata2->u.minfo.r_mailbox))
+ {
+ return FALSE;
+ }
+ if (!domain_name_equal (rdata1->u.minfo.e_mailbox,
+ rdata2->u.minfo.e_mailbox))
+ {
+ return FALSE;
+ }
+ break;
+
+ case MX_TYPE:
+ if (rdata1->u.mx.preference != rdata2->u.mx.preference)
+ return FALSE;
+ if (rdata1->u.mx.exchanger != rdata2->u.mx.exchanger)
+ return FALSE;
+ break;
+
+ case TXT_TYPE:
+ if (rdata1->u.txt.strings->length != rdata2->u.txt.strings->length)
+ return FALSE;
+
+ list1 = rdata1->u.txt.strings->head;
+ list2 = rdata2->u.txt.strings->head;
+ while (list1)
+ {
+ PascalString *p1 = list1->data;
+ PascalString *p2 = list2->data;
+
+ if (!pascal_string_equal (p1, p2))
+ return FALSE;
+
+ list1 = list1->next;
+ list2 = list2->next;
+ }
+ break;
+
+ default:
+ if (rdata1->u.unknown.len != rdata2->u.unknown.len)
+ return FALSE;
+ if (memcmp (rdata1->u.unknown.data,
+ rdata2->u.unknown.data,
+ rdata1->u.unknown.len) != 0)
+ {
+ return FALSE;
+ }
+ break;
+ }
+
+ return TRUE;
+}
+
+
+/*
+ * ResourceRecord implementation
+ */
+static ResourceRecord *
+resource_record_traced_new (const guchar **input,
+ gsize *len,
+ const guchar *msg_start,
+ GList **free_list)
+{
+ ResourceRecord *record = traced_new (ResourceRecord, 1, free_list);
+ RRType type;
+ gsize rdata_len;
+
+ g_return_val_if_fail (input != NULL, NULL);
+ g_return_val_if_fail (*input != NULL, NULL);
+ g_return_val_if_fail (len != NULL, NULL);
+
+ record->name =
+ domain_name_traced_new (input, len, msg_start, free_list);
+ if (!record->name)
+ return NULL;
+
+ if (*len < 10)
+ return NULL;
+
+ type = parse_16_bit (input, len);
+ record->class = parse_16_bit (input, len);
+ record->time_to_live = parse_32_bit (input, len);
+
+ if (record->time_to_live & (1 << 31))
+ {
+ /* RFC 2181:
+ *
+ * Implementations should treat TTL values received with the most
+ * significant bit set as if the entire value received was zero.
+ */
+ record->time_to_live = 0;
+ }
+
+ rdata_len = parse_16_bit (input, len);
+
+ if (rdata_len > *len)
+ return NULL;
+
+ record->rdata = rdata_traced_new (
+ input, len, msg_start, type, rdata_len, free_list);
+
+ if (!record->rdata)
+ return NULL;
+
+ return record;
+}
+
+static void
+resource_record_serialize (const ResourceRecord *record,
+ GString *result)
+{
+ g_return_if_fail (record != NULL);
+ g_return_if_fail (result != NULL);
+
+ domain_name_serialize (record->name, result);
+
+ serialize_16_bit (result, record->rdata->type);
+ serialize_16_bit (result, record->class);
+ serialize_32_bit (result, record->time_to_live);
+
+ rdata_serialize (record->rdata, result);
+}
+
+static char *
+resource_record_dump (const ResourceRecord *resource_record)
+{
+ GString *s = g_string_new ("");
+
+ g_string_append (s, domain_name_to_string (resource_record->name));
+
+ switch (resource_record->rdata->type)
+ {
+ case A_TYPE:
+ g_string_append_printf (s, " A ");
+ break;
+
+ case NS_TYPE:
+ g_string_append_printf (s, "NS ");
+ break;
+
+ case CNAME_TYPE:
+ g_string_append_printf (s, "CNAME ");
+ break;
+
+ case SOA_TYPE:
+ g_string_append_printf (s, "SOA ");
+ break;
+
+ case MB_TYPE:
+ g_string_append_printf (s, "MB ");
+ break;
+
+ case MG_TYPE:
+ g_string_append_printf (s, "MG ");
+ break;
+
+ case MR_TYPE:
+ g_string_append_printf (s, "MR ");
+ break;
+
+ case NULL_TYPE:
+ g_string_append_printf (s, "NULL ");
+ break;
+
+ case WKS_TYPE:
+ g_string_append_printf (s, "WKS ");
+ break;
+
+ case PTR_TYPE:
+ g_string_append_printf (s, "PTR ");
+ break;
+
+ case HINFO_TYPE:
+ g_string_append_printf (s, "HINFO ");
+ break;
+
+ case MINFO_TYPE:
+ g_string_append_printf (s, "MINFO ");
+ break;
+
+ case MX_TYPE:
+ g_string_append_printf (s, "MX ");
+ break;
+
+ case TXT_TYPE:
+ g_string_append_printf (s, "TXT ");
+ break;
+
+ default:
+ g_string_append_printf (s, "INVALID ");
+ break;
+ }
+ g_string_append_printf (s, "... more ...\n");
+
+ return g_string_free (s, FALSE);
+}
+
+/*
+ * Message implementation
+ */
+Message *
+message_new_parsed (const guchar **input,
+ gssize *len,
+ const gchar *msg_start)
+{
+ Message *message = g_new (Message, 1);
+ gint i;
+ guchar c;
+ guint n_question_entries;
+ guint n_answer_records;
+ guint n_authority_records;
+ guint n_additional_records;
+ GList *free_list = NULL;
+ GList *list;
+
+ if (*len < 12)
+ return NULL;
+
+ message->id = parse_16_bit (input, len);
+
+ c = *(*input)++;
+ (*len)--;
+
+ message->kind = bit_is_on (c, 7)? RESPONSE_MESSAGE : QUERY_MESSAGE;
+ message->opcode = bit_range (c, 6, 3);
+ message->authoritative = bit_is_on (c, 2);
+ message->truncated = bit_is_on (c, 1);
+ message->recursion_desired = bit_is_on (c, 0);
+
+ c = *(*input)++;
+ (*len)--;
+
+ message->recursion_available = bit_is_on (c, 7);
+ /* bits 6-4 are always zero */
+ message->response_code = bit_range (c, 3, 0);
+
+ message->question_entries = g_queue_new ();
+ message->answer_records = g_queue_new ();
+ message->authority_records = g_queue_new ();
+ message->additional_records = g_queue_new ();
+
+ n_question_entries = parse_16_bit (input, len);
+ n_answer_records = parse_16_bit (input, len);
+ n_authority_records = parse_16_bit (input, len);
+ n_additional_records = parse_16_bit (input, len);
+
+ /* question entries */
+ for (i = 0; i < n_question_entries; ++i)
+ {
+ QuestionEntry *entry =
+ question_entry_traced_new (input, len, msg_start, &free_list);
+
+ if (!entry)
+ goto error;
+
+ g_queue_push_tail (message->question_entries, entry);
+ }
+
+ /* answer records */
+ for (i = 0; i < n_answer_records; ++i)
+ {
+ ResourceRecord *record =
+ resource_record_traced_new (input, len, msg_start, &free_list);
+
+ if (!record)
+ goto error;
+
+ g_queue_push_tail (message->answer_records, record);
+ }
+
+ /* authority records */
+ for (i = 0; i < n_authority_records; ++i)
+ {
+ ResourceRecord *record =
+ resource_record_traced_new (input, len, msg_start, &free_list);
+
+ if (!record)
+ goto error;
+
+ g_queue_push_tail (message->authority_records, record);
+ }
+
+ /* additional records */
+ for (i = 0; i < n_additional_records; ++i)
+ {
+ ResourceRecord *record =
+ resource_record_traced_new (input, len, msg_start, &free_list);
+
+ if (!record)
+ goto error;
+
+ g_queue_push_tail (message->additional_records, record);
+ }
+
+ message->free_list = free_list;
+ return message;
+
+ error:
+ for (list = free_list; list != NULL; list = list->next->next)
+ {
+ TracedFreeFunc free_func = list->data;
+ gpointer alloc = list->next->data;
+
+ (* free_func) (alloc);
+ }
+ g_list_free (free_list);
+ return NULL;
+}
+
+static guint
+generate_id (void)
+{
+ static guint id = 0;
+
+ ++id;
+
+ /* make sure it doesn't serialize to 0
+ */
+ if (((id & 0xFF00) >> 8 == 0) &&
+ ((id & 0x00FF) == 0))
+ {
+ id = 1;
+ }
+
+ return id;
+}
+
+Message *
+message_new_question (const DomainName *dname,
+ QueryRRType type)
+{
+ Message *message = g_new (Message, 1);
+
+ QuestionEntry *question_entry;
+ GList *free_list = NULL;
+
+ message->id = generate_id ();
+ message->kind = QUERY_MESSAGE;
+ message->opcode = STANDARD;
+ message->response_code = 0;
+ message->authoritative = FALSE;
+ message->truncated = FALSE;
+ message->recursion_desired = TRUE;
+ message->recursion_available = FALSE;
+ message->response_code = 0;
+
+ message->question_entries = traced_queue_new (&free_list);
+ message->answer_records = traced_queue_new (&free_list);
+ message->authority_records = traced_queue_new (&free_list);
+ message->additional_records = traced_queue_new (&free_list);
+
+ question_entry = traced_new (QuestionEntry, 1, &free_list);
+ question_entry->query_type = type;
+ question_entry->query_class = INTERNET_QCLASS;
+ question_entry->domain_name =
+ domain_name_traced_copy (dname, &free_list);
+
+ g_queue_push_tail (message->question_entries, question_entry);
+
+ message->free_list = free_list;
+
+ return message;
+}
+
+void
+message_free (Message *message)
+{
+ GList *list;
+
+ g_return_if_fail (message != NULL);
+
+ for (list = message->free_list; list != NULL; list = list->next->next)
+ {
+ TracedFreeFunc free_func = list->data;
+ gpointer alloc = list->next->data;
+
+ free_func (alloc);
+ }
+ g_free (message);
+}
+
+void
+message_serialize (const Message *message,
+ GString *result)
+{
+ gchar c = 0;
+ GList *list;
+
+ g_return_if_fail (result != NULL);
+
+ /* serialize header */
+ serialize_16_bit (result, message->id);
+
+ if (message->kind == QUERY_MESSAGE)
+ c = set_bit_off (c, 7);
+ else if (message->kind == RESPONSE_MESSAGE)
+ c = set_bit_on (c, 7);
+ else
+ g_assert_not_reached ();
+
+ g_assert (message->opcode <= STATUS);
+
+ c = set_bit_range (c, 6, 3, message->opcode);
+
+ c = set_bit (c, 2, message->authoritative);
+ c = set_bit (c, 1, message->truncated);
+ c = set_bit (c, 0, message->recursion_desired);
+
+ g_string_append_c (result, c);
+
+ c = 0;
+
+ c = set_bit (c, 7, message->recursion_available);
+ c = set_bit_range (c, 6, 4, 0); /* three zero bits */
+ c = set_bit_range (c, 3, 0, message->response_code);
+
+ g_string_append_c (result, c);
+
+ serialize_16_bit (result, message->question_entries->length);
+ serialize_16_bit (result, message->answer_records->length);
+ serialize_16_bit (result, message->authority_records->length);
+ serialize_16_bit (result, message->additional_records->length);
+
+ /* serialize question */
+ for (list = message->question_entries->head; list; list = list->next)
+ question_entry_serialize (list->data, result);
+
+ /* serialize answer records */
+ for (list = message->answer_records->head; list; list = list->next)
+ resource_record_serialize (list->data, result);
+
+ /* serialize authority records */
+ for (list = message->authority_records->head; list; list = list->next)
+ resource_record_serialize (list->data, result);
+
+ /* serialize additional records */
+ for (list = message->additional_records->head; list; list = list->next)
+ resource_record_serialize (list->data, result);
+}
+
+
+/*
+ * Traced memory allocation
+ */
+static void
+traced_add_allocation (gpointer allocation,
+ TracedFreeFunc free_func,
+ GList **free_list)
+{
+ *free_list = g_list_prepend (*free_list, allocation);
+ *free_list = g_list_prepend (*free_list, free_func);
+}
+
+static gpointer
+traced_allocate (gsize size,
+ GList **free_list)
+{
+ gpointer result = g_malloc (size);
+
+ traced_add_allocation (result, g_free, free_list);
+
+ return result;
+}
+
+static gchar *
+traced_strndup (const gchar *str,
+ gsize size,
+ GList **free_list)
+{
+ gchar *result;
+
+ g_return_val_if_fail (str != NULL, NULL);
+
+ result = traced_allocate (size + 1, free_list);
+ strncpy (result, str, size);
+ result[size] = '\0';
+
+ return result;
+}
+
+static GQueue *
+traced_queue_new (GList **free_list)
+{
+ GQueue *queue = g_queue_new ();
+ traced_add_allocation (queue, (TracedFreeFunc)g_queue_free, free_list);
+
+ return queue;
+}
+
+/*
+ * Debug
+ */
+gchar *
+message_dump (const Message *message)
+{
+ GList *list;
+ GString *result = g_string_new ("");
+
+ g_string_append_printf (result, "=================\n");
+ g_string_append_printf (result, "id: %d\n", message->id);
+
+ g_string_append_printf (result, "kind: ");
+ switch (message->kind)
+ {
+ case QUERY_MESSAGE:
+ g_string_append_printf (result, "query message\n");
+ break;
+
+ case RESPONSE_MESSAGE:
+ g_string_append_printf (result, "response message\n");
+ break;
+
+ default:
+ g_string_append_printf (result, "INVALID\n");
+ break;
+ }
+
+ g_string_append_printf (result, "opcode: ");
+ switch (message->opcode)
+ {
+ case STANDARD:
+ g_string_append_printf (result, "standard\n");
+ break;
+
+ case INVERSE:
+ g_string_append_printf (result, "inverse\n");
+ break;
+
+ case STATUS:
+ g_string_append_printf (result, "status\n");
+ break;
+
+ default:
+ g_string_append_printf (result, "INVALID (%d)\n", message->opcode);
+ break;
+ }
+
+ g_string_append_printf (result, "authoritative %s\n",
+ message->authoritative? "TRUE" : "FALSE");
+
+ g_string_append_printf (result, "truncated %s\n",
+ message->truncated? "TRUE" : "FALSE");
+
+ g_string_append_printf (result, "recursion desired %s\n",
+ message->recursion_desired? "TRUE" : "FALSE");
+
+ g_string_append_printf (result, "recursion available %s\n",
+ message->recursion_available? "TRUE" : "FALSE");
+
+ g_string_append_printf (result, "response code ");
+ switch (message->response_code)
+ {
+ case NO_ERROR:
+ g_string_append_printf (result, "no error\n");
+ break;
+
+ case FORMAT_ERROR:
+ g_string_append_printf (result, "format error\n");
+ break;
+
+ case SERVER_FAILURE:
+ g_string_append_printf (result, "server failure\n");
+ break;
+
+ case NAME_ERROR:
+ g_string_append_printf (result, "name error\n");
+ break;
+
+ case NOT_IMPLEMENTED:
+ g_string_append_printf (result, "not implemented\n");
+ break;
+
+ case REFUSED:
+ g_string_append_printf (result, "refused\n");
+ break;
+
+ default:
+ g_string_append_printf (result, "INVALID\n");
+ break;
+ }
+
+ g_string_append_printf (
+ result,
+ "# question entries %d\n", message->question_entries->length);
+
+ g_string_append_printf (
+ result,
+ "# answer records %d\n", message->answer_records->length);
+
+ g_string_append_printf (
+ result,
+ "# authority records %d\n", message->authority_records->length);
+
+ g_string_append_printf (
+ result,
+ "# additional records %d\n", message->additional_records->length);
+
+ g_string_append_printf (result, "question entries ...\n");
+ for (list = message->question_entries->head; list; list = list->next)
+ {
+ QuestionEntry *entry = list->data;
+ gchar *qtext = question_entry_dump (entry);
+ g_string_append (result, qtext);
+ g_free (qtext);
+ }
+
+ g_string_append_printf (
+ result, "answer records ... \n");
+
+ for (list = message->answer_records->head; list; list = list->next)
+ {
+ ResourceRecord *rr = list->data;
+ gchar *rtext = resource_record_dump (rr);
+
+ g_string_append (result, rtext);
+ g_free (rtext);
+ }
+
+ return g_string_free (result, FALSE);
+}
+
+static const guchar valid1[] = {
+ 0, 1, 129, 128, 0, 1, 0, 1, 0, 2, 0, 1, 3, 119, 119, 119,
+ 1, 97, 2, 100, 107, 0, 0, 5, 0, 1, 192, 12, 0, 5, 0, 1,
+ 0, 0, 99, 30, 0, 16, 3, 119, 119, 119, 9, 97, 100, 118, 111, 107,
+ 97, 116, 101, 114, 192, 18, 192, 16, 0, 2, 0, 1, 0, 0, 99, 30,
+ 0, 14, 3, 110, 115, 50, 4, 117, 110, 105, 50, 3, 110, 101, 116, 0,
+ 192, 16, 0, 2, 0, 1, 0, 0, 99, 30, 0, 5, 2, 110, 115, 192,
+ 70, 192, 92, 0, 1, 0, 1, 0, 0, 4, 59, 0, 4, 129, 142, 7,
+ 99
+};
+
+static const guchar valid2[] = {
+ 0, 3, 129, 128, 0, 1, 0, 1, 0, 7, 0, 12, 3, 119, 119, 119, 5, 115,
+ 116, 97, 110, 100, 3, 111, 114, 103, 2, 117, 107, 0, 0, 1, 0, 1,
+ 192, 12, 0, 1, 0, 1, 0, 0, 14, 12, 0, 4, 195, 40, 6, 34, 192, 16, 0,
+ 2, 0, 1, 0, 0, 14, 12, 0, 17, 3, 110, 115, 51, 7, 102, 108, 105,
+ 114, 98, 108, 101, 3, 111, 114, 103, 0, 192, 16, 0, 2, 0, 1, 0, 0,
+ 14, 12, 0, 6, 3, 110, 115, 52, 192, 66, 192, 16, 0, 2, 0, 1, 0, 0,
+ 14, 12, 0, 6, 3, 110, 115, 48, 192, 66, 192, 16, 0, 2, 0, 1, 0, 0,
+ 14, 12, 0, 6, 3, 110, 115, 49, 192, 66, 192, 16, 0, 2, 0, 1, 0, 0,
+ 14, 12, 0, 21, 3, 110, 115, 49, 11, 105, 110, 115, 116, 97, 110,
+ 116, 45, 119, 101, 98, 2, 99, 111, 192, 26, 192, 16, 0, 2, 0, 1, 0,
+ 0, 14, 12, 0, 6, 3, 110, 115, 50, 192, 66, 192, 16, 0, 2, 0, 1, 0,
+ 0, 14, 12, 0, 6, 3, 110, 115, 50, 192, 149, 192, 62, 0, 1, 0, 1, 0,
+ 1, 178, 217, 0, 4, 207, 162, 195, 200, 192, 62, 0, 28, 0, 1, 0, 1,
+ 37, 9, 0, 16, 32, 1, 6, 248, 0, 0, 1, 2, 0, 0, 0, 0, 16, 0, 0, 1,
+ 192, 91, 0, 1, 0, 1, 0, 0, 10, 196, 0, 4, 207, 162, 200, 1, 192, 91,
+ 0, 1, 0, 1, 0, 0, 10, 196, 0, 4, 212, 134, 5, 1, 192, 91, 0, 28, 0,
+ 1, 0, 0, 10, 196, 0, 16, 32, 1, 6, 248, 0, 0, 1, 3, 0, 0, 0, 0, 0,
+ 0, 0, 1, 192, 109, 0, 1, 0, 1, 0, 1, 178, 217, 0, 4, 195, 40, 6, 20,
+ 192, 109, 0, 28, 0, 1, 0, 1, 37, 9, 0, 16, 32, 1, 6, 248, 6, 4, 0,
+ 0, 0, 0, 0, 0, 0, 32, 0, 1, 192, 127, 0, 1, 0, 1, 0, 1, 178, 217, 0,
+ 4, 195, 40, 6, 30, 192, 145, 0, 1, 0, 1, 0, 0, 64, 151, 0, 4, 212,
+ 25, 224, 85, 192, 178, 0, 1, 0, 1, 0, 1, 178, 217, 0, 4, 195, 40, 6,
+ 22, 192, 178, 0, 28, 0, 1, 0, 1, 37, 9, 0, 16, 32, 1, 6, 248, 6, 4,
+ 0, 0, 0, 0, 0, 0, 0, 34, 0, 1, 192, 196, 0, 1, 0, 1, 0, 0, 64, 151,
+ 0, 4, 213, 161, 75, 216
+};
+
+#if 0
+static void
+torture_test (void)
+{
+ const guchar *in = valid1;
+ gsize len = sizeof (valid1);
+ Message *msg;
+
+ /* valid1 */
+
+ msg = message_new_parsed (&in, &len, valid1);
+
+ g_assert (msg);
+ g_assert (len == 0);
+
+ message_free (msg);
+
+ /* valid2 */
+
+ in = valid2;
+ len = sizeof (valid2);
+
+ msg = message_new_parsed (&in, &len, valid2);
+
+ g_assert (msg);
+ g_assert (len == 0);
+
+ message_free (msg);
+}
+#endif
+
+#if 0
+static void
+dump_data (gsize msg_len,
+ guchar *msg)
+{
+ int i, j, k;
+
+ i = 0;
+ k = 0;
+ while (i < msg_len)
+ {
+ printf ("%3d, %3d: ", ++k, i);
+ for (j = 0; j < 2; ++j)
+ {
+ unsigned blah = (guchar)msg[i];
+ printf ("%3d (%c) ", blah, ( msg[i] >= 32)? msg[i] : ' ');
+ ++i;
+ }
+ printf ("\n");
+ }
+}
+#endif
diff --git a/src/lacdns-messages.h b/src/lacdns-messages.h
new file mode 100644
index 0000000..76c58d5
--- /dev/null
+++ b/src/lacdns-messages.h
@@ -0,0 +1,275 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- */
+
+/* Lac - Library for asynchronous communication
+ * Copyright (C) 2002 Søren Sandmann (sandmann@daimi.au.dk)
+ *
+ * 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.
+ */
+
+#include "lac.h"
+
+#ifndef LACDNS_MESSAGES__H
+#define LACDNS_MESSAGES__H
+
+typedef enum {
+ QUERY_MESSAGE = 0,
+ RESPONSE_MESSAGE = 1
+} MessageKind;
+
+typedef enum {
+ STANDARD = 0,
+ INVERSE = 1,
+ STATUS = 2
+} Opcode;
+
+typedef enum {
+ NO_ERROR = 0,
+ FORMAT_ERROR = 1,
+ SERVER_FAILURE = 2,
+ NAME_ERROR = 3,
+ NOT_IMPLEMENTED = 4,
+ REFUSED = 5
+} ResponseCode;
+
+typedef enum {
+ A_TYPE = 1,
+ NS_TYPE = 2,
+ CNAME_TYPE = 5,
+ SOA_TYPE = 6,
+ MB_TYPE = 7,
+ MG_TYPE = 8,
+ MR_TYPE = 9,
+ NULL_TYPE = 10,
+ WKS_TYPE = 11,
+ PTR_TYPE = 12,
+ HINFO_TYPE = 13,
+ MINFO_TYPE = 14,
+ MX_TYPE = 15,
+ TXT_TYPE = 16
+} RRType;
+
+typedef enum {
+ /* keep in sync with 'RRType' above */
+ A_QTYPE = 1,
+ NS_QTYPE = 2,
+ CNAME_QTYPE = 5,
+ SOA_QTYPE = 6,
+ MB_QTYPE = 7,
+ MG_QTYPE = 8,
+ MR_QTYPE = 9,
+ NULL_QTYPE = 10,
+ WKS_QTYPE = 11,
+ PTR_QTYPE = 12,
+ HINFO_QTYPE = 13,
+ MINFO_QTYPE = 14,
+ MX_QTYPE = 15,
+ TXT_QTYPE = 16,
+
+ AXFR_QTYPE = 252,
+ MAILB_QTYPE = 253,
+ ANY_QTYPE = 255
+} QueryRRType;
+
+typedef enum {
+ INTERNET_CLASS = 1,
+ CHAOS_CLASS = 2, /* what is this? */
+ HESOID_CLASS = 3 /* what is this? */
+} Class;
+
+typedef enum {
+ /* keep in sync with 'Class' above */
+ INTERNET_QCLASS = 1,
+ CHAOS_QCLASS = 2, /* what is this? */
+ HESOID_QCLASS = 3, /* what is this? */
+
+ ANY_QCLASS = 4
+} QueryClass;
+
+/*
+ * Data objects
+ */
+typedef struct Message Message;
+typedef struct QuestionEntry QuestionEntry;
+typedef struct PascalString PascalString;
+typedef struct DomainName DomainName;
+typedef struct ResourceRecord ResourceRecord;
+typedef struct RData RData;
+
+
+/*
+ * Message
+ */
+struct Message {
+ guint id;
+ MessageKind kind;
+ Opcode opcode;
+
+ guint authoritative : 1;
+ guint truncated : 1;
+ guint recursion_desired : 1;
+ guint recursion_available : 1;
+
+ ResponseCode response_code;
+
+ GQueue * question_entries;
+ GQueue * answer_records;
+ GQueue * authority_records;
+ GQueue * additional_records;
+
+ GList * free_list;
+};
+
+Message *message_new_parsed (const guchar **input,
+ gssize *len,
+ const gchar *msg_start);
+Message *message_new_question (const DomainName *name,
+ QueryRRType type);
+void message_free (Message *message);
+void message_serialize (const Message *message,
+ GString *result);
+gchar * message_dump (const Message *message);
+
+
+/*
+ * DomainName
+ */
+DomainName *domain_name_copy (const DomainName *dname);
+DomainName *domain_name_new_from_str (const gchar *str);
+void domain_name_free (DomainName *domain_name);
+void domain_name_serialize (const DomainName *domain_name,
+ GString *result);
+gchar * domain_name_to_string (const DomainName *dname);
+guint domain_name_hash (const DomainName *dname);
+gboolean domain_name_equal (const DomainName *dname1,
+ const DomainName *dname2);
+guint domain_name_n_labels (const DomainName *domain_name);
+void domain_name_strip_prefix (DomainName *domain_name);
+
+/*
+ * QuestionEntry
+ */
+struct QuestionEntry {
+ DomainName *domain_name;
+
+ QueryRRType query_type;
+ QueryClass query_class;
+};
+
+/*
+ * ResourceRecord
+ */
+struct ResourceRecord {
+ DomainName * name;
+ Class class;
+ guint time_to_live;
+ RData * rdata;
+};
+
+/*
+ * RData
+ */
+struct RData {
+ RRType type;
+
+ union {
+ struct {
+ guint a;
+ guint b;
+ guint c;
+ guint d;
+ } a;
+
+ struct {
+ DomainName * name_server;
+ } ns;
+
+ struct {
+ DomainName * name;
+ } cname;
+
+ struct {
+ DomainName * server;
+ DomainName * mailbox;
+ guint serial;
+ guint refresh;
+ guint retry;
+ guint expire;
+ guint minimum;
+ } soa;
+
+ struct {
+ DomainName * mad_name;
+ } mb;
+
+ struct {
+ DomainName * mgm_name;
+ } mg;
+
+ struct {
+ DomainName * new_name;
+ } mr;
+
+ struct {
+ guint8 * data;
+ guint len;
+ } null;
+
+ struct {
+ guchar address_a;
+ guchar address_b;
+ guchar address_c;
+ guchar address_d;
+ guchar protocol_number;
+ guint8 *bitmap;
+ gsize bitmap_len;
+ } wks;
+
+ struct {
+ DomainName *ptr_name;
+ } ptr;
+
+ struct {
+ PascalString * cpu;
+ PascalString * os;
+ } hinfo;
+
+ struct {
+ DomainName * r_mailbox;
+ DomainName * e_mailbox;
+ } minfo;
+
+ struct {
+ gint preference;
+ DomainName * exchanger;
+ } mx;
+
+ struct {
+ GQueue * strings;
+ } txt;
+
+ struct {
+ gsize len;
+ guint8 * data;
+ } unknown;
+ } u;
+};
+
+gboolean rdata_equal (const RData *rdata1,
+ const RData *rdata2);
+void rdata_free (RData *rdata);
+RData * rdata_copy (const RData *rdata);
+
+#endif /* LACDNS_MESSAGES__H */
diff --git a/src/lacdns-nameserver.c b/src/lacdns-nameserver.c
new file mode 100644
index 0000000..9c24533
--- /dev/null
+++ b/src/lacdns-nameserver.c
@@ -0,0 +1,555 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- */
+
+/* Lac - Library for asynchronous communication
+ * Copyright (C) 2002, 2003 Søren Sandmann (sandmann@daimi.au.dk)
+ *
+ * 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.
+ */
+
+#include "lacdns-nameserver.h"
+
+#if 0
+#include <stdio.h>
+#include <unistd.h>
+#endif
+
+enum {
+ MAX_UDP_LENGTH = 512,
+ DNS_PORT = 53,
+ QUERY_TIMEOUT = 300000, /* after this long, we consider any
+ * replies spurious
+ */
+ MAX_UDP_QUERIES = 64 /* maximum number of outstanding
+ * UDP queries
+ */
+};
+
+/*
+ * Query
+ */
+typedef struct Query Query;
+struct Query {
+ gint id;
+ guchar * msg;
+ gsize msg_len;
+ NameServer * name_server;
+ ResponseFunc callback;
+ gpointer data;
+ guint timeout_id;
+ guint ref_count;
+};
+
+static Query *
+query_new (guint id,
+ guchar *msg,
+ gsize msg_len,
+ NameServer *server,
+ ResponseFunc callback,
+ gpointer data)
+{
+ Query *query;
+
+ query = g_new (Query, 1);
+ query->id = id;
+ query->msg = msg;
+ query->msg_len = msg_len;
+ query->name_server = server;
+ query->callback = callback;
+ query->data = data;
+ query->timeout_id = 0;
+ query->ref_count = 1;
+
+ return query;
+}
+
+static void
+query_free (Query *query)
+{
+ g_return_if_fail (query != NULL);
+
+ if (--query->ref_count == 0)
+ {
+ if (query->timeout_id)
+ g_source_remove (query->timeout_id);
+
+ g_free (query->msg);
+ g_free (query);
+ }
+}
+
+/*
+ * NameServer implementation
+ */
+struct NameServer {
+ LacAddress * address;
+
+ GHashTable * outstanding_queries; /* map from id to queries */
+
+ /* UDP */
+ gint fd;
+ GQueue * unwritten_udp_queries;
+};
+
+static void name_server_read_udp (gpointer data);
+static void name_server_write_udp (gpointer data);
+static void name_server_dispatch_tcp_query (NameServer *server,
+ Query *query);
+
+static gboolean
+query_timeout (gpointer data)
+{
+ Query *query = data;
+ GError *err = g_error_new (LAC_DNS_ERROR, LAC_DNS_ERROR_TIMEOUT,
+ "No response from server");
+
+#if 0
+ g_print ("timeout on %p (query->id = %d)\n", data, query->id);
+#endif
+
+ query->callback (query->id, NULL, query->data, err);
+ g_error_free (err);
+
+ query->timeout_id = 0;
+
+ g_hash_table_remove (
+ query->name_server->outstanding_queries, &(query->id));
+
+ return FALSE;
+}
+
+#if 0
+static void
+print_message (const Message *message)
+{
+ gchar *tmp = message_dump (message);
+ g_print ("sending message\n");
+ g_print ("%s", tmp);
+ g_free (tmp);
+
+ g_print ("message id: %d\n", message->id);
+}
+#endif
+
+void
+name_server_query (NameServer *name_server,
+ gboolean use_tcp,
+ const Message *message,
+ ResponseFunc f,
+ gpointer data)
+{
+ GString *string;
+ Query *query;
+
+ string = g_string_new ("");
+
+ message_serialize (message, string);
+
+ if (string->len > MAX_UDP_LENGTH)
+ use_tcp = TRUE;
+
+ query = query_new (
+ message->id, string->str, string->len, name_server, f, data);
+
+ g_string_free (string, FALSE);
+
+ if (use_tcp)
+ {
+ name_server_dispatch_tcp_query (name_server, query);
+ }
+ else
+ {
+ g_queue_push_tail (name_server->unwritten_udp_queries, query);
+ name_server_write_udp (name_server);
+ }
+}
+
+NameServer *
+name_server_new (const LacAddress *address,
+ GError **err)
+{
+ NameServer *name_server;
+ int fd;
+
+ g_return_val_if_fail (err == NULL || *err == NULL, NULL);
+
+ fd = lac_socket_udp (err);
+ if (fd < 0)
+ return NULL;
+
+ if (!lac_set_blocking (fd, FALSE, err))
+ return NULL;
+
+ if (!lac_connect (fd, address, DNS_PORT, err))
+ return NULL;
+
+ name_server = g_new (NameServer, 1);
+
+ name_server->address = lac_address_copy (address);
+
+ name_server->outstanding_queries = g_hash_table_new_full (
+ g_int_hash, g_int_equal, NULL, (GDestroyNotify)query_free);
+
+ name_server->fd = fd;
+ lac_fd_add_watch (name_server->fd, name_server);
+ name_server->unwritten_udp_queries = g_queue_new ();
+
+ return name_server;
+}
+
+static void
+unparsable_message (void)
+{
+ g_warning ("Unparsable message received from server\n");
+}
+
+static void
+spurious_response (guint id)
+{
+ g_warning ("Spurious response received from DNS server - id: %d", id);
+}
+
+static void
+run_callbacks (GQueue *callbacks)
+{
+ while (!g_queue_is_empty (callbacks))
+ {
+ ResponseFunc callback = g_queue_pop_head (callbacks);
+ guint id = GPOINTER_TO_UINT (g_queue_pop_head (callbacks));
+ Message *reply = g_queue_pop_head (callbacks);
+ gpointer data = g_queue_pop_head (callbacks);
+
+ callback (id, reply, data, NULL);
+ message_free (reply);
+ }
+}
+
+static void
+handle_reply (GHashTable *queries, const guchar *input, gsize len, GQueue *callbacks)
+{
+ Message *reply;
+ Query *query;
+#if 0
+ gsize orig_len = len;
+ const guchar *orig_input = input;
+#endif
+
+#if 0
+ g_print ("handling reply of length %d\n", len);
+#endif
+
+ reply = message_new_parsed (&input, &len, input);
+
+ if (reply)
+ {
+ query = g_hash_table_lookup (queries, &(reply->id));
+
+ if (query)
+ {
+ g_queue_push_tail (callbacks, query->callback);
+ g_queue_push_tail (callbacks, GINT_TO_POINTER (query->id));
+ g_queue_push_tail (callbacks, reply);
+ g_queue_push_tail (callbacks, query->data);
+
+ g_hash_table_remove (queries, &(query->id));
+ }
+ else
+ {
+ spurious_response (reply->id);
+ }
+ }
+ else
+ {
+#if 0
+ pid_t pid = getpid();
+ gchar *name = g_strdup_printf ("unparsable.%d", pid);
+ FILE *logfile = fopen (name, "w");
+
+ g_print ("writing %d bytes\n", orig_len);
+ if (logfile)
+ {
+ int i;
+ for (i = 0; i < orig_len; ++i)
+ fwrite (&(orig_input[i]), 1, 1, logfile);
+ fclose (logfile);
+ }
+#endif
+
+ unparsable_message ();
+
+ }
+}
+
+static gboolean
+parse_tcp_data (GHashTable *queries, const guchar *input, gsize len)
+{
+ guint msg_len;
+
+ GQueue *callbacks;
+
+ if (len < 2)
+ return FALSE; /* need more data */
+
+ msg_len = *input++ << 8;
+ msg_len |= *input++;
+ len -= 2;
+
+ if (len < msg_len)
+ return FALSE; /* need more data */
+
+ callbacks = g_queue_new ();
+
+ handle_reply (queries, input, msg_len, callbacks);
+
+ run_callbacks (callbacks);
+ g_queue_free (callbacks);
+ return TRUE;
+}
+
+typedef struct TcpClosure {
+ NameServer *server;
+ GString *unparsed_data;
+} TcpClosure;
+
+static void
+name_server_handle_tcp_event (LacConnection *connection,
+ const LacConnectionEvent *event)
+{
+ TcpClosure *closure = lac_connection_get_data (connection);
+
+#if 0
+ lac_debug_checkpoint ();
+#endif
+
+ switch (event->type)
+ {
+ case LAC_CONNECTION_EVENT_CONNECT:
+ /* ignore */
+ break;
+
+ case LAC_CONNECTION_EVENT_READ:
+ g_string_append_len (
+ closure->unparsed_data, event->read.data, event->read.len);
+
+ if (parse_tcp_data (
+ closure->server->outstanding_queries,
+ closure->unparsed_data->str, closure->unparsed_data->len))
+ {
+ lac_connection_close (connection);
+ }
+ break;
+
+ case LAC_CONNECTION_EVENT_CLOSE:
+ if (event->close.remote_closed && closure->unparsed_data->len > 0)
+ {
+ /* Since we didn't get a useful response, we can't
+ * really report an error on any outstanding
+ * query
+ */
+ g_warning ("DNS: server closed connection in the "
+ "middle of a message");
+ }
+ lac_connection_unref (connection);
+ g_string_free (closure->unparsed_data, TRUE);
+ g_free (closure);
+ break;
+
+ case LAC_CONNECTION_EVENT_ERROR:
+ g_warning ("error: %s\n", event->error.err->message);
+ lac_connection_unref (connection);
+ g_string_free (closure->unparsed_data, TRUE);
+ g_free (closure);
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+}
+
+static void
+name_server_dispatch_tcp_query (NameServer *server, Query *query)
+{
+ TcpClosure *closure;
+ LacConnection *connection;
+ GString *msg_str;
+
+ g_assert (!g_hash_table_lookup (
+ server->outstanding_queries, &(query->id)));
+
+ g_hash_table_insert (server->outstanding_queries, &(query->id), query);
+
+ closure = g_new (TcpClosure, 1);
+ closure->server = server;
+ closure->unparsed_data = g_string_new ("");
+
+#if 0
+ g_print ("NEW CONNECTION\n");
+#endif
+ connection = lac_connection_new (
+ server->address, DNS_PORT,
+ name_server_handle_tcp_event, closure);
+
+ msg_str = g_string_new ("");
+
+ /* length */
+ g_string_append_c (msg_str, (query->msg_len & 0xFF00) >> 8);
+ g_string_append_c (msg_str, (query->msg_len & 0x00FF));
+
+ /* message */
+ g_string_append_len (msg_str, query->msg, query->msg_len);
+
+ lac_connection_write (connection, msg_str->str, msg_str->len);
+ query->timeout_id = g_timeout_add (QUERY_TIMEOUT, query_timeout, query);
+
+ g_string_free (msg_str, TRUE);
+}
+
+#if 0
+static void
+count (gpointer key, gpointer value, gpointer data)
+{
+ int *size = data;
+ Query *q = value;
+
+ *size += q->msg_len;
+}
+#endif
+
+static void
+name_server_update_watches (NameServer *ns)
+{
+ if (g_hash_table_size (ns->outstanding_queries) > 0)
+ {
+ lac_fd_set_read_callback (ns->fd, name_server_read_udp);
+ }
+ else
+ {
+ lac_fd_set_read_callback (ns->fd, NULL);
+ }
+
+ if (!g_queue_is_empty (ns->unwritten_udp_queries) &&
+ g_hash_table_size (ns->outstanding_queries) < MAX_UDP_QUERIES)
+ {
+ lac_fd_set_write_callback (ns->fd, name_server_write_udp);
+ }
+ else
+ {
+ lac_fd_set_write_callback (ns->fd, NULL);
+ }
+}
+
+static void
+name_server_read_udp (gpointer data)
+{
+ NameServer *ns = data;
+
+ gint8 *msg;
+ gssize msg_len;
+ GQueue *callbacks = g_queue_new ();
+
+ callbacks = g_queue_new ();
+
+ while (TRUE)
+ {
+ GError *err = NULL;
+
+ msg = g_new (gint8, MAX_UDP_LENGTH);
+
+ msg_len = lac_recv (ns->fd, msg, MAX_UDP_LENGTH, &err);
+ if (err)
+ {
+ if (!g_error_matches (
+ err, LAC_SOCKET_ERROR, LAC_SOCKET_ERROR_AGAIN))
+ {
+ /* This is the best we can do as we don't know
+ * what query to report an error on.
+ *
+ * FIXME: we should move the server to the back of
+ * the queue so we will be less likely to try it
+ * again.
+ */
+ g_warning ("error on recv(): %s\n", err->message);
+ }
+
+ g_free (msg);
+ g_error_free (err);
+ break;
+ }
+
+ if (msg_len == 0)
+ {
+ gchar *addr = lac_address_to_string (ns->address);
+ g_warning ("received empty message from %s", addr);
+ g_free (addr);
+ g_free (msg);
+ break;
+ }
+
+ handle_reply (ns->outstanding_queries, msg, msg_len, callbacks);
+
+ g_free (msg);
+ }
+
+ name_server_update_watches (ns);
+
+ run_callbacks (callbacks);
+ g_queue_free (callbacks);
+}
+
+static void
+name_server_write_udp (gpointer data)
+{
+ NameServer *ns = data;
+ Query *query;
+ GError *err = NULL;
+
+ while (!g_queue_is_empty (ns->unwritten_udp_queries) &&
+ g_hash_table_size (ns->outstanding_queries) < MAX_UDP_QUERIES)
+ {
+ query = g_queue_pop_head (ns->unwritten_udp_queries);
+
+#if 0
+ g_print ("writing UDP for %p\n", query);
+#endif
+
+ lac_send (ns->fd, query->msg, query->msg_len, &err);
+
+ if (err)
+ {
+ if (g_error_matches (
+ err, LAC_SOCKET_ERROR, LAC_SOCKET_ERROR_AGAIN))
+ {
+ g_queue_push_head (ns->unwritten_udp_queries, query);
+ }
+ else
+ {
+ query->callback (query->id, NULL, query->data, err);
+ query_free (query);
+ g_error_free (err);
+ }
+
+ break; /* while */
+ }
+
+ query->timeout_id = g_timeout_add (QUERY_TIMEOUT, query_timeout, query);
+#if 0
+ g_print ("inserting %p\n", query);
+#endif
+ g_hash_table_insert (ns->outstanding_queries, &(query->id), query);
+ }
+
+ name_server_update_watches (ns);
+}
diff --git a/src/lacdns-nameserver.h b/src/lacdns-nameserver.h
new file mode 100644
index 0000000..cc43430
--- /dev/null
+++ b/src/lacdns-nameserver.h
@@ -0,0 +1,47 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- */
+
+/* Lac - Library for asynchronous communication
+ * Copyright (C) 2002 Søren Sandmann (sandmann@daimi.au.dk)
+ *
+ * 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 LACDNS_NAMESERVER_H
+#define LACDNS_NAMESERVER_H
+
+#include "lac.h"
+#include "lacdns-messages.h"
+
+
+/*
+ * Name server declaration
+ */
+typedef struct NameServer NameServer;
+
+typedef void (* ResponseFunc) (gint id,
+ const Message *reply,
+ gpointer data,
+ const GError *err);
+
+NameServer *name_server_new (const LacAddress *address,
+ GError **err);
+void name_server_query (NameServer *name_server,
+ gboolean use_tcp,
+ const Message *message,
+ ResponseFunc f,
+ gpointer data);
+
+#endif /* LACDNS_NAMESERVER_H */
diff --git a/src/lacdns-query.c b/src/lacdns-query.c
new file mode 100644
index 0000000..976d61f
--- /dev/null
+++ b/src/lacdns-query.c
@@ -0,0 +1,808 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- */
+
+/* Lac - Library for asynchronous communication
+ * Copyright (C) 2002, 2003 Søren Sandmann (sandmann@daimi.au.dk)
+ *
+ * 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.
+ */
+
+#include "lacdns-nameserver.h"
+#include "lacdns-cache.h"
+#include "lacdns-config.h"
+#include "lacdns-query.h"
+#include <string.h>
+
+typedef struct DnsQuery DnsQuery;
+typedef struct Callback Callback;
+
+struct Callback {
+ DnsQueryEventFunc func;
+ gpointer data;
+};
+
+struct DnsQuery {
+ GList * callbacks;
+
+ /* query description */
+ RRType type;
+ GQueue * all_names;
+ GQueue * all_servers;
+
+ /* uncachable records */
+ GQueue * uncachable_records;
+
+ /* query state */
+ GQueue * untried_names;
+ DomainName * current_name;
+ GQueue * tried_names;
+
+ GQueue * untried_servers;
+ NameServer * current_server;
+ GQueue * tried_servers;
+
+ gint current_id; /* Id of the latest question we
+ * asked any server.
+ */
+
+ gint attempts; /* how many trips through the list
+ * of servers we have made
+ */
+
+ gboolean use_tcp;
+ gint timeout;
+
+ gint ref_count;
+};
+
+static gboolean dns_query_next_name (DnsQuery *query);
+static gboolean dns_query_next_server (DnsQuery *query);
+static void dns_query_ask_server (DnsQuery *query);
+static gboolean dns_query_cache_lookup (DnsQuery *query);
+static void dns_query_unref (DnsQuery *query);
+static DnsQuery *dns_query_ref (DnsQuery *query);
+
+static GHashTable *outstanding_queries = NULL;
+
+static void
+do_callback (DnsQuery *query, const DnsQueryEvent *event)
+{
+ GList *list;
+ ResourceRecord *rr;
+
+ g_assert (g_hash_table_remove (outstanding_queries, query));
+
+#if 0
+ g_print ("callback on %p\n", query);
+#endif
+
+ g_assert (!g_hash_table_lookup (outstanding_queries, query));
+
+ if (query->timeout)
+ {
+ g_source_remove (query->timeout);
+ query->timeout = 0;
+ }
+
+ /* Delete uncachable records from the cache before calling the user.
+ * See comment in analyze_normal_message()
+ */
+ while ((rr = g_queue_pop_head (query->uncachable_records)))
+ cache_delete_rdata (rr->name, rr->rdata);
+
+ /* call user */
+ for (list = query->callbacks; list != NULL; list = list->next)
+ {
+ Callback *callback = list->data;
+
+ callback->func (event, callback->data);
+ }
+
+#if 0
+ g_print ("end callback on %p\n", query);
+#endif
+
+ dns_query_unref (query);
+}
+
+static void
+emit_error (DnsQuery *query, const GError *err)
+{
+ DnsQueryEvent event;
+
+ event.type = DNS_QUERY_EVENT_ERROR;
+ event.error.err = err;
+
+ do_callback (query, &event);
+}
+
+static void
+emit_results (DnsQuery *query, const GList *results)
+{
+ DnsQueryEvent event;
+
+ event.type = DNS_QUERY_EVENT_RESULTS;
+ event.results.results = results;
+
+ do_callback (query, &event);
+}
+
+static void
+emit_no_data (DnsQuery *query)
+{
+ DnsQueryEvent event;
+
+ event.type = DNS_QUERY_EVENT_NO_DATA;
+
+ do_callback (query, &event);
+}
+
+static void
+emit_no_such_name (DnsQuery *query)
+{
+ DnsQueryEvent event;
+
+ if (dns_query_next_name (query))
+ {
+ if (!dns_query_cache_lookup (query))
+ dns_query_ask_server (query);
+
+ return;
+ }
+
+ event.type = DNS_QUERY_EVENT_NO_SUCH_NAME;
+
+ do_callback (query, &event);
+}
+
+static void
+insert_in_cache (ResourceRecord *rr,
+ GQueue *uncachable_records)
+{
+ /* FIXME: currently, we simply ignore cname cycles.
+ * Maybe we shouldn't (or maybe the cache itself should
+ * handle cname cycles
+ */
+ if (rr->time_to_live == 0)
+ {
+ /* Make sure the cache won't delete the record
+ * immediately.
+ */
+ rr->time_to_live = 1000;
+
+ g_queue_push_tail (uncachable_records, rr);
+ }
+
+ cache_insert_positive (rr, NULL);
+}
+
+static void
+analyze_normal_message (const Message *message, DnsQuery *query)
+{
+ GList *list;
+
+ /* The basic idea here is to insert everything in the cache
+ * and then do a dns_query_cache_lookup()
+ *
+ * However, records with time-to-live == 0 must not be cached.
+ * To make sure we don't end up with uncacheable records in the
+ * cache, we maintain a separate list of records with ttl = 0.
+ * Then when the dns_query_cache_lookup() is done, we delete
+ * those records from the cache.
+ *
+ * To make sure we don't callback to the user with uncachable
+ * records in the cache, the list is stored in the query
+ * and the records are removed from the cache by do_callback()
+ * before actually calling out the user. Yeah, this could use
+ * some reorganization ...
+ */
+ gchar *error_msg = NULL;
+
+ g_return_if_fail (query->current_name != NULL);
+
+ for (list = message->answer_records->head; list; list = list->next)
+ insert_in_cache (list->data, query->uncachable_records);
+
+ for (list = message->authority_records->head; list; list = list->next)
+ insert_in_cache (list->data, query->uncachable_records);
+
+ for (list = message->additional_records->head; list; list = list->next)
+ insert_in_cache (list->data, query->uncachable_records);
+
+ switch (message->response_code)
+ {
+ case NO_ERROR:
+ if (!dns_query_cache_lookup (query))
+ {
+ RData *soa = cache_lookup_soa (query->current_name);
+ if (soa)
+ {
+ cache_insert_no_data (
+ query->current_name, query->type, soa->u.soa.minimum);
+ rdata_free (soa);
+ }
+
+ emit_no_data (query);
+ }
+ break;
+
+ case NAME_ERROR:
+ {
+ RData *soa = cache_lookup_soa (query->current_name);
+ if (soa)
+ {
+ cache_insert_no_such_name (query->current_name, soa->u.soa.minimum);
+ rdata_free (soa);
+ }
+
+ emit_no_such_name (query);
+ break;
+ }
+
+ case FORMAT_ERROR:
+ error_msg = g_strdup ("Server reported \"format error\" (this is "
+ "probably a bug in Lac)");
+ break;
+
+ case SERVER_FAILURE:
+ error_msg = g_strdup ("Server failed");
+ break;
+
+ case NOT_IMPLEMENTED:
+ error_msg = g_strdup ("Server reported \"not implemented\"");
+ break;
+
+ case REFUSED:
+ error_msg = g_strdup ("Server refused service");
+ break;
+
+ default:
+ error_msg = g_strdup_printf ("Unknown response code received "
+ "from server: %d",
+ message->response_code);
+ break;
+ }
+
+ if (error_msg)
+ {
+ GError *dns_error =
+ g_error_new (LAC_DNS_ERROR, LAC_DNS_ERROR_SERVER_FAILED,
+ error_msg);
+ emit_error (query, dns_error);
+ g_error_free (dns_error);
+ g_free (error_msg);
+ }
+}
+
+static void
+analyze_reply (gint id, const Message *message, gpointer data, const GError *err)
+{
+ DnsQuery *query = data;
+
+ /* If the original query is no longer in the table of outstanding queries
+ * it means that we already called back to the user, so we can't do
+ * anything useful with this reply.
+ *
+ * FIXME: hmm, we *could* try to cache it. The data should be good enough ...
+ */
+ if (query == g_hash_table_lookup (outstanding_queries, query))
+ {
+ /* ignore error replies to old questions */
+ if (err && id == query->current_id)
+ {
+ if (dns_query_next_server (query))
+ dns_query_ask_server (query);
+ else
+ emit_error (query, err);
+ }
+ else if (message->truncated && !query->use_tcp)
+ {
+ /* This is the right thing to do even if this is a reply
+ * to an old query. We assume the data is basically OK (just
+ * slow to arrive), so any UDP reply will be truncated. Thus,
+ * we might as well try a TCP connection immediately.
+ */
+ query->use_tcp = TRUE;
+
+ dns_query_ask_server (query);
+ }
+ else
+ {
+ /* if we got a tcp reply with "truncated" set to
+ * TRUE, we'll assume that the server set this
+ * bit by mistake and that the reply is otherwise
+ * good enough.
+ */
+
+ analyze_normal_message (message, query);
+ }
+ }
+ else
+ {
+ g_assert (query->timeout == 0);
+ }
+
+ dns_query_unref (query);
+}
+
+static gboolean
+handle_timeout (gpointer data)
+{
+ DnsQuery *query = data;
+
+ query->timeout = 0;
+
+ if (dns_query_next_server (query))
+ {
+ dns_query_ask_server (query);
+ }
+ else
+ {
+ GError *err = g_error_new (
+ LAC_DNS_ERROR, LAC_DNS_ERROR_TIMEOUT,
+ "No servers could be reached");
+
+ emit_error (query, err);
+
+ g_error_free (err);
+ }
+
+ return FALSE;
+}
+
+static DomainName *
+canonicalize_name (const DomainName *name)
+{
+ DomainName *result = NULL;
+ RData *cname = cache_lookup_cname (name);
+
+ if (cname)
+ {
+ if (!(result = canonicalize_name (cname->u.cname.name)))
+ result = domain_name_copy (cname->u.cname.name);
+
+ rdata_free (cname);
+ }
+
+ return result;
+}
+
+static gboolean
+dns_query_cache_lookup (DnsQuery *query)
+{
+ CacheResult *result;
+
+ g_return_val_if_fail (query->current_name != NULL, FALSE);
+
+ result = cache_lookup_recursive (query->current_name, query->type);
+
+ if (result->type == CACHE_NOTHING_KNOWN ||
+ result->type == CACHE_NAME_EXISTS)
+ {
+ DomainName *cname = canonicalize_name (query->current_name);
+
+ if (cname)
+ {
+ g_queue_push_head (query->untried_names, cname);
+
+ dns_query_next_name (query);
+ dns_query_ask_server (query);
+
+ return TRUE;
+ }
+
+ cache_result_free (result);
+ return FALSE;
+ }
+
+#if 0
+ g_print ("data in cache\n");
+#endif
+
+ if (result->type == CACHE_NO_DATA)
+ emit_no_data (query);
+ else if (result->type == CACHE_DATA_FOUND)
+ emit_results (query, result->records);
+ else if (result->type == CACHE_NO_SUCH_NAME)
+ emit_no_such_name (query);
+
+ cache_result_free (result);
+ return TRUE;
+}
+
+static void
+dns_query_ask_server (DnsQuery *query)
+{
+ Message *question;
+
+ g_return_if_fail (query != NULL);
+ g_return_if_fail (query->current_server != NULL);
+
+ if (query->timeout)
+ {
+ g_source_remove (query->timeout);
+ query->timeout = 0;
+ }
+
+ question = message_new_question (query->current_name, query->type);
+
+#if 0
+ g_print ("QUESTION WITH ID: %d\n", question->id);
+#endif
+ query->current_id = question->id;
+
+ query->timeout = g_timeout_add (
+ dns_config_get_timeout (), handle_timeout, query);
+
+ name_server_query (query->current_server, query->use_tcp,
+ question, analyze_reply, dns_query_ref (query));
+
+
+ message_free (question);
+}
+
+static gboolean
+dns_query_next_server (DnsQuery *query)
+{
+ g_return_val_if_fail (query != NULL, FALSE);
+
+#if 0
+ g_print ("next server called on %p\n", query);
+#endif
+
+ if (query->current_server)
+ g_queue_push_tail (query->tried_servers, query->current_server);
+
+ if (g_queue_is_empty (query->untried_servers))
+ {
+ ++query->attempts;
+
+ if (query->attempts < dns_config_get_attempts())
+ {
+ GQueue *tmp = query->tried_servers;
+ query->tried_servers = query->untried_servers;
+ query->untried_servers = tmp;
+ }
+ }
+
+ query->current_server = g_queue_pop_head (query->untried_servers);
+
+ if (!query->current_server)
+ return FALSE;
+
+ return TRUE;
+}
+
+static gboolean
+dns_query_next_name (DnsQuery *query)
+{
+ query->current_name = g_queue_pop_head (query->untried_names);
+
+ if (!query->current_name)
+ return FALSE;
+
+ query->attempts = 0;
+ return TRUE;
+}
+
+/*
+ * DnsQuery public functions
+ */
+static guint
+count_dots (const gchar *s)
+{
+ guint n_dots = 0;
+ while (*s)
+ {
+ if (*s++ == '.')
+ n_dots++;
+ }
+ return n_dots;
+}
+
+static GQueue *
+build_names (const gchar *domain_str)
+{
+ GQueue *search_list = dns_config_get_search_list();
+ GQueue *try_names;
+ DomainName *absolute_name;
+ guint config_n_dots;
+ guint dots;
+ GList *list;
+
+ g_return_val_if_fail (domain_str != NULL, NULL);
+
+ try_names = g_queue_new ();
+
+ absolute_name = domain_name_new_from_str (domain_str);
+
+ g_assert (absolute_name);
+
+ config_n_dots = dns_config_get_n_dots ();
+ dots = count_dots (domain_str);
+
+ /* if input contains more than the configured n_dots,
+ * try as absolute name before search list ...
+ */
+ if (dots >= config_n_dots)
+ g_queue_push_tail (try_names, absolute_name);
+
+ /* ignore search list if input ends in a dot
+ */
+ if (search_list &&
+ domain_str[strlen (domain_str) - 1] != '.')
+ {
+ for (list = search_list->head; list; list = list->next)
+ {
+ gchar *search_path = list->data;
+ gchar *str;
+ DomainName *name;
+
+ str = g_strconcat (domain_str, ".", search_path, NULL);
+
+ name = domain_name_new_from_str (str);
+ if (name)
+ g_queue_push_tail (try_names, name);
+
+ g_free (str);
+ }
+ }
+
+ /* ... otherwise try as absolute name after the search list
+ */
+ if (dots < config_n_dots)
+ g_queue_push_tail (try_names, absolute_name);
+
+ /* free search list */
+ for (list = search_list->head; list != NULL; list = list->next)
+ g_free (list->data);
+ g_queue_free (search_list);
+
+ g_assert (!g_queue_is_empty (try_names));
+ return try_names;
+}
+
+static guint
+dns_query_hash (gconstpointer data)
+{
+ const DnsQuery *query = data;
+ guint hash;
+ GList *list;
+
+ hash = query->type;
+
+ for (list = query->all_names->head; list != NULL; list = list->next)
+ hash += domain_name_hash (list->data);
+
+ for (list = query->all_servers->head; list != NULL; list = list->next)
+ hash += g_direct_hash (list->data);
+
+ return hash;
+}
+
+static gboolean
+dns_query_equal (gconstpointer data1, gconstpointer data2)
+{
+ const DnsQuery *query1 = data1;
+ const DnsQuery *query2 = data2;
+
+ GList *list1, *list2;
+
+ /* check that the queries are of the same type */
+ if (query1->type != query2->type)
+ return FALSE;
+
+ /* check that the queries are trying the same names in the same order */
+ if (query1->all_names->length != query2->all_names->length)
+ return FALSE;
+
+ list1 = query1->all_names->head;
+ list2 = query2->all_names->head;
+ while (list1 && list2)
+ {
+ DomainName *d1 = list1->data;
+ DomainName *d2 = list2->data;
+
+ if (!domain_name_equal (d1, d2))
+ return FALSE;
+
+ list1 = list1->next;
+ list2 = list2->next;
+ }
+
+ /* check that the queries are trying the same servers in the same order */
+ if (query1->all_servers->length != query2->all_servers->length)
+ return FALSE;
+
+ list1 = query1->all_servers->head;
+ list2 = query2->all_servers->head;
+ while (list1 && list2)
+ {
+ NameServer *n1 = list1->data;
+ NameServer *n2 = list2->data;
+
+ if (n1 != n2)
+ return FALSE;
+
+ list1 = list1->next;
+ list2 = list2->next;
+ }
+
+ return TRUE;
+}
+
+static GQueue *
+copy_queue (GQueue *queue)
+{
+ GQueue *copy = g_queue_new ();
+ GList *list;
+
+ for (list = queue->head; list != NULL; list = list->next)
+ g_queue_push_tail (copy, list->data);
+
+ return copy;
+}
+
+static void
+dns_query_free (DnsQuery *query)
+{
+ GList *list;
+
+ g_assert (g_queue_is_empty (query->uncachable_records));
+
+ g_queue_free (query->uncachable_records);
+
+ for (list = query->callbacks; list != NULL; list = list->next)
+ g_free (list->data);
+ g_list_free (query->callbacks);
+
+ while (!g_queue_is_empty (query->all_names))
+ domain_name_free (g_queue_pop_head (query->all_names));
+
+ g_queue_free (query->all_names);
+ g_queue_free (query->all_servers);
+
+ g_queue_free (query->untried_names);
+ g_queue_free (query->tried_names);
+
+ g_queue_free (query->untried_servers);
+ g_queue_free (query->tried_servers);
+
+ g_free (query);
+}
+
+static void
+dns_query_unref (DnsQuery *query)
+{
+ if (--query->ref_count == 0)
+ dns_query_free (query);
+}
+
+static DnsQuery *
+dns_query_ref (DnsQuery *query)
+{
+ ++query->ref_count;
+ return query;
+}
+
+static gboolean
+domain_is_well_formed (const gchar *domain)
+{
+ DomainName *dname = domain_name_new_from_str (domain);
+
+ if (!dname)
+ return FALSE;
+
+ domain_name_free (dname);
+ return TRUE;
+}
+
+void
+dns_query (const gchar *domain,
+ RRType type,
+ DnsQueryEventFunc func,
+ gpointer data)
+{
+ DnsQuery *query;
+ DnsQuery *existing;
+ Callback *callback;
+
+ g_return_if_fail (domain != NULL);
+ g_return_if_fail (strlen (domain) > 0);
+ g_return_if_fail (func != NULL);
+
+ if (!domain_is_well_formed (domain))
+ {
+ DnsQueryEvent event;
+
+ GError *err = g_error_new (
+ LAC_DNS_ERROR, LAC_DNS_ERROR_MALFORMED_NAME,
+ "The string %s is not a valid domain name", domain);
+
+ event.type = DNS_QUERY_EVENT_ERROR;
+ event.error.err = err;
+
+ func (&event, data);
+
+ g_error_free (err);
+ return;
+ }
+
+ if (!outstanding_queries)
+ {
+ outstanding_queries =
+ g_hash_table_new (dns_query_hash, dns_query_equal);
+ }
+
+ query = g_new (DnsQuery, 1);
+
+ query->type = type;
+ query->all_servers = dns_config_get_servers ();
+ query->all_names = build_names (domain);
+
+ callback = g_new (Callback, 1);
+ callback->func = func;
+ callback->data = data;
+
+ existing = g_hash_table_lookup (outstanding_queries, query);
+ if (existing)
+ {
+#if 0
+ g_print ("using existing (%p)\n", existing);
+#endif
+ existing->callbacks = g_list_prepend (existing->callbacks, callback);
+ while (!g_queue_is_empty (query->all_names))
+ domain_name_free (g_queue_pop_head (query->all_names));
+
+ g_queue_free (query->all_names);
+ g_queue_free (query->all_servers);
+
+ g_free (query);
+ }
+ else
+ {
+ query->callbacks = g_list_prepend (NULL, callback);
+
+ query->uncachable_records = g_queue_new ();
+
+ query->untried_names = copy_queue (query->all_names);
+ query->current_name = NULL;
+ query->tried_names = g_queue_new();
+
+ query->untried_servers = copy_queue (query->all_servers);
+ query->current_server = NULL;
+ query->tried_servers = g_queue_new();
+
+ query->current_id = 0;
+ query->attempts = 0;
+
+ query->timeout = 0;
+
+ query->use_tcp = FALSE;
+ query->ref_count = 1;
+
+ dns_query_next_name (query);
+ dns_query_next_server (query);
+
+#if 0
+ g_print ("inserting %p\n", query);
+#endif
+
+ g_hash_table_insert (outstanding_queries, query, query);
+
+ if (!dns_query_cache_lookup (query))
+ dns_query_ask_server (query);
+ }
+}
diff --git a/src/lacdns-query.h b/src/lacdns-query.h
new file mode 100644
index 0000000..c10b53e
--- /dev/null
+++ b/src/lacdns-query.h
@@ -0,0 +1,78 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- */
+
+/* Lac - Library for asynchronous communication
+ * Copyright (C) 2002 Søren Sandmann (sandmann@daimi.au.dk)
+ *
+ * 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 LACDNS_QUERY_H
+#define LACDNS_QUERY_H
+
+#include "lac.h"
+#include "lacdns-messages.h"
+#include "lacdns-nameserver.h"
+#include "lacinternals.h"
+
+typedef struct DnsQueryEventResults DnsQueryEventResults;
+typedef struct DnsQueryEventNoSuchName DnsQueryEventNoSuchName;
+typedef struct DnsQueryEventNoData DnsQueryEventNoData;
+typedef struct DnsQueryEventError DnsQueryEventError;
+typedef union DnsQueryEvent DnsQueryEvent;
+
+typedef void (* DnsQueryEventFunc) (const DnsQueryEvent *event,
+ gpointer data);
+
+typedef enum {
+ DNS_QUERY_EVENT_RESULTS,
+ DNS_QUERY_EVENT_NO_SUCH_NAME,
+ DNS_QUERY_EVENT_NO_DATA,
+ DNS_QUERY_EVENT_ERROR,
+} DnsQueryEventType;
+
+struct DnsQueryEventResults {
+ DnsQueryEventType type;
+ const GList * results;
+};
+
+struct DnsQueryEventNoSuchName {
+ DnsQueryEventType type;
+};
+
+struct DnsQueryEventNoData {
+ DnsQueryEventType type;
+};
+
+struct DnsQueryEventError {
+ DnsQueryEventType type;
+ const GError * err;
+};
+
+union DnsQueryEvent {
+ DnsQueryEventType type;
+ DnsQueryEventResults results;
+ DnsQueryEventNoSuchName no_such_name;
+ DnsQueryEventNoData no_data;
+ DnsQueryEventError error;
+};
+
+
+void dns_query (const gchar *domain,
+ RRType type,
+ DnsQueryEventFunc func,
+ gpointer data);
+
+#endif /* LACDNS_QUERY_H */
diff --git a/src/lachttp.c b/src/lachttp.c
new file mode 100644
index 0000000..c969c8a
--- /dev/null
+++ b/src/lachttp.c
@@ -0,0 +1,2588 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- */
+
+/* Lac - Library for asynchronous communication
+ * Copyright (C) 2001, 2002, 2003 Søren Sandmann (sandmann@daimi.au.dk)
+ *
+ * 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.
+ */
+
+#include "lac.h"
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+
+/*
+ * HTTP RFC: 2616
+ *
+ * cookies rfc: 2109
+ */
+
+#define CR 13
+#define LF 10
+static const gchar CRLF[] = { CR, LF, '\0' };
+
+#define USE_PIPELINING 1
+
+enum {
+ MAX_ONE_SHOT_CONNECTIONS = 8,
+ WRITE_BUFFER_TIMEOUT = 50,
+ ANSWER_TIMEOUT = 6000, /* after this long without hearing
+ * anything, we assume the server has
+ * choked on the pipeline and retry all
+ * outstanding requests.
+ */
+ MAX_LINE = 104096 /* we won't accept headers or status
+ * lines longer than this
+ */
+};
+
+typedef enum {
+ GET_REQUEST,
+ HEAD_REQUEST,
+ DELETE_REQUEST,
+ POST_REQUEST,
+ PUT_REQUEST,
+} RequestType;
+
+typedef struct _HttpHeader HttpHeader;
+typedef struct _HttpHost HttpHost;
+typedef struct _HttpTransport HttpTransport;
+
+struct _HttpHeader
+{
+ gchar *header;
+ gchar *value;
+};
+
+#define junk_detected() g_print (G_STRLOC ": WARNING: junk detected after response")
+
+GQuark
+lac_http_error_quark (void)
+{
+ static GQuark q = 0;
+
+ if (q == 0)
+ q = g_quark_from_static_string ("lac-http-error-quark");
+
+ return q;
+}
+
+/*
+ * HttpRequest declaration
+ */
+typedef enum {
+ INITIAL,
+ EXPECT_STATUS_LINE,
+ EXPECT_HEADER,
+ EXPECT_CHUNK_HEADER,
+ EXPECT_CHUNK_DATA,
+ EXPECT_CHUNK_TRAILER,
+ EXPECT_LENGTH_DELIMITED_BODY,
+ EXPECT_CLOSE_DELIMITED_BODY,
+ DONE,
+} RequestParseState;
+
+typedef enum {
+ NEED_MORE_DATA,
+ OK,
+ ERROR,
+} ParseResult;
+
+struct _LacHttpRequest {
+ RequestType type;
+ LacUri * uri;
+ LacHttpFunc callback;
+
+ GList * headers;
+
+ GString * content;
+
+ HttpHost * host;
+ gboolean canceled;
+
+ gint ref_count;
+ gpointer data;
+
+ gboolean in_emit_events;
+ GQueue * pending_events;
+
+ /* transport */
+ HttpTransport * transport;
+
+ /* parsing */
+ GString * unparsed;
+ gsize n_content_bytes;
+ gsize n_ignore_bytes;
+ RequestParseState parser_state;
+
+ gboolean seen_status_line;
+ gboolean seen_connection_close;
+ gboolean seen_connection_keep_alive;
+ gboolean close_delimited_body;
+ gboolean seen_transfer_encoding_chunked;
+
+ gboolean sent_begin_content;
+ gssize content_length;
+ gssize current_chunk_size;
+ gint status_code;
+ gint major;
+ gint minor;
+};
+
+static LacHttpRequest *request_alloc (const LacUri *uri,
+ LacHttpFunc func,
+ gpointer data);
+static void request_destroy (LacHttpRequest *request);
+static gchar * request_serialize (LacHttpRequest *request,
+ const gchar *connection);
+static gssize request_parse_response (LacHttpRequest *request,
+ guint len,
+ const guint8 *data);
+static void request_parse_end_of_file (LacHttpRequest *request);
+static gboolean request_done_parsing (LacHttpRequest *request);
+static gboolean request_done_parsing_headers (LacHttpRequest *request);
+static gboolean request_connection_will_close (LacHttpRequest *request);
+static void request_restart_parsing (LacHttpRequest *request);
+static void request_emit_events (LacHttpRequest *request);
+static void request_emit_connecting (LacHttpRequest *request);
+static void request_emit_sent (LacHttpRequest *request);
+static void request_emit_host_found (LacHttpRequest *request,
+ const LacAddress *addr);
+static void request_emit_status_line (LacHttpRequest *request,
+ gint major,
+ gint minor,
+ gint status_code,
+ const gchar *reason_phrase);
+static void request_emit_no_status_line (LacHttpRequest *request);
+static void request_emit_header (LacHttpRequest *request,
+ const gchar *header,
+ const gchar *value);
+static void request_emit_begin_content (LacHttpRequest *request,
+ gint length);
+static void request_emit_content (LacHttpRequest *request,
+ gint length,
+ const guint8 *data);
+static void request_emit_end_content (LacHttpRequest *request);
+static void request_emit_no_content (LacHttpRequest *request);
+static void request_emit_error (LacHttpRequest *request,
+ const GError *err);
+static ParseResult request_parse_close_delimited_body (LacHttpRequest *request);
+static ParseResult request_parse_status_line (LacHttpRequest *request);
+static ParseResult request_parse_header (LacHttpRequest *request);
+static ParseResult request_parse_chunk_header (LacHttpRequest *request);
+static ParseResult request_parse_chunk_data (LacHttpRequest *request);
+static ParseResult request_parse_chunk_trailer (LacHttpRequest *request);
+static ParseResult request_parse_length_delimited_body (LacHttpRequest *request);
+static ParseResult request_parse_close_delimited_body (LacHttpRequest *request);
+static gssize request_parse_response (LacHttpRequest *request,
+ gsize len,
+ const guint8 *data);
+
+
+/*
+ * HttpHost declaration
+ */
+struct _HttpHost {
+ LacAddress * address;
+ gint port;
+
+ GPtrArray * pipelines;
+ GPtrArray * one_shots;
+
+ GQueue * unsent;
+ gboolean optimistic; /* whether newly created transports are pipelines */
+
+ /* handling broken servers */
+ gboolean broken_server; /* if TRUE, _never_ try pipelining */
+ gboolean recovering; /* if TRUE, don't create new connections. */
+ guint timeout_id;
+
+ gchar *name;
+};
+
+static HttpHost *http_host_new (const LacAddress *addr,
+ gint port);
+static void http_host_add_request (HttpHost *host,
+ LacHttpRequest *request);
+static void http_host_cancel_request (HttpHost *host,
+ LacHttpRequest *request);
+static void http_host_transport_will_close_notify (HttpHost *host,
+ HttpTransport *transport);
+static void http_host_transport_cant_pipeline_notify (HttpHost *host,
+ HttpTransport *transport);
+static void http_host_transport_broken_server_notify (HttpHost *host,
+ HttpTransport *transport,
+ guint recover_time);
+static void http_host_transport_can_pipeline_notify (HttpHost *host,
+ HttpTransport *transport);
+static void http_host_transport_activity_notify (HttpHost *host,
+ HttpTransport *transport);
+static void http_host_transport_closed_notify (HttpHost *host,
+ HttpTransport *transport);
+
+/*
+ * HttpTransport declaration
+ */
+struct _HttpTransport {
+ HttpHost * host;
+ LacConnection * connection;
+ GQueue * unsent;
+ GQueue * in_progress;
+ LacHttpRequest * current;
+ gboolean pipelining;
+ gboolean broken_server;
+ guint successful_requests;
+ GString * write_buffer;
+ gint write_timeout;
+ gint answer_timeout;
+ gboolean did_pipeline; /* if TRUE, we did pipeline at some point,
+ * so the server may have choked even
+ * if we are not pipelining any longer
+ */
+#if 0
+ gint debug_timeout;
+#endif
+};
+
+static HttpTransport *http_transport_new (HttpHost *host,
+ gboolean pipelining,
+ gboolean broken);
+static void http_transport_destroy (HttpTransport *connection);
+static void http_transport_add_request (HttpTransport *connection,
+ LacHttpRequest *request);
+static void http_transport_cancel_request (HttpTransport *connection,
+ LacHttpRequest *request);
+
+
+/*
+ * HttpRequest implementation
+ */
+
+static void
+request_initialize_parser_state (LacHttpRequest *request)
+{
+ request->unparsed = g_string_new ("");
+ request->parser_state = INITIAL;
+
+ request->n_content_bytes = 0;
+ request->n_ignore_bytes = 0;
+
+ request->seen_status_line = FALSE;
+ request->seen_connection_close = FALSE;
+ request->seen_connection_keep_alive = FALSE;
+ request->seen_transfer_encoding_chunked = FALSE;
+
+ request->sent_begin_content = FALSE;
+ request->content_length = -1;
+ request->current_chunk_size = -1;
+ request->status_code = 0;
+}
+
+static LacHttpRequest *
+request_alloc (const LacUri *uri, LacHttpFunc func, gpointer data)
+{
+ LacHttpRequest *request = g_new0 (LacHttpRequest, 1);
+
+ request->uri = lac_uri_copy (uri);
+ request->callback = func;
+
+ request->headers = NULL;
+
+ request->content = g_string_new ("");
+
+ request->host = NULL;
+ request->canceled = FALSE;
+
+ request->ref_count = 1;
+ request->data = data;
+
+ request->in_emit_events = FALSE;
+ request->pending_events = g_queue_new ();
+
+ /* transport */
+ request->transport = NULL;
+
+ /* parsing state */
+ request_initialize_parser_state (request);
+
+ return request;
+}
+
+static void
+request_restart_parsing (LacHttpRequest *request)
+{
+ g_string_free (request->unparsed, TRUE);
+ request_initialize_parser_state (request);
+}
+
+static void
+http_event_free (LacHttpEvent *event)
+{
+ switch (event->type)
+ {
+ case LAC_HTTP_EVENT_HOST_FOUND:
+ lac_address_free ((LacAddress *)event->host_found.address);
+ break;
+
+ case LAC_HTTP_EVENT_STATUS_LINE:
+ g_free ((gchar *)event->status_line.reason_phrase);
+ break;
+
+ case LAC_HTTP_EVENT_HEADER:
+ g_free ((gchar *)event->header.header);
+ g_free ((gchar *)event->header.value);
+ break;
+
+ case LAC_HTTP_EVENT_CONTENT:
+ g_free ((guint8 *)event->content.data);
+ break;
+
+ case LAC_HTTP_EVENT_ERROR:
+ g_error_free ((GError *)event->error.err);
+ break;
+
+ default:
+ break;
+ }
+
+ g_free (event);
+}
+
+static void
+request_destroy (LacHttpRequest *request)
+{
+ LacHttpEvent *event;
+ GList *list;
+
+ lac_uri_free (request->uri);
+
+ for (list = request->headers; list != NULL; list = list->next)
+ {
+ HttpHeader *header = list->data;
+ g_free (header->header);
+ g_free (header->value);
+ g_free (header);
+ }
+
+ g_list_free (request->headers);
+ g_string_free (request->content, TRUE);
+ g_string_free (request->unparsed, TRUE);
+
+ while ((event = g_queue_pop_head (request->pending_events)))
+ http_event_free (event);
+ g_queue_free (request->pending_events);
+
+ g_free (request);
+}
+
+static gchar *
+escape_spaces (gchar *string)
+{
+ gint i, j;
+ gint n_spaces;
+ gchar *result;
+
+ n_spaces = 0;
+ for (i = 0; string[i] != '\0'; ++i)
+ if (string[i] == ' ')
+ ++n_spaces;
+
+ result = g_malloc (strlen (string) + 2 * n_spaces + 1);
+
+ j = 0;
+ for (i = 0; string[i] != '\0'; ++i)
+ {
+ if (string[i] == ' ')
+ {
+ result[j++] = '%';
+ result[j++] = '2';
+ result[j++] = '0';
+ }
+ else
+ {
+ result[j++] = string[i];
+ }
+ }
+ result[j++] = '\0';
+
+ g_free (string);
+ return result;
+}
+
+static gchar *
+request_serialize (LacHttpRequest *request, const gchar *connection)
+{
+ GString *result;
+ gchar *uri_str;
+
+ const guint8 *method = "";
+ GList *list;
+
+ result = g_string_new ("");
+
+ switch (request->type)
+ {
+ case GET_REQUEST:
+ method = "GET";
+ break;
+
+ case PUT_REQUEST:
+ method = "PUT";
+ break;
+
+ case DELETE_REQUEST:
+ method = "DELETE";
+ break;
+
+ case POST_REQUEST:
+ method = "POST";
+ break;
+
+ case HEAD_REQUEST:
+ method = "HEAD";
+ break;
+ }
+
+ uri_str = escape_spaces (
+ g_strdup_printf (
+ "%s%s%s",
+ request->uri->u.http.path,
+ request->uri->u.http.query? "?" : "",
+ request->uri->u.http.query? request->uri->u.http.query : ""));
+
+ g_string_append_printf (
+ result,
+ "%s %s HTTP/1.1%s"
+ "Host: %s%s"
+ "%s%s"
+ "User-Agent: Lac (sandmann@daimi.au.dk)%s",
+
+ method, uri_str, CRLF,
+
+ request->uri->u.http.host, CRLF,
+
+ connection? connection : "", connection? CRLF : "",
+
+ CRLF);
+
+ g_free (uri_str);
+
+ for (list = request->headers; list != NULL; list = list->next)
+ {
+ HttpHeader *header = list->data;
+
+ g_string_append_printf (
+ result, "%s: %s%s", header->header, header->value, CRLF);
+ }
+
+ g_string_append_printf (result, CRLF);
+
+ return g_string_free (result, FALSE);
+}
+
+static ParseResult
+request_get_line (LacHttpRequest *request, gchar **result)
+{
+ gchar *lf_pos;
+ gsize line_len;
+
+#if 0
+ g_print ("unparsed: %s\n", request->unparsed->str);
+#endif
+ lf_pos = strchr (request->unparsed->str, LF);
+
+ if (!lf_pos)
+ {
+ *result = NULL;
+
+ if (request->unparsed->len > MAX_LINE ||
+ request->unparsed->len > strlen (request->unparsed->str))
+ {
+ /* request->unparsed->len > strlen (request->unparsed->str)
+ * means that we have received a '\0' byte
+ */
+ return ERROR;
+ }
+
+ return NEED_MORE_DATA;
+ }
+
+ line_len = lf_pos - request->unparsed->str;
+
+ *result = g_strstrip (g_strndup (request->unparsed->str, line_len));
+
+ /* delete up to and including the LF */
+ g_string_erase (request->unparsed, 0, line_len + 1);
+
+ return OK;
+}
+
+static gint
+eat_spaces (const gchar **input)
+{
+ gint retval = 0;
+
+ while (g_ascii_isspace (*(*input)))
+ {
+ retval++;
+ (*input)++;
+ }
+
+ return retval;
+}
+
+static gboolean
+empty_line (const gchar *line)
+{
+ const gchar *s;
+ gboolean spaces_only = TRUE;
+
+ for (s = line; *s != '\0'; ++s)
+ if (!g_ascii_isspace (*s))
+ {
+ spaces_only = FALSE;
+ break;
+ }
+
+ return spaces_only;
+}
+
+static gboolean
+parse_version_number (const gchar **input, gint *major, gint *minor)
+{
+ const gchar *in = *input;
+ gchar *endptr;
+ gint maj, min;
+
+ /* slash */
+ if (*in++ != '/')
+ return FALSE;
+
+ /* major */
+ maj = strtol (in, &endptr, 10);
+ if (endptr == in)
+ return FALSE;
+ in = endptr;
+
+ /* dot */
+ if (*in++ != '.')
+ return FALSE;
+
+ /* minor */
+ min = strtol (in, &endptr, 10);
+ if (endptr == in)
+ return FALSE;
+ in = endptr;
+
+ if (major)
+ *major = maj;
+ if (minor)
+ *minor = min;
+
+ *input = in;
+
+ return TRUE;
+}
+
+static ParseResult
+real_parse_status_line (LacHttpRequest *request, const char *input)
+{
+ gchar *endptr;
+
+ /* HTTP */
+ if (strncmp (input, "HTTP", 4) != 0)
+ return ERROR;
+ input += 4;
+
+ /* version number */
+ if (!parse_version_number (&input, &request->major, &request->minor))
+ {
+ request->major = 1;
+ request->minor = 0;
+ }
+
+ /* status code */
+ eat_spaces (&input);
+
+ request->status_code = strtol (input, &endptr, 10);
+ if (endptr == input)
+ return ERROR;
+ input = endptr;
+
+ /* it is ok for the server to leave out the reason phrase
+ */
+ eat_spaces (&input);
+
+ /* reason phrase is rest of input */
+ request_emit_status_line (
+ request, request->major, request->minor, request->status_code, input);
+ request->parser_state = EXPECT_HEADER;
+
+ return OK;
+}
+
+static ParseResult
+request_parse_status_line (LacHttpRequest *request)
+{
+ gchar *status_line;
+ ParseResult result;
+
+ if (request->unparsed->len < 4)
+ return NEED_MORE_DATA;
+
+ /* Check for HTTP 0.9 */
+ if (strncmp (request->unparsed->str, "HTTP", 4) != 0)
+ {
+ request_emit_no_status_line (request);
+ request->parser_state = EXPECT_CLOSE_DELIMITED_BODY;
+ return request_parse_close_delimited_body (request);
+ }
+
+ result = request_get_line (request, &status_line);
+
+ if (result == OK)
+ {
+ result = real_parse_status_line (request, status_line);
+
+ if (result == ERROR)
+ {
+ gchar *s;
+ g_print ("error in status line for %s\n",
+ lac_uri_string (request->uri));
+
+ g_print ("the status line follows:\n");
+ for (s = status_line; *s != '\0'; ++s)
+ g_print ("%c (%d)\n", *s, *s);
+ }
+
+ g_free (status_line);
+ }
+
+ return result;
+}
+
+static ParseResult
+real_parse_header (LacHttpRequest *request, const gchar *input)
+{
+ gchar *header_name;
+ gchar *header_content;
+ const gchar *colon;
+
+ if (empty_line (input))
+ {
+ /* see RFC 2616 [HTTP], section 4.4
+ */
+ if (request->type == HEAD_REQUEST ||
+ ((request->status_code < 200 && request->status_code >= 100) ||
+ request->status_code == 204 ||
+ request->status_code == 304))
+ {
+ request_emit_no_content (request);
+ request->parser_state = DONE;
+ }
+ else if (request->seen_transfer_encoding_chunked)
+ {
+ request->parser_state = EXPECT_CHUNK_HEADER;
+ }
+ else if (request->content_length == 0)
+ {
+ request_emit_begin_content (request, 0);
+ request_emit_end_content (request);
+
+ request->parser_state = DONE;
+ }
+ else if (request->content_length > 0)
+ {
+ request->parser_state = EXPECT_LENGTH_DELIMITED_BODY;
+ }
+#if 0
+ else if (byteranges/multipart)
+ {
+ /* FIXME */
+ /* ignore this for now - the server MUST NOT send it anyway,
+ * unless we implied that we would be able to parse it,
+ * which we didn't.
+ */
+ }
+#endif
+ else
+ {
+ request->parser_state = EXPECT_CLOSE_DELIMITED_BODY;
+ request->close_delimited_body = TRUE;
+ }
+
+ return OK;
+ }
+
+ colon = strchr (input, ':');
+ if (!colon)
+ {
+#if 0
+ g_print ("Header line without colon\n");
+#endif
+ return ERROR;
+ }
+
+ header_name = g_strndup (input, colon - input);
+ header_content = g_strdup (colon + 1);
+
+ g_strstrip (header_name);
+ g_strstrip (header_content);
+
+ if (g_ascii_strcasecmp (header_name, "Transfer-Encoding") == 0 &&
+ g_ascii_strcasecmp (header_content, "chunked") == 0)
+ {
+ /* Don't emit "Transfer-Encoding: chunked".
+ * Chunked messages are decoded before the application sees them
+ */
+
+ request->seen_transfer_encoding_chunked = TRUE;
+ }
+ else if (g_ascii_strcasecmp (header_name, "Content-Length") == 0)
+ {
+ gchar *endptr;
+
+ request->content_length = strtol (header_content, &endptr, 10);
+ if (endptr != header_content + strlen (header_content))
+ {
+ /* The Content-Length header is garbage, ignore it. */
+#if 0
+ g_print ("-------------> content_length is garbage\n");
+#endif
+ request->content_length = -1;
+ }
+#if 0
+ else
+ g_print ("content-length: %d\n", request->content_length);
+#endif
+ }
+ else if (g_ascii_strcasecmp (header_name, "Connection") == 0)
+ {
+ if (g_ascii_strcasecmp (header_content, "close") == 0)
+ {
+#if 0
+ g_print ("CONNECTION: CLOSE\n");
+#endif
+ request->seen_connection_close = TRUE;
+ }
+ else if (g_ascii_strcasecmp (header_content, "keep-alive") == 0)
+ {
+#if 0
+ g_print ("CONNECTION: KEEP-ALIVE\n");
+#endif
+ request->seen_connection_keep_alive = TRUE;
+ }
+ }
+ else
+ {
+ request_emit_header (request, header_name, header_content);
+ }
+
+ g_free (header_name);
+ g_free (header_content);
+
+ return OK;
+}
+
+static ParseResult
+request_parse_header (LacHttpRequest *request)
+{
+ gchar *header_line;
+ ParseResult result;
+
+ result = request_get_line (request, &header_line);
+ if (result == OK)
+ {
+ result = real_parse_header (request, header_line);
+ g_free (header_line);
+ }
+
+ return result;
+}
+
+static ParseResult
+real_parse_chunk_header (LacHttpRequest *request, const gchar *input)
+{
+ gchar *endptr;
+
+ /* size */
+ request->current_chunk_size = strtol (input, &endptr, 16);
+ if (endptr == input)
+ {
+#if 0
+ g_print ("bad chunk header\n");
+#endif
+ return ERROR;
+ }
+
+ /* ignore extensions - we won't understand them anyway */
+
+ if (request->current_chunk_size == 0)
+ request->parser_state = EXPECT_CHUNK_TRAILER;
+ else
+ request->parser_state = EXPECT_CHUNK_DATA;
+
+ return OK;
+}
+
+static ParseResult
+request_parse_chunk_header (LacHttpRequest *request)
+{
+ gchar *chunk_header_line;
+ ParseResult result;
+
+ result = request_get_line (request, &chunk_header_line);
+ if (result == OK)
+ {
+ result = real_parse_chunk_header (request, chunk_header_line);
+ g_free (chunk_header_line);
+ }
+
+ return result;
+}
+
+static ParseResult
+request_parse_chunk_data (LacHttpRequest *request)
+{
+ g_assert (request->current_chunk_size > 0);
+
+ /* We want either enough data to finish the chunk completely,
+ * including the final CRLF, or we want strictly less than a
+ * complete chunk. That way we won't have to deal with
+ * dangling CRLF's
+ */
+ if (request->unparsed->len >= request->current_chunk_size + 2)
+ {
+ /* FIXME: should we also accept a single LF here? */
+ if (request->unparsed->str[request->current_chunk_size] == CR &&
+ request->unparsed->str[request->current_chunk_size + 1] == LF)
+ {
+ request_emit_content (
+ request, request->current_chunk_size, request->unparsed->str);
+
+ g_string_erase (
+ request->unparsed, 0, request->current_chunk_size + 2);
+
+ request->parser_state = EXPECT_CHUNK_HEADER;
+ return OK;
+ }
+ else
+ {
+#if 0
+ g_print ("chunk data not followed by CRLF\n");
+#endif
+ return ERROR;
+ }
+ }
+ else
+ {
+ if (request->unparsed->len < request->current_chunk_size)
+ {
+ request->current_chunk_size -= request->unparsed->len;
+
+ if (!request->sent_begin_content)
+ request_emit_begin_content (request, -1);
+
+ request_emit_content (
+ request, request->unparsed->len, request->unparsed->str);
+
+ g_string_truncate (request->unparsed, 0);
+ }
+
+ return NEED_MORE_DATA;
+ }
+}
+
+static ParseResult
+request_parse_chunk_trailer (LacHttpRequest *request)
+{
+ gchar *line;
+ ParseResult result;
+
+ g_assert (request->current_chunk_size == 0);
+
+ request_emit_end_content (request);
+
+ /* FIXME: for now we ignore trailing headers */
+ result = request_get_line (request, &line);
+
+ if (result == OK)
+ {
+ if (empty_line (line))
+ request->parser_state = DONE;
+
+ g_free (line);
+ }
+
+ return result;
+}
+
+static ParseResult
+request_parse_length_delimited_body (LacHttpRequest *request)
+{
+ gsize emit_size;
+
+ g_assert (request->content_length > request->n_content_bytes);
+
+ if (!request->sent_begin_content)
+ request_emit_begin_content (request, request->content_length);
+
+ emit_size =
+ MIN (request->unparsed->len,
+ request->content_length - request->n_content_bytes);
+
+ request_emit_content (
+ request, emit_size, request->unparsed->str);
+ g_string_erase (request->unparsed, 0, emit_size);
+
+ g_assert (request->n_content_bytes <= request->content_length);
+
+#if 0
+ g_print ("so far: %d\n", request->n_content_bytes);
+#endif
+
+ if (request->n_content_bytes == request->content_length)
+ {
+ request_emit_end_content (request);
+ request->parser_state = DONE;
+ return OK;
+ }
+
+ return NEED_MORE_DATA;
+}
+
+static ParseResult
+request_parse_close_delimited_body (LacHttpRequest *request)
+{
+ request_emit_content (
+ request, request->unparsed->len, request->unparsed->str);
+ g_string_truncate (request->unparsed, 0);
+
+ return NEED_MORE_DATA;
+}
+
+/* return number of bytes used, or -1 on error
+ */
+static gssize
+request_parse_response (LacHttpRequest *request,
+ gsize len,
+ const guint8 *data)
+{
+ ParseResult result;
+
+#if 0
+ int i;
+ for (i = 0; i < len; ++i)
+ g_print ("%c", data[i]);
+ g_print ("received: %d\n", len);
+#endif
+
+ g_string_append_len (request->unparsed, data, len);
+
+ while (request->unparsed->len > 0)
+ {
+#if 0
+ g_print ("unparsed: %d\n", request->unparsed->len);
+#endif
+
+ result = ERROR;
+
+ switch (request->parser_state)
+ {
+ case INITIAL:
+ request->parser_state = EXPECT_STATUS_LINE;
+ result = OK;
+ break;
+
+ case EXPECT_STATUS_LINE:
+ result = request_parse_status_line (request);
+ break;
+
+ case EXPECT_HEADER:
+ result = request_parse_header (request);
+ break;
+
+ case EXPECT_CHUNK_DATA:
+ result = request_parse_chunk_data (request);
+ break;
+
+ case EXPECT_CHUNK_TRAILER:
+ result = request_parse_chunk_trailer (request);
+ break;
+
+ case EXPECT_CHUNK_HEADER:
+ result = request_parse_chunk_header (request);
+ break;
+
+ case EXPECT_LENGTH_DELIMITED_BODY:
+ result = request_parse_length_delimited_body (request);
+ break;
+
+ case EXPECT_CLOSE_DELIMITED_BODY:
+ result = request_parse_close_delimited_body (request);
+ break;
+
+ case DONE:
+ /* we are supposed to return how much of the passed in
+ * data we used.
+ *
+ * We just assume that whatever is left in request->unparsed
+ * is the data we didn't use. This works because we are
+ * never in the situation where there is data left in
+ * request->unparsed that were passed in by a previous
+ * invocation of this function. That is, if calling
+ * request_parse_response() causes parsing to finish,
+ * then this will be detected by the same invocation.
+ */
+ g_assert (request->unparsed->len < len);
+
+ return len - request->unparsed->len;
+ break;
+ }
+
+ if (result == NEED_MORE_DATA)
+ return len;
+ else if (result == ERROR)
+ return -1;
+ }
+
+ return len;
+}
+
+static void
+request_parse_end_of_file (LacHttpRequest *request)
+{
+ if (request->parser_state == DONE)
+ return;
+
+ if (request->parser_state == EXPECT_STATUS_LINE)
+ {
+ /* treat as HTTP/0.9 */
+ request_emit_no_status_line (request);
+ request->parser_state = EXPECT_CLOSE_DELIMITED_BODY;
+ }
+
+ if (request->parser_state == EXPECT_CLOSE_DELIMITED_BODY)
+ {
+ if (request->unparsed->len > 0)
+ {
+ request_emit_content (
+ request, request->unparsed->len, request->unparsed->str);
+ g_string_truncate (request->unparsed, 0);
+ }
+ request_emit_end_content (request);
+ request->parser_state = DONE;
+ }
+ else
+ {
+ GError *err = g_error_new (
+ LAC_HTTP_ERROR, LAC_HTTP_ERROR_PREMATURE_CLOSE,
+ "Server closed connection before sending a complete response");
+
+ request_emit_error (request, err);
+ g_error_free (err);
+ }
+}
+
+static gboolean
+request_done_parsing (LacHttpRequest *request)
+{
+ return (request->parser_state == DONE);
+}
+
+static gboolean
+request_done_parsing_headers (LacHttpRequest *request)
+{
+ return (request->parser_state > EXPECT_HEADER);
+}
+
+static gboolean
+request_done_parsing_status_line (LacHttpRequest *request)
+{
+ return (request->parser_state > EXPECT_STATUS_LINE);
+}
+
+static gboolean
+request_seen_keep_alive (LacHttpRequest *request)
+{
+ return request->seen_connection_keep_alive;
+}
+
+/*
+ * Return TRUE if we know the server is at least at HTTP/1.1
+ */
+static gboolean
+request_connection_is_1_1 (LacHttpRequest *request)
+{
+ if (request_done_parsing_status_line (request))
+ {
+ if (request->major > 1)
+ return TRUE;
+
+ if (request->major == 1 && request->minor >= 1)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/*
+ * return TRUE if we know the connection is going to close
+ * (or the server is lying)
+ */
+static gboolean
+request_connection_will_close (LacHttpRequest *request)
+{
+ if (request->seen_connection_close ||
+ request->close_delimited_body)
+ {
+#if 0
+ g_print ("************* expect close since \"connection: close\"\n");
+#endif
+#if 0
+ g_print ("expect close since seen \"connection: close\" "
+ "or request is delimited by close\n");
+#endif
+ return TRUE;
+ }
+ else if (request->seen_connection_keep_alive)
+ {
+#if 0
+ g_print ("*************************\n");
+ g_print (
+ "wont close since seen connection: KEEP ALIVE (%p)\n",
+ request->transport);
+#endif
+ return FALSE;
+ }
+ else if (request_done_parsing_headers (request) &&
+ request->major <= 1 &&
+ request->minor == 0)
+ {
+#if 0
+ g_print ("************* expect close since http <= 1.0 and no keep alive\n");
+#endif
+ return TRUE;
+ }
+ else
+ {
+#if 0
+ g_print ("don't expect close - by default for 1.1\n");
+#endif
+ return FALSE;
+ }
+}
+
+/* emit methods
+ */
+
+static GList *requests_needing_emit = NULL;
+
+static void
+request_emit_events (LacHttpRequest *request)
+{
+ LacHttpEvent *event;
+
+ if (request->in_emit_events)
+ return;
+
+ request->in_emit_events = TRUE;
+ lac_http_request_ref (request);
+
+ while ((event = g_queue_pop_head (request->pending_events)))
+ {
+ if (!request->canceled)
+ request->callback (request, event);
+
+ http_event_free (event);
+ }
+
+ request->in_emit_events = FALSE;
+ lac_http_request_unref (request);
+}
+
+static void
+dispatch_events (void)
+{
+ GList *list;
+ GList *req;
+
+ req = g_list_copy (requests_needing_emit);
+
+ g_list_free (requests_needing_emit);
+ requests_needing_emit = NULL;
+
+ for (list = req; list != NULL; list = list->next)
+ request_emit_events (list->data);
+
+ g_list_free (req);
+}
+
+static void
+request_queue_event (LacHttpRequest *request, LacHttpEvent *event)
+{
+ g_queue_push_tail (request->pending_events, event);
+
+ if (request->pending_events->length == 1)
+ {
+ requests_needing_emit =
+ g_list_prepend (requests_needing_emit, request);
+ }
+}
+
+static void
+request_emit_connecting (LacHttpRequest *request)
+{
+ LacHttpEvent *event = g_new (LacHttpEvent, 1);
+
+ event->type = LAC_HTTP_EVENT_CONNECTING;
+
+ request_queue_event (request, event);
+}
+
+static void
+request_emit_sent (LacHttpRequest *request)
+{
+ LacHttpEvent *event = g_new (LacHttpEvent, 1);
+
+ event->type = LAC_HTTP_EVENT_SENT;
+
+ request_queue_event (request, event);
+}
+
+static void
+request_emit_host_found (LacHttpRequest *request,
+ const LacAddress *addr)
+{
+ LacHttpEvent *event = g_new (LacHttpEvent, 1);
+
+ event->type = LAC_HTTP_EVENT_HOST_FOUND;
+ event->host_found.address = lac_address_copy (addr);
+
+ request_queue_event (request, event);
+}
+
+static void
+request_emit_status_line (LacHttpRequest *request,
+ gint major,
+ gint minor,
+ gint status_code,
+ const gchar *reason_phrase)
+{
+ LacHttpEvent *event = g_new (LacHttpEvent, 1);
+
+ event->type = LAC_HTTP_EVENT_STATUS_LINE;
+ event->status_line.major = major;
+ event->status_line.minor = minor;
+ event->status_line.status_code = status_code;
+ event->status_line.reason_phrase = g_strdup (reason_phrase);
+
+ request_queue_event (request, event);
+}
+
+static void
+request_emit_no_status_line (LacHttpRequest *request)
+{
+ LacHttpEvent *event = g_new (LacHttpEvent, 1);
+
+ event->type = LAC_HTTP_EVENT_NO_STATUS_LINE;
+
+ g_print ("HTTP 0.9 response (%s) %d\n", lac_uri_string (request->uri), *(int *)request->transport->connection);
+
+ request_queue_event (request, event);
+}
+
+static void
+request_emit_header (LacHttpRequest *request,
+ const gchar *header,
+ const gchar *value)
+{
+ LacHttpEvent *event = g_new (LacHttpEvent, 1);
+
+ event->type = LAC_HTTP_EVENT_HEADER;
+ event->header.header = g_strdup (header);
+ event->header.value = g_strdup (value);
+
+ request_queue_event (request, event);
+}
+
+static void
+request_emit_begin_content (LacHttpRequest *request,
+ gint length)
+{
+ LacHttpEvent *event = g_new (LacHttpEvent, 1);
+
+ event->type = LAC_HTTP_EVENT_BEGIN_CONTENT;
+ event->begin_content.length = length;
+
+ request->sent_begin_content = TRUE;
+ request->n_content_bytes = 0;
+
+ request_queue_event (request, event);
+}
+
+static void
+request_emit_content (LacHttpRequest *request,
+ gint length,
+ const guint8 *data)
+{
+ LacHttpEvent *event = g_new (LacHttpEvent, 1);
+ gint bytes_to_emit;
+
+ if (!request->sent_begin_content)
+ request_emit_begin_content (request, -1);
+
+ bytes_to_emit = length;
+
+ if (request->n_content_bytes < request->n_ignore_bytes)
+ {
+ bytes_to_emit = length -
+ (request->n_ignore_bytes - request->n_content_bytes);
+ }
+
+ request->n_content_bytes += length;
+
+ if (bytes_to_emit <= 0)
+ return;
+
+ data = data + (length - bytes_to_emit);
+
+ event->type = LAC_HTTP_EVENT_CONTENT;
+ event->content.length = bytes_to_emit;
+ event->content.data = g_memdup (data, bytes_to_emit);
+
+ request_queue_event (request, event);
+}
+
+static void
+request_emit_no_content (LacHttpRequest *request)
+{
+ LacHttpEvent *event = g_new (LacHttpEvent, 1);
+
+ event->type = LAC_HTTP_EVENT_NO_CONTENT;
+
+ request_queue_event (request, event);
+}
+
+static void
+request_emit_end_content (LacHttpRequest *request)
+{
+ LacHttpEvent *event = g_new (LacHttpEvent, 1);
+
+ event->end_content.type = LAC_HTTP_EVENT_END_CONTENT;
+ event->end_content.total = request->n_content_bytes;
+
+ request_queue_event (request, event);
+}
+
+static void
+request_emit_error (LacHttpRequest *request, const GError *err)
+{
+ LacHttpEvent *event = g_new (LacHttpEvent, 1);
+
+ event->type = LAC_HTTP_EVENT_ERROR;
+ event->error.err = g_error_copy (err);
+
+ request_queue_event (request, event);
+}
+
+/* public interface */
+
+LacHttpRequest *
+lac_http_request_new_get (const LacUri *uri,
+ LacHttpFunc func,
+ gpointer data)
+{
+ LacHttpRequest *request;
+
+ g_return_val_if_fail (uri->scheme == LAC_SCHEME_HTTP, NULL);
+ g_return_val_if_fail (func != NULL, NULL);
+
+#if 0
+ g_print ("//////////////////// NEW URL: >%s<\n", lac_uri_string (uri));
+#endif
+
+ request = request_alloc (uri, func, data);
+ request->type = GET_REQUEST;
+
+ return request;
+}
+
+LacHttpRequest *
+lac_http_request_new_head (const LacUri *uri,
+ LacHttpFunc func,
+ gpointer data)
+{
+ LacHttpRequest *request;
+
+ g_return_val_if_fail (uri->scheme == LAC_SCHEME_HTTP, NULL);
+ g_return_val_if_fail (func != NULL, NULL);
+
+ request = request_alloc (uri, func, data);
+ request->type = HEAD_REQUEST;
+
+ return request;
+}
+
+LacHttpRequest *
+lac_http_request_new_post (const LacUri *uri,
+ LacHttpFunc func,
+ gpointer data)
+{
+ LacHttpRequest *request;
+
+ g_return_val_if_fail (uri->scheme == LAC_SCHEME_HTTP, NULL);
+ g_return_val_if_fail (func != NULL, NULL);
+
+ request = request_alloc (uri, func, data);
+ request->type = POST_REQUEST;
+
+ return request;
+}
+
+LacHttpRequest *
+lac_http_request_new_put (const LacUri *uri,
+ LacHttpFunc func,
+ gpointer data)
+{
+ LacHttpRequest *request;
+
+ g_return_val_if_fail (uri->scheme == LAC_SCHEME_HTTP, NULL);
+ g_return_val_if_fail (func != NULL, NULL);
+
+ request = request_alloc (uri, func, data);
+ request->type = PUT_REQUEST;
+
+ return request;
+}
+
+LacHttpRequest *
+lac_http_request_new_delete (const LacUri *uri,
+ LacHttpFunc func,
+ gpointer data)
+{
+ LacHttpRequest *request;
+
+ g_return_val_if_fail (uri->scheme == LAC_SCHEME_HTTP, NULL);
+ g_return_val_if_fail (func != NULL, NULL);
+
+ request = request_alloc (uri, func, data);
+ request->type = DELETE_REQUEST;
+
+ return request;
+}
+
+gpointer
+lac_http_request_get_data (LacHttpRequest *request)
+{
+ return request->data;
+}
+
+void
+lac_http_request_cancel (LacHttpRequest *request)
+{
+ request->canceled = TRUE;
+
+ if (request->host)
+ http_host_cancel_request (request->host, request);
+}
+
+static void
+address_callback (const LacAddress *new_addr,
+ gpointer data,
+ const GError *err)
+{
+ LacHttpRequest *request = data;
+
+ if (!request->canceled)
+ {
+ if (err)
+ {
+ request_emit_error (request, err);
+ }
+ else
+ {
+ HttpHost *http_host;
+
+ http_host = http_host_new (
+ new_addr, request->uri->u.http.port);
+
+ request_emit_host_found (request, new_addr);
+
+ http_host_add_request (http_host, request);
+ request->host = http_host;
+ }
+ }
+
+ dispatch_events ();
+ lac_http_request_unref (request);
+}
+
+void
+lac_http_request_dispatch (LacHttpRequest *request)
+{
+ g_return_if_fail (request != NULL);
+
+ lac_http_request_ref (request);
+
+ lac_address_new_lookup_from_name (
+ request->uri->u.http.host, address_callback, request);
+}
+
+LacHttpRequest *
+lac_http_request_ref (LacHttpRequest *request)
+{
+ request->ref_count++;
+ return request;
+}
+
+void
+lac_http_request_unref (LacHttpRequest *request)
+{
+ request->ref_count--;
+ if (request->ref_count == 0)
+ request_destroy (request);
+}
+
+void
+lac_http_request_add_content (LacHttpRequest *request,
+ const guint8 *data,
+ gsize len)
+{
+ g_return_if_fail (request != NULL);
+ g_return_if_fail (
+ request->type == POST_REQUEST ||
+ request->type == PUT_REQUEST);
+ g_return_if_fail (data != NULL);
+
+ g_string_append_len (request->content, data, len);
+}
+
+void
+lac_http_request_add_header (LacHttpRequest *request,
+ const gchar *header,
+ const gchar *value)
+{
+ HttpHeader *http_header = g_new (HttpHeader, 1);
+
+ http_header->header = g_ascii_strdown (header, -1);
+ http_header->value = g_ascii_strdown (value, -1);
+
+ request->headers = g_list_prepend (request->headers, http_header);
+}
+
+/*
+ * HttpHost implementation
+ */
+static void
+lac_g_queue_remove (GQueue *queue, gpointer data)
+{
+ GList *list = queue->head;
+ while (list != NULL)
+ {
+ GList *next = list->next;
+
+ if (list->data == data)
+ {
+ queue->length--;
+ if (queue->tail == list)
+ queue->tail = list->prev;
+ queue->head = g_list_delete_link (queue->head, list);
+ g_assert ((queue->head != NULL && queue->tail != NULL)||
+ (queue->head == NULL && queue->tail == NULL));
+ return;
+ }
+
+ list = next;
+ }
+}
+
+static GList *all_hosts = NULL;
+
+#if 0
+static gboolean
+host_dump_spam (HttpHost *host)
+{
+ g_print ("**** host info: \n");
+ g_print (" unsent: %d\n", host->unsent->length);
+ g_print (" pipelined transports: %d\n", host->pipelines->len);
+ g_print (" oneshot transports: %d\n", host->one_shots->len);
+
+ return TRUE;
+}
+#endif
+
+static HttpHost *
+http_host_new (const LacAddress *addr, gint port)
+{
+ HttpHost *host;
+ GList *list;
+
+ for (list = all_hosts; list != NULL; list = list->next)
+ {
+ host = list->data;
+ if (lac_address_equal (host->address, addr) && host->port == port)
+ return host;
+ }
+
+ host = g_new0 (HttpHost, 1);
+
+ host->address = lac_address_copy (addr);
+ host->port = port;
+ host->pipelines = g_ptr_array_new ();
+ host->one_shots = g_ptr_array_new ();
+ host->unsent = g_queue_new ();
+ host->recovering = FALSE;
+ host->timeout_id = 0;
+
+ if (USE_PIPELINING)
+ {
+ host->optimistic = TRUE;
+ host->broken_server = FALSE;
+ }
+ else
+ {
+ host->optimistic = FALSE;
+ host->broken_server = TRUE;
+ }
+
+ all_hosts = g_list_prepend (all_hosts, host);
+
+#if 0
+ g_timeout_add (2000, host_dump_spam, host);
+#endif
+
+ return host;
+}
+
+static void
+http_host_dispatch_unsent (HttpHost *host)
+{
+ LacHttpRequest *request;
+
+ if (host->recovering)
+ return;
+
+ while ((request = g_queue_pop_head (host->unsent)))
+ {
+ HttpTransport *transport = NULL;
+
+ if (host->pipelines->len > 0)
+ {
+ transport = host->pipelines->pdata[0];
+#if 0
+ transport = g_ptr_array_remove_index (host->pipelines, 0);
+ g_ptr_array_add (host->pipelines, transport);
+#endif
+ }
+ else if (host->optimistic)
+ {
+#if 0
+ g_print ("---------> new optimistic transport to %s\n",
+ lac_address_to_str (host->address));
+#endif
+ transport = http_transport_new (host, TRUE, host->broken_server);
+ g_ptr_array_add (host->pipelines, transport);
+ }
+ else if (host->one_shots->len < MAX_ONE_SHOT_CONNECTIONS)
+ {
+#if 0
+ g_print ("---------> new pessimistic transport to %s\n",
+ lac_address_to_str (host->address));
+#endif
+ transport = http_transport_new (host, FALSE, host->broken_server);
+ g_ptr_array_add (host->one_shots, transport);
+ }
+ else
+ {
+ g_queue_push_head (host->unsent, request);
+ break;
+ }
+
+ if (transport)
+ {
+ http_transport_add_request (transport, request);
+ lac_http_request_unref (request);
+ }
+ }
+}
+
+static void
+http_host_add_request (HttpHost *host, LacHttpRequest *request)
+{
+ g_queue_push_tail (host->unsent, lac_http_request_ref (request));
+
+ request->transport = NULL;
+
+ http_host_dispatch_unsent (host);
+}
+
+static void
+http_host_transport_will_close_notify (HttpHost *host,
+ HttpTransport *transport)
+{
+#if 0
+ g_print ("WILL CLOSE NOTIFY\n");
+#endif
+ g_ptr_array_remove (host->pipelines, transport);
+ g_ptr_array_remove (host->one_shots, transport);
+
+ http_host_dispatch_unsent (host);
+}
+
+static void
+http_host_transport_closed_notify (HttpHost *host,
+ HttpTransport *transport)
+{
+#if 0
+ g_print ("CLOSED NOTIFY\n");
+#endif
+ g_ptr_array_remove (host->pipelines, transport);
+ g_ptr_array_remove (host->one_shots, transport);
+
+ http_transport_destroy (transport);
+
+ http_host_dispatch_unsent (host);
+}
+
+static void
+http_host_transport_cant_pipeline_notify (HttpHost *host,
+ HttpTransport *transport)
+{
+#if 0
+ g_print ("CANT PIPELINE NOTIFY\n");
+#endif
+ g_ptr_array_remove (host->pipelines, transport);
+
+ host->optimistic = FALSE;
+
+ http_host_dispatch_unsent (host);
+}
+
+static gboolean
+recover_timeout (gpointer data)
+{
+ HttpHost *host = data;
+
+ host->recovering = FALSE;
+ host->timeout_id = 0;
+
+ http_host_dispatch_unsent (host);
+
+ return FALSE;
+}
+
+static void
+http_host_transport_broken_server_notify (HttpHost *host,
+ HttpTransport *transport,
+ guint recover_time)
+{
+#if 0
+ g_print ("BROKEN SERVER NOITFY\n");
+#endif
+ g_ptr_array_remove (host->pipelines, transport);
+
+ host->optimistic = FALSE;
+ host->broken_server = TRUE;
+
+#if 0
+ g_print ("************* BROKEN SERVER DETECTED ***************\n");
+#endif
+
+ /* FIXME: for now we recover for as long as the *first* transport
+ * requests. We should recover for max over all transports
+ */
+ if (recover_time && !host->timeout_id)
+ {
+ host->timeout_id = g_timeout_add (recover_time, recover_timeout, host);
+ host->recovering = TRUE;
+ }
+
+ http_host_dispatch_unsent (host);
+}
+
+static void
+http_host_transport_activity_notify (HttpHost *host,
+ HttpTransport *transport)
+{
+ g_print ("activity\n");
+}
+
+static void
+http_host_transport_can_pipeline_notify (HttpHost *host,
+ HttpTransport *transport)
+{
+ g_ptr_array_remove (host->one_shots, transport);
+#if 0
+ g_print ("CAN pipeline (%p)\n", transport);
+#endif
+
+ if (!host->broken_server)
+ {
+ host->optimistic = TRUE;
+
+ g_ptr_array_add (host->pipelines, transport);
+ }
+
+ http_host_dispatch_unsent (host);
+}
+
+static void
+http_host_cancel_request (HttpHost *host, LacHttpRequest *request)
+{
+ if (request->transport)
+ http_transport_cancel_request (request->transport, request);
+ else
+ {
+ /* request should be in the unsent queue */
+ lac_g_queue_remove (host->unsent, request);
+ lac_http_request_unref (request);
+ }
+}
+
+/* HttpTransport implementation
+ */
+static void
+http_transport_delete_write_buffer (HttpTransport *transport)
+{
+ if (transport->write_buffer->len > 0)
+ {
+ g_string_truncate (transport->write_buffer, 0);
+ if (transport->write_timeout)
+ {
+ g_source_remove (transport->write_timeout);
+ transport->write_timeout = 0;
+ }
+ }
+}
+
+static void
+http_transport_return_outstanding_requests (HttpTransport *transport)
+{
+ LacHttpRequest *request;
+
+ while ((request = g_queue_pop_head (transport->in_progress)))
+ {
+ request->transport = NULL;
+#if 0
+ g_print ("rescheduling IN PROGRESS request\n");
+#endif
+ http_host_add_request (transport->host, request);
+ lac_http_request_unref (request);
+ }
+
+ while ((request = g_queue_pop_head (transport->unsent)))
+ {
+ request->transport = NULL;
+#if 0
+ g_print ("rescheduling UNSENT request\n");
+#endif
+ http_host_add_request (transport->host, request);
+ lac_http_request_unref (request);
+ }
+
+ http_transport_delete_write_buffer (transport);
+}
+
+static void
+server_choked_on_pipeline (HttpTransport *transport, guint recover_time)
+{
+ gchar *s;
+ http_host_transport_broken_server_notify (transport->host, transport, recover_time);
+
+ s = lac_address_to_string (transport->host->address);
+ g_print (
+ "broken server (%s)\n", s);
+ g_free (s);
+
+ transport->broken_server = TRUE;
+
+ if (transport->current)
+ {
+ /* If we have a current request, and the server has choked,
+ * we just retry *ignoring* as many bytes of the reply as we
+ * have seen until now. Yes, it sucks, and yes, it's broken,
+ * but there is not a lot we can do in this case (short of
+ * giving up on pipelining completely)
+ *
+ * The alternative: report back to the user that the data
+ * he already received was useless, is not nice.
+ */
+ LacHttpRequest *request = transport->current;
+ gsize n_bytes;
+
+ n_bytes = request->n_content_bytes;
+
+ request_restart_parsing (request);
+ request->n_ignore_bytes = n_bytes;
+ request->transport = NULL;
+
+ http_host_add_request (transport->host, request);
+
+ transport->current = NULL;
+ lac_http_request_unref (request);
+ }
+
+ http_transport_return_outstanding_requests (transport);
+}
+
+static gboolean
+answer_timeout (gpointer data)
+{
+ HttpTransport *transport = data;
+
+ /* we need to do this before closing the connection, because otherwise
+ * we might write into a destroyed transport
+ */
+ transport->answer_timeout = 0;
+
+ if (transport->did_pipeline &&
+ (!g_queue_is_empty (transport->in_progress) || transport->current))
+ {
+ g_print ("CHOKED\n");
+ server_choked_on_pipeline (transport, 4000);
+ http_host_transport_will_close_notify (transport->host, transport);
+ lac_connection_close (transport->connection);
+ }
+
+ return FALSE;
+}
+
+static void
+http_transport_remove_answer_timeout (HttpTransport *transport)
+{
+#if 0
+ g_print ("removing timeout\n");
+#endif
+ if (transport->answer_timeout)
+ {
+ g_source_remove (transport->answer_timeout);
+ transport->answer_timeout = 0;
+ }
+}
+
+static void
+http_transport_reset_answer_timeout (HttpTransport *transport)
+{
+ http_transport_remove_answer_timeout (transport);
+ if (!transport->broken_server)
+ {
+ transport->answer_timeout =
+ g_timeout_add (ANSWER_TIMEOUT, answer_timeout, transport);
+ }
+}
+
+static void
+http_transport_flush_write_buffer (HttpTransport *transport)
+{
+ if (transport->write_buffer->len > 0)
+ {
+ lac_connection_write (
+ transport->connection,
+ transport->write_buffer->str, transport->write_buffer->len);
+
+ g_string_truncate (transport->write_buffer, 0);
+ }
+}
+
+static gboolean
+flush_write_buffer_timeout (gpointer data)
+{
+ HttpTransport *transport = data;
+
+ http_transport_flush_write_buffer (transport);
+ lac_connection_flush (transport->connection);
+ transport->write_timeout = 0;
+
+ return FALSE;
+}
+
+/*
+ * We move a request to the in_progress queue as soon as
+ * we have serialized it. There is no point in distinguishing
+ * between stuff we have buffered up here and stuff that has
+ * been passed to send(), because it could just as well be buffered
+ * in the LacConnection, in the kernel or in transit.
+ */
+static void
+http_transport_transmit_request (HttpTransport *transport,
+ LacHttpRequest *request)
+{
+ gchar *serialized;
+
+#if 0
+ g_print ("transmit\n");
+#endif
+ g_queue_push_tail (transport->in_progress, request);
+
+ if (transport->current || transport->in_progress->length > 1)
+ transport->did_pipeline = TRUE;
+
+ if (transport->broken_server)
+ serialized = request_serialize (request, "Connection: close");
+#if 0
+ else
+ serialized = request_serialize (request, "Connection: keep-alive");
+#endif
+ else
+ serialized = request_serialize (request, NULL);
+
+#if 0
+ g_print ("-----------------------------\n");
+ g_print ("sending:\n%s\n", serialized);
+ g_print ("-----------------------------\n");
+#endif
+
+ if (transport->write_timeout)
+ {
+ g_source_remove (transport->write_timeout);
+ transport->write_timeout = 0;
+ }
+
+ g_string_append (transport->write_buffer, serialized);
+ g_free (serialized);
+
+#if 0
+#define MAX_BUFFER_SIZE 8280
+
+ if (transport->write_buffer->len >= MAX_BUFFER_SIZE)
+ http_transport_flush_write_buffer (transport);
+#endif
+
+ if (transport->pipelining)
+ {
+ transport->write_timeout = g_timeout_add (
+ WRITE_BUFFER_TIMEOUT, flush_write_buffer_timeout, transport);
+ }
+ else
+ {
+ http_transport_flush_write_buffer (transport);
+ lac_connection_flush (transport->connection);
+ }
+
+ http_transport_reset_answer_timeout (transport);
+
+#if 0
+ g_print ("queued on %d\n", *(int *)transport->connection);
+#endif
+
+ request_emit_sent (request);
+}
+
+static void
+http_transport_all_requests_failed (HttpTransport *transport, const GError *err)
+{
+ LacHttpRequest *request;
+
+ if (transport->current)
+ {
+ request_emit_error (transport->current, err);
+ transport->current->transport = NULL;
+ lac_http_request_unref (transport->current);
+ transport->current = NULL;
+ }
+
+ while ((request = g_queue_pop_head (transport->in_progress)))
+ {
+ request_emit_error (request, err);
+ request->transport = NULL;
+ lac_http_request_unref (request);
+ }
+
+ while ((request = g_queue_pop_head (transport->unsent)))
+ {
+ request_emit_error (request, err);
+ request->transport = NULL;
+ lac_http_request_unref (request);
+ }
+}
+
+static void
+write_log_file (HttpTransport * transport,
+ guint len,
+ gint used,
+ const LacConnectionReadEvent *read_event)
+{
+ static FILE *f;
+ int i, n_bytes;
+
+ if (!f)
+ f = fopen ("logfile", "w");
+
+ fprintf (f, "%s: \n", lac_uri_string (transport->current->uri));
+
+ g_assert (f);
+
+ if (used < 0)
+ n_bytes = len;
+ else
+ n_bytes = used;
+
+ for (i = 0; i < n_bytes; ++i)
+ {
+ fprintf (f, "%c", read_event->data[i]);
+#if 0
+ if (i % 10 == 0)
+ fprintf (f, "\n");
+#endif
+ }
+
+ fprintf (f, "\n");
+ fprintf (f, "used: %d\n", used);
+ fprintf (f, "length: %d\n", len);
+}
+
+static void
+http_transport_handle_read (HttpTransport *transport,
+ const LacConnectionReadEvent *read_event)
+{
+ const guint8 *data = read_event->data;
+ guint len = read_event->len;
+
+#if 0
+ {
+ int i;
+ for (i = 0; i < len - 6; ++i)
+ {
+ if (read_event->data[i + 0] == 'H' &&
+ read_event->data[i + 1] == 'T' &&
+ read_event->data[i + 2] == 'T' &&
+ read_event->data[i + 3] == 'P' &&
+ read_event->data[i + 4] == '/' &&
+ read_event->data[i + 5] == '0' &&
+ read_event->data[i + 6] == '0')
+ g_print ("************** TADA **************\n");
+ }
+ }
+#endif
+
+ while (len > 0)
+ {
+ gssize used;
+
+ if (!transport->current)
+ {
+ if (g_queue_is_empty (transport->in_progress))
+ {
+ junk_detected();
+ return;
+ }
+ else
+ {
+ g_assert (transport->in_progress->length > 0);
+ transport->current = g_queue_pop_head (transport->in_progress);
+ g_assert (transport->current);
+ }
+ }
+
+ g_assert (transport->current);
+
+ used = request_parse_response (transport->current, len, data);
+
+ write_log_file (transport, len, used, read_event);
+
+ if (used < 0)
+ {
+ GError *err = g_error_new (LAC_HTTP_ERROR,
+ LAC_HTTP_ERROR_MALFORMED_RESPONSE,
+ "Malformed response from server");
+
+ http_host_transport_will_close_notify (transport->host, transport);
+
+ g_print ("malformed response\n");
+ if (!transport->broken_server && transport->did_pipeline)
+ {
+ server_choked_on_pipeline (transport, 5000);
+ g_print ("retrying ...\n");
+ }
+ else
+ {
+ http_transport_all_requests_failed (transport, err);
+ }
+
+ g_error_free (err);
+ lac_connection_close (transport->connection);
+ break;
+ }
+
+ if (request_done_parsing (transport->current))
+ {
+#if 0
+ g_print ("RESPONSE SUCCESFULLY PARSED\n");
+#endif
+ ++transport->successful_requests;
+ }
+
+ if (transport->pipelining)
+ {
+#if 0
+ g_print ("pipelining\n");
+#endif
+ if (request_connection_will_close (transport->current))
+ {
+#if 0
+ g_print ("closing after successful requests: %d\n",
+ transport->successful_requests);
+#endif
+#if 0
+ g_print ("%p CONNECITON WILL CLOSE\n", transport);
+#endif
+ if ((transport->successful_requests > 1 ||
+ g_queue_is_empty (transport->in_progress)) &&
+ request_connection_is_1_1 (transport->current))
+ {
+#if 0
+ if (g_queue_is_empty (transport->in_progress))
+ g_print ("nothing in progress\n");
+#endif
+ /* This is an HTTP/1.1 server, where we have either
+ * seen pipelining work, or we have never sent more
+ * than one request, so we won't tell the host object
+ * that we can't pipeline.
+ */
+ http_host_transport_will_close_notify (
+ transport->host, transport);
+ }
+ else
+ {
+ /* We haven't seen pipelining work for this server,
+ * and we have sent stuff to the server, so tell the
+ * host object that we can't pipeline.
+ */
+#if 0
+ g_print ("length: %d\n", transport->in_progress->length);
+ g_print ("***************************\n");
+ g_print ("can't pipeline (%s)\n",
+ http_host_get_name (transport->host));
+ g_print ("***************************\n");
+#endif
+ http_host_transport_cant_pipeline_notify (
+ transport->host, transport);
+ }
+
+ http_transport_return_outstanding_requests (transport);
+ transport->pipelining = FALSE;
+ }
+ }
+ else
+ {
+ if (request_done_parsing_headers (transport->current) &&
+ !request_connection_will_close (transport->current) &&
+ !transport->broken_server &&
+ (request_connection_is_1_1 (transport->current) ||
+ request_seen_keep_alive))
+ {
+#if 0
+ g_print ("***************************\n");
+ g_print ("can pipeline (%d)\n", *(int *)transport->connection);
+ g_print ("***************************\n");
+#endif
+ transport->pipelining = TRUE;
+
+ http_host_transport_can_pipeline_notify (
+ transport->host, transport);
+ }
+ }
+
+ if (request_done_parsing (transport->current))
+ {
+ transport->current->transport = NULL;
+ lac_http_request_unref (transport->current);
+ transport->current = NULL;
+
+ if (!transport->pipelining)
+ {
+ /* FIXME return outstanding */
+ http_host_transport_will_close_notify (
+ transport->host, transport);
+ lac_connection_close (transport->connection);
+ }
+ }
+
+ len -= used;
+ data += used;
+ }
+}
+
+static void
+http_transport_handle_close (HttpTransport *transport)
+{
+#if 0
+ g_print ("remote closed (%p)\n", transport);
+#endif
+ if (transport->current)
+ {
+ request_parse_end_of_file (transport->current);
+
+ transport->current->transport = NULL;
+ lac_http_request_unref (transport->current);
+ transport->current = NULL;
+ }
+
+ if (transport->pipelining && !g_queue_is_empty (transport->in_progress))
+ {
+ /* If we have outstanding reqeusts at this point,
+ * the server will have lead us to believe that it
+ * supported pipelining, then went on to close the
+ * connection.
+ *
+ * This is known to happen with Netscape/Enterprise 3.6 SP.
+ * We should perhaps special case that particular server
+ */
+
+ http_host_transport_broken_server_notify (
+ transport->host, transport, 0);
+ }
+
+ http_transport_remove_answer_timeout (transport);
+ http_transport_return_outstanding_requests (transport);
+ http_host_transport_closed_notify (transport->host, transport);
+}
+
+static void
+connection_callback (LacConnection *connection,
+ const LacConnectionEvent *event)
+{
+ HttpTransport *transport = lac_connection_get_data (connection);
+
+ http_transport_reset_answer_timeout (transport);
+
+ switch (event->type)
+ {
+ case LAC_CONNECTION_EVENT_CONNECT:
+#if 0
+ g_print ("CONNECT\n");
+#endif
+ while (transport->unsent->length > 0)
+ {
+ LacHttpRequest *request;
+
+ while ((request = g_queue_pop_head (transport->unsent)))
+ http_transport_transmit_request (transport, request);
+ }
+ break;
+
+ case LAC_CONNECTION_EVENT_READ:
+ http_transport_handle_read (transport, &(event->read));
+ break;
+
+ case LAC_CONNECTION_EVENT_CLOSE:
+#if 0
+ g_print ("%p HTTP TRANSPORT CLOSED\n", transport);
+#endif
+ http_transport_handle_close (transport);
+ break;
+
+ case LAC_CONNECTION_EVENT_ERROR:
+ g_print ("seen error: %s\n", event->error.err->message);
+
+ if (!transport->broken_server &&
+ transport->current &&
+ request_connection_will_close (transport->current) &&
+ g_error_matches (
+ event->error.err,
+ LAC_SOCKET_ERROR, LAC_SOCKET_ERROR_CONNECTION_RESET))
+ {
+ /* Some servers close the socket immediately after sending
+ * a response. This means that a pipeline can arrive at
+ * a closed port, causing the server TCP to send an RST.
+ * This shows here as an ECONNRESET.
+ *
+ * If we expected a close, we'll treat this error as a close.
+ */
+ g_print ("but that's ok, I guess\n");
+ http_transport_handle_close (transport);
+ }
+ else
+ {
+ if (!transport->broken_server && transport->did_pipeline)
+ server_choked_on_pipeline (transport, 0);
+ else
+ http_transport_all_requests_failed (transport, event->error.err);
+
+ http_host_transport_closed_notify (transport->host, transport);
+ }
+ break;
+ }
+
+ dispatch_events();
+}
+
+static gboolean
+transport_dump_spam (HttpTransport *transport)
+{
+ if (transport->current)
+ {
+ int i;
+ g_print ("**** transport info: \n");
+ g_print (" transport->current->unparsed: %d\n", transport->current->unparsed->len);
+ g_print (" tran current->content-len: %d\n", transport->current->content_length);
+ g_print (" t ->so far: %d\n", transport->current->n_content_bytes);
+ for (i = 0; i < transport->current->unparsed->len; ++i)
+ printf ("%x", transport->current->unparsed->str[i]);
+
+ switch (transport->current->parser_state)
+ {
+ case INITIAL:
+ g_print ("parser_state is %s\n", "INITIAL");
+ break;
+ case EXPECT_STATUS_LINE:
+ g_print ("parser_state is %s\n", "EXPECT_STATUS_LINE");
+ break;
+ case EXPECT_HEADER:
+ g_print ("parser_state is %s\n", "EXPECT_HEADER");
+ break;
+ case EXPECT_CHUNK_DATA:
+ g_print ("parser_state is %s\n", "EXPECT_CHUNK_DATA");
+ break;
+ case EXPECT_CHUNK_TRAILER:
+ g_print ("parser_state is %s\n", "EXPECT_CHUNK_TRAILER");
+ break;
+ case EXPECT_CHUNK_HEADER:
+ g_print ("parser_state is %s\n", "EXPECT_CHUNK_HEADER");
+ break;
+ case EXPECT_LENGTH_DELIMITED_BODY:
+ g_print ("parser_state is %s\n", "EXPECT_LENGTH_DELIMITED_BODY");
+ break;
+ case EXPECT_CLOSE_DELIMITED_BODY:
+ g_print ("parser_state is %s\n", "EXPECT_CLOSE_DELIMITED_BODY");
+ break;
+ case DONE:
+ g_print ("parser_state is %s\n", "DONE");
+ break;
+ }
+ g_print ("\n");
+ }
+
+ return TRUE;
+}
+
+static HttpTransport *
+http_transport_new (HttpHost *host,
+ gboolean pipelining,
+ gboolean broken)
+{
+ HttpTransport *transport = g_new0 (HttpTransport, 1);
+
+#if 0
+ transport->debug_timeout =
+ g_timeout_add (2000, transport_dump_spam, transport);
+#endif
+
+#if 0
+ g_print ("%p NEW TRANSPORT: (%s)\n", transport,
+ pipelining? "optimistic" : "pessimistic");
+#endif
+
+ transport->host = host;
+ transport->connection = lac_connection_new (
+ host->address, host->port, connection_callback, transport);
+
+ transport->unsent = g_queue_new ();
+ transport->in_progress = g_queue_new ();
+ transport->current = NULL;
+ transport->pipelining = pipelining;
+ transport->broken_server = broken;
+ transport->successful_requests = 0;
+ transport->write_buffer = g_string_new ("");
+ transport->write_timeout = 0;
+
+ transport->answer_timeout = 0;
+
+ return transport;
+}
+
+static void
+http_transport_destroy (HttpTransport *transport)
+{
+ lac_connection_unref (transport->connection);
+
+#if 0
+ g_source_remove (transport->debug_timeout);
+#endif
+
+#if 0
+ g_print ("timeout removed on %p (destroyed)\n", transport);
+#endif
+ http_transport_remove_answer_timeout (transport);
+
+ if (transport->write_timeout)
+ g_source_remove (transport->write_timeout);
+
+ g_assert (g_queue_is_empty (transport->unsent));
+ g_queue_free (transport->unsent);
+
+ g_assert (g_queue_is_empty (transport->in_progress));
+ g_queue_free (transport->in_progress);
+
+ g_string_free (transport->write_buffer, TRUE);
+
+ g_free (transport);
+}
+
+static void
+http_transport_add_request (HttpTransport *transport,
+ LacHttpRequest *request)
+{
+ lac_http_request_ref (request);
+
+ request->transport = transport;
+
+ if (lac_connection_is_connected (transport->connection))
+ {
+ http_transport_transmit_request (transport, request);
+ }
+ else
+ {
+ g_queue_push_tail (transport->unsent, request);
+ request_emit_connecting (request);
+ }
+
+ dispatch_events ();
+}
+
+static void
+http_transport_cancel_request (HttpTransport *transport,
+ LacHttpRequest *request)
+{
+ if (g_list_find (transport->unsent->head, request))
+ {
+ lac_g_queue_remove (transport->unsent, request);
+ request->transport = NULL;
+ lac_http_request_unref (request);
+ }
+
+ /* We don't do anything about requests that are already
+ * in progress.
+ *
+ * FIXME: this needs some thought.
+ *
+ * It would be possible to close this transport and
+ * resend everything, but that doesn't sound like a
+ * good idea to me.
+ *
+ * We could check if all in-progress requests were canceled
+ * and if so, close the connection. This might be a better
+ * idea.
+ *
+ * How should this work in the presence of caching?
+ *
+ * Maybe canceling a request should be dispatched through the cache,
+ * so that the cache would be able to make the decision about
+ * whether to store a partially downloaded request.
+ */
+}
diff --git a/src/lacinternals.h b/src/lacinternals.h
new file mode 100644
index 0000000..97cad07
--- /dev/null
+++ b/src/lacinternals.h
@@ -0,0 +1,50 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- */
+
+/* Lac - Library for asynchronous communication
+ * Copyright (C) 2002 Søren Sandmann (sandmann@daimi.au.dk)
+ *
+ * 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 LAC_INTERNALS_H
+#define LAC_INTERNALS_H
+
+#include "lac.h"
+
+/*
+ * Activity
+ */
+typedef void (* LacCancelNotify) (gpointer data);
+typedef void (* LacDestroyNotify) (gpointer data);
+
+void lac_activity_disable_cancel (LacActivity activity);
+gboolean lac_activity_unref (LacActivity activity);
+LacActivity lac_activity_ref (LacActivity activity);
+LacActivity lac_activity_new (void);
+gboolean lac_activity_wait (LacActivity activity);
+gboolean lac_activity_canceled (LacActivity activity);
+
+/*
+ * Addresses
+ */
+struct in_addr;
+
+LacAddress *lac_address_allocate (void);
+void lac_address_get_in_addr (const LacAddress *addr,
+ struct in_addr *in_addr);
+void lac_address_set_in_addr (LacAddress *addr,
+ struct in_addr *in_addr);
+#endif /* LAC_INTERNALS_H */
diff --git a/src/lacprimitives.c b/src/lacprimitives.c
new file mode 100644
index 0000000..cc6569e
--- /dev/null
+++ b/src/lacprimitives.c
@@ -0,0 +1,690 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- */
+
+/* Lac - Library for asynchronous communication
+ * Copyright (C) 2000, 2001, 2002, 2003 Søren Sandmann
+ *
+ * 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.
+ */
+
+#include "lac.h"
+#include "config.h"
+#include <glib.h>
+#ifdef HAVE_SYS_TYPES_H
+# include <sys/types.h>
+#endif
+#ifdef HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif
+#ifdef HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif
+#ifdef HAVE_ARPA_INET_H
+# include <arpa/inet.h>
+#endif
+#ifdef HAVE_NETDB_H
+# include <netdb.h>
+#endif
+extern gint h_errno;
+#include <errno.h>
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+#ifdef HAVE_SIGNAL_H
+# include <signal.h>
+#endif
+#ifdef HAVE_SYS_UTSNAME_H
+# include <sys/utsname.h>
+#endif
+#ifdef HAVE_SYS_TIME_H
+# include <sys/time.h>
+#endif
+#ifdef HAVE_FCNTL_H
+# include <fcntl.h>
+#endif
+#ifdef HAVE_NETINET_TCP_H
+# include <netinet/tcp.h>
+#endif
+#ifdef STDC_HEADERS
+# include <stdio.h>
+#endif
+#include <ctype.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "lacinternals.h"
+
+static void lac_set_error_from_errno (GError **err,
+ gint eno,
+ const gchar *msg);
+
+GQuark
+lac_socket_error_quark (void)
+{
+ static GQuark q = 0;
+
+ if (q == 0)
+ q = g_quark_from_static_string ("lac-error-quark");
+
+ return q;
+}
+
+void
+lac_sigpipe_ignore (void)
+{
+ struct sigaction sa;
+ gint ret;
+
+ memset (&sa, 0, sizeof (sa));
+ sa.sa_handler = SIG_IGN;
+
+ do
+ {
+ ret = sigaction (SIGPIPE, &sa, NULL);
+ }
+ while (ret == -1 && errno == EINTR);
+
+ if (ret == -1)
+ {
+ g_warning (G_STRLOC " sigaction() returned -1 (error: %s)\n",
+ g_strerror (errno));
+ }
+}
+
+void
+lac_sigpipe_default (void)
+{
+ struct sigaction sa;
+ int ret;
+
+ memset (&sa, 0, sizeof (sa));
+ sa.sa_handler = SIG_DFL;
+
+ do
+ {
+ ret = sigaction (SIGPIPE, &sa, NULL);
+ }
+ while (ret == -1 && errno == EINTR);
+
+ if (ret == -1)
+ {
+ g_warning (G_STRLOC ": sigaction() returned -1 (error: %s)\n",
+ g_strerror (errno));
+ }
+}
+
+gchar *
+lac_gethostname (void)
+{
+ struct utsname name;
+
+ uname (&name);
+ return g_strdup (name.nodename);
+}
+
+gint
+lac_socket_tcp (GError **err)
+{
+ gint fd;
+
+ g_return_val_if_fail (err == NULL || *err == NULL, -1);
+
+ errno = 0;
+ do
+ {
+ fd = socket (AF_INET, SOCK_STREAM, 0);
+ }
+ while (fd < 0 && errno == EINTR);
+
+ if (fd < 0)
+ {
+ lac_set_error_from_errno (err, errno, "socket() failed %s");
+ return -1;
+ }
+
+ return fd;
+}
+
+gint
+lac_socket_udp (GError **err)
+{
+ gint fd;
+
+ g_return_val_if_fail (err == NULL || *err == NULL, -1);
+
+ errno = 0;
+ do
+ {
+ fd = socket (AF_INET, SOCK_DGRAM, 0);
+ }
+ while (fd < 0 && errno == EINTR);
+
+ if (fd < 0)
+ {
+ lac_set_error_from_errno (err, errno, "socket() failed: %d");
+ return -1;
+ }
+
+ return fd;
+}
+
+gboolean
+lac_connect (gint fd,
+ const LacAddress *host,
+ gint port,
+ GError **err)
+{
+ struct sockaddr_in host_addr;
+
+ gint res;
+
+ g_return_val_if_fail (fd > 0, FALSE);
+ g_return_val_if_fail (host != NULL, FALSE);
+ g_return_val_if_fail (port > 0, FALSE);
+ g_return_val_if_fail (err == NULL || *err == NULL, FALSE);
+
+ memset (&host_addr, 0, sizeof (host_addr));
+ host_addr.sin_family = AF_INET;
+ host_addr.sin_port = g_htons (port);
+ lac_address_get_in_addr (host, &(host_addr.sin_addr));
+
+ errno = 0;
+ do
+ {
+ res = connect (fd, (struct sockaddr *)&host_addr,
+ sizeof (struct sockaddr));
+ }
+ while (res < 0 && errno == EINTR);
+
+ if (res < 0)
+ {
+ lac_set_error_from_errno (err, errno, "connect() failed: %s");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean
+lac_bind (gint fd,
+ const LacAddress *host,
+ gint port,
+ GError **err)
+{
+ struct sockaddr_in host_addr;
+ gint ret;
+
+ g_return_val_if_fail (fd > 0, FALSE);
+ g_return_val_if_fail (port > 0, FALSE);
+ g_return_val_if_fail (err == NULL || *err == NULL, FALSE);
+
+ memset (&host_addr, 0, sizeof (host_addr));
+ host_addr.sin_family = AF_INET;
+ host_addr.sin_port = g_htons (port);
+ if (host)
+ lac_address_get_in_addr (host, &host_addr.sin_addr);
+ else
+ host_addr.sin_addr.s_addr = htonl (INADDR_ANY);
+
+ errno = 0;
+ do
+ {
+ ret = bind (
+ fd, (struct sockaddr *)&host_addr, sizeof (struct sockaddr));
+ }
+ while (ret < 0 && errno == EINTR);
+
+ if (ret < 0)
+ {
+ lac_set_error_from_errno (err, errno, "bind() failed: %s");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean
+lac_listen (gint fd,
+ guint backlog,
+ GError **err)
+{
+ gint ret;
+
+ g_return_val_if_fail (fd > 0, FALSE);
+ g_return_val_if_fail (backlog >= 0, FALSE); /* FIXME: is this correct? */
+ g_return_val_if_fail (err == NULL || *err == NULL, FALSE);
+
+ errno = 0;
+ do
+ {
+ ret = listen (fd, backlog);
+ }
+ while (ret < 0 && errno == EINTR);
+
+ if (ret < 0)
+ {
+ lac_set_error_from_errno (err, errno, "listen() failed: %s");
+ return FALSE;
+ }
+ return TRUE;
+}
+
+gint
+lac_accept (gint fd,
+ LacAddress **addr,
+ gint *port,
+ GError **err)
+{
+ gint ret;
+ struct sockaddr_in host_addr;
+ guint addr_len = sizeof (struct sockaddr_in);
+
+ g_return_val_if_fail (fd > 0, FALSE);
+ g_return_val_if_fail (err == NULL || *err == NULL, FALSE);
+
+ errno = 0;
+ do
+ {
+ ret = accept (fd, (struct sockaddr *)&host_addr, &addr_len);
+ }
+ while (ret < 0 && errno == EINTR);
+
+ if (ret < 0)
+ {
+ lac_set_error_from_errno (err, errno, "accept() failed: %s");
+ return -1;
+ }
+
+ /* FIXME -- fill out addr and port if they are non-NULL */
+
+ return ret;
+}
+
+gint
+lac_send (gint fd,
+ const gchar *msg,
+ guint len,
+ GError **err)
+{
+ gint res;
+ g_return_val_if_fail (fd > 0, -1);
+ g_return_val_if_fail (msg != NULL, -1);
+ g_return_val_if_fail (err == NULL || *err == NULL, -1);
+
+ errno = 0;
+ do
+ {
+ res = send (fd, msg, len, 0);
+ }
+ while (res < 0 && errno == EINTR);
+
+ if (res < 0)
+ {
+ lac_set_error_from_errno (err, errno, "send() failed: %s:");
+ return -1;
+ }
+ if (res == 0)
+ g_warning (G_STRLOC ": send() returned 0");
+
+ return res;
+}
+
+gint
+lac_recv (gint fd,
+ gchar *buf,
+ guint len,
+ GError **err)
+{
+ gint res;
+
+ g_return_val_if_fail (fd > 0, -1);
+ g_return_val_if_fail (buf != NULL, -1);
+ g_return_val_if_fail (err == NULL || *err == NULL, -1);
+
+ errno = 0;
+ do
+ {
+ res = recv (fd, buf, len, 0);
+ }
+ while (res < 0 && errno == EINTR);
+
+ if (res < 0)
+ {
+ lac_set_error_from_errno (err, errno, "recv() failed: %s");
+ return -1;
+ }
+
+ return res;
+}
+
+gint
+lac_sendto (gint fd,
+ gchar *buf,
+ guint len,
+ const LacAddress *address,
+ int port,
+ GError **err)
+{
+ struct sockaddr_in host_addr;
+ gint res;
+
+ g_return_val_if_fail (fd > 0, -1);
+ g_return_val_if_fail (buf != NULL, -1);
+ g_return_val_if_fail (err == NULL || *err == NULL, -1);
+ g_return_val_if_fail (address != NULL, -1);
+
+ memset (&host_addr, 0, sizeof (host_addr));
+ host_addr.sin_family = AF_INET;
+ host_addr.sin_port = g_htons (port);
+ lac_address_get_in_addr (address, &(host_addr.sin_addr));
+
+ errno = 0;
+ do
+ {
+ res = sendto (fd, buf, len, 0,
+ (struct sockaddr *)&host_addr, sizeof (struct sockaddr));
+ }
+ while (res < 0 && errno == EINTR);
+
+ if (res < 0)
+ {
+ lac_set_error_from_errno (err, errno, "sendto() failed: %s");
+ return FALSE;
+ }
+
+ return res;
+}
+
+gint
+lac_recvfrom (gint fd,
+ gchar *buf,
+ guint len,
+ LacAddress **address,
+ int *port,
+ GError **err)
+{
+ struct sockaddr_in host_addr;
+ guint addr_len = sizeof (struct sockaddr_in);
+ gint res;
+
+ g_return_val_if_fail (fd > 0, -1);
+ g_return_val_if_fail (buf != NULL, -1);
+ g_return_val_if_fail (err == NULL || *err == NULL, -1);
+
+ errno = 0;
+ do
+ {
+ res = recvfrom (fd, buf, len, 0,
+ (struct sockaddr *)&host_addr,
+ &addr_len);
+ }
+ while (res < 0 && errno == EINTR);
+
+ if (res < 0)
+ {
+ lac_set_error_from_errno (err, errno, "sendto() failed: %s");
+ return -1;
+ }
+
+ if (address)
+ {
+ *address = lac_address_allocate ();
+ lac_address_set_in_addr (*address, &host_addr.sin_addr);
+ }
+ if (port)
+ *port = host_addr.sin_port;
+
+ return res;
+}
+
+gboolean
+lac_getpeername (gint fd,
+ LacAddress **addr,
+ gint *port,
+ GError **err)
+{
+ gint res;
+ struct sockaddr_in host_addr;
+ guint addr_len = sizeof (struct sockaddr_in);
+
+ g_return_val_if_fail (fd > 0, -1);
+ g_return_val_if_fail (err == NULL || *err == NULL, FALSE);
+
+ errno = 0;
+ do
+ {
+ res = getpeername (fd, (struct sockaddr *)&host_addr, &addr_len);
+ }
+ while (res < 0 && errno == EINTR);
+
+ if (res < 0)
+ {
+ lac_set_error_from_errno (err, errno, "getpeername() failed: %s");
+ if (addr)
+ *addr = NULL;
+ if (port)
+ *port = -1;
+ return FALSE;
+ }
+
+ if (addr)
+ {
+ *addr = lac_address_allocate ();
+ lac_address_set_in_addr (*addr, &host_addr.sin_addr);
+ }
+ if (port)
+ *port = host_addr.sin_port;
+ return TRUE;
+}
+
+gboolean
+lac_shutdown (gint fd,
+ LacShutdownMethod how,
+ GError **err)
+{
+ gint res;
+
+ g_return_val_if_fail (fd > 0, FALSE);
+ g_return_val_if_fail (how == 0 || how == 1 || how == 2, FALSE);
+ g_return_val_if_fail (err == NULL || *err == NULL, FALSE);
+
+ errno = 0;
+ do
+ {
+ res = shutdown (fd, how);
+ }
+ while (res < 0 && errno == EINTR);
+
+ if (res < 0)
+ {
+ lac_set_error_from_errno (err, errno, "shutdown() failed: %s");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean
+lac_close (gint fd,
+ GError **err)
+{
+ g_return_val_if_fail (fd > 0, FALSE);
+ g_return_val_if_fail (err == NULL || *err == NULL, FALSE);
+
+ if (lac_fd_is_watched (fd))
+ g_warning ("Closing a filedescriptor with a watch attached");
+
+ errno = 0;
+
+ /* It is actually not right to retry the close if we get EINTR.
+ * If a signal arrives, the file descriptor is left in an indeterminate
+ * state. The best we can do is leak it and return an error
+ */
+ if (close (fd) < 0)
+ {
+ lac_set_error_from_errno (err, errno, "close() failed: %s");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean
+lac_set_blocking (gint fd,
+ gboolean blocking,
+ GError **err)
+{
+ gint flags;
+ gint ret;
+
+ g_return_val_if_fail (fd > 0, FALSE);
+ g_return_val_if_fail (err == NULL || *err == NULL, FALSE);
+
+ errno = 0;
+ do
+ {
+ flags = fcntl (fd, F_GETFL);
+ }
+ while (flags == -1 && errno == EINTR);
+
+ if (flags == -1)
+ {
+ lac_set_error_from_errno (err, errno, "fcntl() failed: %s");
+ return FALSE;
+ }
+
+#ifdef O_NONBLCOK
+ if (blocking)
+ flags &= ~O_NONBLOCK;
+ else
+ flags |= O_NONBLOCK;
+#else
+ if (blocking)
+ flags &= ~O_NDELAY;
+ else
+ flags |= O_NDELAY;
+#endif
+
+ do
+ {
+ ret = fcntl (fd, F_SETFL, flags);
+ }
+ while (ret == -1 && errno == EINTR);
+
+ if (ret == -1)
+ {
+ lac_set_error_from_errno (err, errno, "fcntl() failed: %s");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean
+lac_set_nagle (gint fd,
+ gboolean use_nagle,
+ GError **err)
+{
+ gint op = !use_nagle;
+
+ g_return_val_if_fail (fd > 0, FALSE);
+ g_return_val_if_fail (err == NULL || *err == NULL, FALSE);
+
+ if (setsockopt (fd, IPPROTO_TCP, TCP_NODELAY, &op, sizeof (op)) < 0)
+ {
+ lac_set_error_from_errno (err, errno, "setsockopt() failed: %s");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static LacError
+lac_error_from_errno (int eno)
+{
+ switch (eno)
+ {
+#ifdef EINPROGRESS
+ case EINPROGRESS:
+ return LAC_SOCKET_ERROR_IN_PROGRESS;
+ break;
+#endif
+
+#ifdef ETIMEOUT
+ case ETIMEOUT:
+ return LAC_SOCKET_ERROR_TIMEOUT;
+ break;
+#endif
+
+#ifdef ECONNRESET
+ case ECONNRESET:
+ return LAC_SOCKET_ERROR_CONNECTION_RESET;
+ break;
+#endif
+
+#ifdef ECONNREFUSED
+ case ECONNREFUSED:
+ return LAC_SOCKET_ERROR_CONNECTION_REFUSED;
+ break;
+#endif
+
+#ifdef ENETUNREACH
+ case ENETUNREACH:
+ return LAC_SOCKET_ERROR_NET_UNREACHABLE;
+ break;
+#endif
+
+#ifdef EMFILE
+ case EMFILE:
+ return LAC_SOCKET_ERROR_NO_RESOURCES;
+ break;
+#endif
+
+#ifdef ENFILE
+ case ENFILE:
+ return LAC_SOCKET_ERROR_NO_RESOURCES;
+ break;
+#endif
+
+#ifdef EAGAIN
+ case EAGAIN:
+ return LAC_SOCKET_ERROR_AGAIN;
+ break;
+#endif
+
+#if (defined (EWOULDBLOCK) && (!defined (EAGAIN) || EAGAIN != EWOULDBLOCK))
+ case EWOULDBLOCK:
+ return LAC_ERROR_AGAIN;
+ break;
+#endif
+
+ default:
+ return LAC_SOCKET_ERROR_FAILED;
+ break;
+ }
+}
+
+static void
+lac_set_error_from_errno (GError **err,
+ int eno,
+ const gchar *msg)
+{
+ g_set_error (err,
+ LAC_SOCKET_ERROR,
+ lac_error_from_errno (eno),
+ msg,
+ g_strerror (errno));
+}
diff --git a/src/lacuri.c b/src/lacuri.c
new file mode 100644
index 0000000..5931b70
--- /dev/null
+++ b/src/lacuri.c
@@ -0,0 +1,1016 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- */
+
+/* Lac - Library for asynchronous communication
+ * Copyright (C) 2000, 2001, 2002, 2003 Søren Sandmann
+ *
+ * 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.
+ */
+
+/* RFC's:
+ *
+ * General URI syntax: 2396
+ * Relative URL syntax: 1808 (subsumed by 2396)
+ * FTP URL syntax: 1738
+ *
+ */
+
+/* Why don't we just do a recursive descent parser? That would probably
+ * lead to much simpler and clearer code.
+ *
+ * The FTP handling is not correct - RFC 1738 states that the password should
+ * be the internet mail address for the user.
+ *
+ * We should either copy "anonymous" into ->username and somehow generate an email
+ * address OR (more likely) just provide "" or NULL depending on whether there are
+ * @ and/or : in the user_info. (This would leave it up to the application to decide
+ * what to do if no username/password is supplied. Maybe there should be API to generate
+ * these strings? -- lac_uri_ftp_set_username/password?)
+ */
+
+#include "lac.h"
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+enum LacUriState {
+ LAC_SCHEME_FIRST_CHAR,
+ LAC_SCHEME_OTHER_CHAR,
+ LAC_AUTHORITY,
+ LAC_AUTHORITY_FIRST_SLASH,
+ LAC_AUTHORITY_SECOND_SLASH,
+ LAC_PATH,
+ LAC_QUERY_FIRST_CHAR,
+ LAC_QUERY_OTHER_CHAR,
+ LAC_FRAGMENT_FIRST_CHAR,
+ LAC_FRAGMENT_OTHER_CHAR
+};
+
+typedef enum LacUriState LacUriState;
+
+static LacUri *
+lac_uri_parse_unknown (const gchar *u)
+{
+ LacUri *uri;
+ LacUriState state = LAC_SCHEME_FIRST_CHAR;
+ gint last = -1;
+ gint r;
+ guint length;
+
+ g_return_val_if_fail (u != NULL, NULL);
+
+ uri = g_new (LacUri, 1);
+
+ uri->scheme = LAC_SCHEME_UNKNOWN;
+ uri->u.unknown.scheme = NULL;
+ uri->u.unknown.authority = NULL;
+ uri->u.unknown.path = NULL;
+ uri->u.unknown.query = NULL;
+ uri->fragment = NULL;
+
+ length = strlen (u);
+
+ for (r=0; r < length + 1; r++) /* we want to look at the final '\0' */
+ {
+ switch (state)
+ {
+ case LAC_SCHEME_FIRST_CHAR:
+ if (u[r]==':' || u[r]=='/' || u[r]=='?' || u[r]=='#')
+ {
+ /* look at this character again in the AUTHORITY state */
+ r = last;
+ state = LAC_AUTHORITY;
+ }
+ else if (u[r]=='\0')
+ return uri;
+ else
+ state = LAC_SCHEME_OTHER_CHAR;
+ break;
+
+ case LAC_SCHEME_OTHER_CHAR:
+ if (u[r]==':')
+ {
+ uri->u.unknown.scheme = g_strndup (u+last+1, r-last-1);
+ last = r;
+ state = LAC_AUTHORITY;
+ }
+ else if (u[r]=='/' || u[r]=='?' || u[r]=='#' || u[r]=='\0')
+ {
+ r = last;
+ state = LAC_AUTHORITY;
+ }
+ else
+ continue;
+ break;
+
+ case LAC_AUTHORITY:
+ if (u[r]=='/')
+ state = LAC_AUTHORITY_FIRST_SLASH;
+ else
+ {
+ r = last;
+ state = LAC_PATH;
+ }
+ break;
+
+ case LAC_AUTHORITY_FIRST_SLASH:
+ if (u[r]=='/')
+ state = LAC_AUTHORITY_SECOND_SLASH;
+ else
+ {
+ r = last;
+ state = LAC_PATH;
+ }
+ break;
+
+ case LAC_AUTHORITY_SECOND_SLASH:
+ if (u[r]=='/' || u[r]=='?' || u[r]=='#' || u[r]=='\0')
+ {
+ --r;
+ if (r-last > 0)
+ uri->u.unknown.authority = g_strndup (u+last+3, r-last-2);
+ last = r;
+ state = LAC_PATH;
+ }
+ else
+ continue;
+ break;
+
+ case LAC_PATH:
+ if (u[r]=='?' || u[r]=='#' || u[r]=='\0')
+ {
+ --r;
+ if (r-last > 0)
+ uri->u.unknown.path = g_strndup (u+last+1, r-last);
+ last = r;
+ state = LAC_QUERY_FIRST_CHAR;
+ }
+ else
+ continue;
+ break;
+
+ case LAC_QUERY_FIRST_CHAR:
+ if (u[r]=='?')
+ state = LAC_QUERY_OTHER_CHAR;
+ else
+ {
+ r = last;
+ state = LAC_FRAGMENT_FIRST_CHAR;
+ }
+ break;
+
+ case LAC_QUERY_OTHER_CHAR:
+ if (u[r]=='#' || u[r]=='\0')
+ {
+ --r;
+ if (r-last-1 > 0)
+ uri->u.unknown.query = g_strndup (u+last+2, r-last-1);
+ last = r;
+ state = LAC_FRAGMENT_FIRST_CHAR;
+ }
+ else
+ continue;
+ break;
+
+ case LAC_FRAGMENT_FIRST_CHAR:
+ if (u[r]=='#')
+ {
+ state = LAC_FRAGMENT_OTHER_CHAR;
+ }
+ else
+ return uri;
+ break;
+
+ case LAC_FRAGMENT_OTHER_CHAR:
+ if (u[r]=='\0')
+ {
+ if (r-last-1 > 0)
+ uri->fragment = g_strndup (u+last+2, r-last-1);
+ return uri;
+ }
+ else
+ continue;
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+ }
+ return NULL;
+}
+
+static void
+lac_uri_authority_to_port_and_host (const gchar *authority,
+ gchar **host, gint *port)
+{
+ const gchar *s;
+ const gchar *colon;
+ gchar *tmp_host;
+
+ if (authority == NULL)
+ {
+ *host = g_strdup ("localhost");
+ *port = -1;
+ return;
+ }
+
+ s = strchr (authority, '@');
+ if (!s)
+ s = authority;
+ else
+ s++;
+
+ colon = strchr (s, ':');
+ if (!colon)
+ {
+ tmp_host = g_strdup (s);
+ *port = -1;
+ }
+ else
+ {
+ if (colon == s)
+ tmp_host = NULL;
+ else
+ tmp_host = g_strndup (s, colon - s);
+
+ *port = (gint)strtol (colon+1, (gchar **)NULL, 10);
+ if (*port <= 0 || *port > 65535)
+ *port = -1;
+ }
+
+ if (tmp_host)
+ {
+ *host = g_ascii_strdown (tmp_host, -1);
+ g_free (tmp_host);
+ }
+ else
+ {
+ *host = g_strdup ("localhost");
+ }
+}
+
+static void
+lac_uri_authority_to_username_and_password (const gchar *authority,
+ char **username,
+ char **password)
+{
+ gchar *userinfo;
+ gint userinfo_length;
+ gint username_length;
+ gchar *pw;
+
+ if (authority == NULL ||
+ strchr (authority, '@') == NULL ||
+ (userinfo_length = strcspn (authority, "@")) == 0)
+ {
+ *username = g_strdup ("anonymous");
+ *password = NULL;
+ return;
+ }
+ userinfo = g_strndup (authority, userinfo_length);
+
+ username_length = strcspn (userinfo, ":");
+ if (username_length == 0)
+ *username = g_strdup ("anonymous");
+ else
+ *username = g_strndup (userinfo, username_length);
+
+ pw = strchr (userinfo, ':');
+ if (pw == NULL)
+ *password = NULL;
+ else
+ {
+ if (pw[1] == '\0')
+ *password = NULL;
+ else
+ *password = g_strdup (pw+1);
+ }
+ g_free (userinfo);
+}
+
+static void
+lac_uri_path_to_ftp_type (const gchar *path, LacFtpType *type)
+{
+ gchar *s;
+ gchar ty;
+
+ if ((s = strrchr (path, ';')) &&
+ sscanf (s, "; type = %c", &ty) == 1)
+ {
+ switch (ty)
+ {
+ case 'a': case 'A':
+ *type = LAC_FTP_TYPE_A;
+ break;
+ case 'i': case 'I':
+ *type = LAC_FTP_TYPE_I;
+ break;
+ case 'd': case 'D':
+ *type = LAC_FTP_TYPE_D;
+ break;
+ default:
+ *type = LAC_FTP_TYPE_NONE;
+ break;
+ }
+ }
+ else
+ *type = LAC_FTP_TYPE_NONE;
+}
+
+enum {
+ LAC_HTTP_DEFAULT_PORT = 80,
+ LAC_FTP_DEFAULT_PORT = 21
+};
+
+static void
+lac_uri_unknown_to_http (LacUri *uri)
+{
+ gchar *host;
+ gint port;
+ gchar *path;
+ gchar *query;
+
+ if (uri->u.unknown.scheme)
+ g_free (uri->u.unknown.scheme);
+
+ lac_uri_authority_to_port_and_host (uri->u.unknown.authority,
+ &host, &port);
+ if (port == -1)
+ port = LAC_HTTP_DEFAULT_PORT;
+ g_free (uri->u.unknown.authority);
+
+ if (uri->u.unknown.path == NULL)
+ path = g_strdup ("/");
+ else
+ path = uri->u.unknown.path;
+ query = uri->u.unknown.query;
+
+ uri->scheme = LAC_SCHEME_HTTP;
+ uri->u.http.host = host;
+ uri->u.http.port = port;
+ uri->u.http.path = path;
+ uri->u.http.query = query;
+}
+
+static void
+lac_uri_unknown_to_ftp (LacUri *uri)
+{
+ gchar *host;
+ gchar *username;
+ gchar *password;
+ gint port;
+ gchar *path;
+ LacFtpType type;
+
+ g_assert (uri->scheme == LAC_SCHEME_UNKNOWN);
+
+ if (uri->u.unknown.scheme)
+ g_free (uri->u.unknown.scheme);
+
+ lac_uri_authority_to_username_and_password (uri->u.unknown.authority,
+ &username, &password);
+ lac_uri_authority_to_port_and_host (uri->u.unknown.authority,
+ &host, &port);
+ if (port == -1)
+ port = LAC_FTP_DEFAULT_PORT;
+ g_free (uri->u.unknown.authority);
+
+ if (uri->u.unknown.path == NULL)
+ path = g_strdup ("/");
+ else
+ path = uri->u.unknown.path;
+ lac_uri_path_to_ftp_type (uri->u.unknown.path, &type);
+
+ if (uri->u.unknown.query)
+ g_free (uri->u.unknown.query); /* RFC 1808: querys are not
+ allowed with the ftp scheme */
+
+ uri->scheme = LAC_SCHEME_FTP;
+ uri->u.ftp.host = host;
+ uri->u.ftp.username = username;
+ uri->u.ftp.password = password;
+ uri->u.ftp.port = port;
+ uri->u.ftp.path = path;
+ uri->u.ftp.type = type;
+}
+
+/* The functions
+ *
+ * is_uri_relative(),
+ * remove_internal_relative_components (),
+ * make_full_uri_from_relative()
+ *
+ * below are from GnomeVFS:
+ *
+ * Copyright (C) 1999 Free Software Foundation
+ * Copyright (C) 2000, 2001 Eazel, Inc.
+ *
+ * Author: Ettore Perazzoli <ettore@gnu.org>
+ */
+
+static gboolean
+is_uri_relative (const char *uri)
+{
+ const char *current;
+
+ /* RFC 2396 section 3.1 */
+ for (current = uri ;
+ *current
+ && ((*current >= 'a' && *current <= 'z')
+ || (*current >= 'A' && *current <= 'Z')
+ || (*current >= '0' && *current <= '9')
+ || ('-' == *current)
+ || ('+' == *current)
+ || ('.' == *current)) ;
+ current++)
+ ;
+
+ return !(':' == *current);
+}
+
+/*
+ * FIXME this is not the simplest or most time-efficent way
+ * to do this. Probably a far more clear way of doing this processing
+ * is to split the path into segments, rather than doing the processing
+ * in place.
+ */
+static void
+remove_internal_relative_components (char *uri_current)
+{
+ char *segment_prev, *segment_cur;
+ gsize len_prev, len_cur;
+
+ len_prev = len_cur = 0;
+ segment_prev = NULL;
+
+ segment_cur = uri_current;
+
+ while (*segment_cur)
+ {
+ len_cur = strcspn (segment_cur, "/");
+
+ if (len_cur == 1 && segment_cur[0] == '.')
+ {
+ /* Remove "." 's */
+ if (segment_cur[1] == '\0')
+ {
+ segment_cur[0] = '\0';
+ break;
+ }
+ else
+ {
+ memmove (segment_cur, segment_cur + 2, strlen (segment_cur + 2) + 1);
+ continue;
+ }
+ }
+ else if (len_cur == 2 && segment_cur[0] == '.' && segment_cur[1] == '.' )
+ {
+ /* Remove ".."'s (and the component to the left of it) that aren't at the
+ * beginning or to the right of other ..'s
+ */
+ if (segment_prev)
+ {
+ if (! (len_prev == 2
+ && segment_prev[0] == '.'
+ && segment_prev[1] == '.'))
+ {
+ if (segment_cur[2] == '\0')
+ {
+ segment_prev[0] = '\0';
+ break;
+ }
+ else
+ {
+ memmove (segment_prev, segment_cur + 3, strlen (segment_cur + 3) + 1);
+
+ segment_cur = segment_prev;
+ len_cur = len_prev;
+
+ /* now we find the previous segment_prev */
+ if (segment_prev == uri_current)
+ {
+ segment_prev = NULL;
+ }
+ else if (segment_prev - uri_current >= 2)
+ {
+ segment_prev -= 2;
+ for ( ; segment_prev > uri_current && segment_prev[0] != '/'
+ ; segment_prev-- );
+ if (segment_prev[0] == '/')
+ {
+ segment_prev++;
+ }
+ }
+ continue;
+ }
+ }
+ }
+ }
+
+ /* Forward to next segment */
+
+ if (segment_cur [len_cur] == '\0')
+ {
+ break;
+ }
+
+ segment_prev = segment_cur;
+ len_prev = len_cur;
+ segment_cur += len_cur + 1;
+ }
+}
+
+/* If I had known this relative uri code would have ended up this long, I would
+ * have done it a different way
+ */
+static char *
+make_full_uri_from_relative (const char *base_uri, const char *uri)
+{
+ char *result = NULL;
+
+ g_return_val_if_fail (base_uri != NULL, g_strdup (uri));
+ g_return_val_if_fail (uri != NULL, NULL);
+
+ /* See section 5.2 in RFC 2396 */
+
+ /* FIXME bugzilla.eazel.com 4413: This function does not take
+ * into account a BASE tag in an HTML document, so its
+ * functionality differs from what Mozilla itself would do.
+ */
+
+ if (is_uri_relative (uri))
+ {
+ char *mutable_base_uri;
+ char *mutable_uri;
+
+ char *uri_current;
+ gsize base_uri_length;
+ char *separator;
+
+ /* We may need one extra character
+ * to append a "/" to uri's that have no "/"
+ * (such as help:)
+ */
+
+ mutable_base_uri = g_malloc(strlen(base_uri)+2);
+ strcpy (mutable_base_uri, base_uri);
+
+ uri_current = mutable_uri = g_strdup (uri);
+
+ /* Chew off Fragment and Query from the base_url */
+
+ separator = strrchr (mutable_base_uri, '#');
+
+ if (separator)
+ *separator = '\0';
+
+ separator = strrchr (mutable_base_uri, '?');
+
+ if (separator)
+ *separator = '\0';
+
+ if ('/' == uri_current[0] && '/' == uri_current [1])
+ {
+ /* Relative URI's beginning with the authority
+ * component inherit only the scheme from their parents
+ */
+
+ separator = strchr (mutable_base_uri, ':');
+
+ if (separator)
+ separator[1] = '\0';
+ }
+ else if ('/' == uri_current[0])
+ {
+ /* Relative URI's beginning with '/' absolute-path based
+ * at the root of the base uri
+ */
+
+ separator = strchr (mutable_base_uri, ':');
+
+ /* g_assert (separator), really */
+ if (separator)
+ {
+ /* If we start with //, skip past the authority section */
+ if ('/' == separator[1] && '/' == separator[2])
+ {
+ separator = strchr (separator + 3, '/');
+ if (separator)
+ separator[0] = '\0';
+ }
+ else
+ {
+ /* If there's no //, just assume the scheme is the root */
+ separator[1] = '\0';
+ }
+ }
+ }
+ else if ('#' != uri_current[0])
+ {
+ /* Handle the ".." convention for relative uri's */
+
+ /* If there's a trailing '/' on base_url, treat base_url
+ * as a directory path.
+ * Otherwise, treat it as a file path, and chop off the filename
+ */
+
+ base_uri_length = strlen (mutable_base_uri);
+ if ('/' == mutable_base_uri[base_uri_length-1])
+ {
+ /* Trim off '/' for the operation below */
+ mutable_base_uri[base_uri_length-1] = 0;
+ }
+ else
+ {
+ separator = strrchr (mutable_base_uri, '/');
+ if (separator)
+ *separator = '\0';
+ }
+
+ remove_internal_relative_components (uri_current);
+
+ /* handle the "../"'s at the beginning of the relative URI */
+ while (0 == strncmp ("../", uri_current, 3))
+ {
+ uri_current += 3;
+ separator = strrchr (mutable_base_uri, '/');
+ if (separator)
+ {
+ *separator = '\0';
+ }
+ else
+ {
+ /* <shrug> */
+ break;
+ }
+ }
+
+ /* handle a ".." at the end */
+ if (uri_current[0] == '.' && uri_current[1] == '.'
+ && uri_current[2] == '\0')
+ {
+
+ uri_current += 2;
+ separator = strrchr (mutable_base_uri, '/');
+ if (separator)
+ {
+ *separator = '\0';
+ }
+ }
+
+ /* Re-append the '/' */
+ mutable_base_uri [strlen(mutable_base_uri)+1] = '\0';
+ mutable_base_uri [strlen(mutable_base_uri)] = '/';
+ }
+
+ result = g_strconcat (mutable_base_uri, uri_current, NULL);
+ g_free (mutable_base_uri);
+ g_free (mutable_uri);
+
+ }
+ else
+ {
+ result = g_strdup (uri);
+ }
+
+ return result;
+}
+
+static LacUri *
+lac_uri_new_from_full_str (const gchar *str)
+{
+ LacUri *uri;
+
+ g_return_val_if_fail (str != NULL, NULL);
+
+ uri = lac_uri_parse_unknown (str);
+
+ g_assert (uri->scheme == LAC_SCHEME_UNKNOWN);
+
+ if (uri->u.unknown.scheme)
+ {
+ gchar *scheme = g_ascii_strdown (uri->u.unknown.scheme, -1);
+
+ if (strcmp (scheme, "http") == 0)
+ lac_uri_unknown_to_http (uri);
+ else if (strcmp (scheme, "ftp") == 0)
+ lac_uri_unknown_to_ftp (uri);
+
+ g_free (scheme);
+ }
+ return uri;
+}
+
+static void
+lac_uri_unknown_to_known (LacUri *uri)
+{
+ gchar *scheme = g_ascii_strdown (uri->u.unknown.scheme, -1);
+
+ if (strcmp (scheme, "http") == 0)
+ lac_uri_unknown_to_http (uri);
+ else if (strcmp (scheme, "ftp") == 0)
+ lac_uri_unknown_to_ftp (uri);
+
+ g_free (scheme);
+}
+
+static void
+merge_uris (const LacUri *base_uri, LacUri *uri)
+{
+
+}
+
+#if 0
+LacUri *
+new_lac_uri_new_from_str (const LacUri *base_uri, const gchar *str)
+{
+ LacUri *uri;
+ LacScheme scheme;
+
+ g_return_val_if_fail (str != NULL, NULL);
+
+ /* see the algorithm in RFC 2396, section 5 */
+
+ /* step 1 */
+ uri = lac_uri_parse_unknown (str);
+ g_assert (uri->scheme == LAC_SCHEME_UNKNOWN);
+
+ /* step 2 */
+ if ((uri->u.unknown.path == NULL || *uri->u.unknown.patch == '\0') &&
+ !uri->u.unknown.scheme &&
+ !uri->u.unknown.authority &&
+ !uri->u.unknown.query)
+ {
+ /* FIXME - this is not right */
+ return lac_uri_copy (base_uri);
+ }
+
+ /* step 3 */
+ if (uri->u.unknown.scheme)
+ {
+ lac_uri_unknown_to_known (uri);
+ return uri;
+ }
+
+ scheme = base_uri->scheme;
+
+ /* step 4 */
+ if (!uri->u.unknown.authority)
+ {
+ if (base_uri->u.unknown.authority)
+ uri->u.unknown.authority = g_strdup (base_uri->u.unknown.authority);
+ else
+ uri->u.unknown.authority = NULL;
+
+ /* step 5 */
+ if (!uri->u.unknown.path && !base_uri->u.unknown.path)
+ {
+ uri->u.unknown.path = g_strdup (base
+ }
+ else if || uri->u.unknown.path[0] != '/')
+ {
+ /* step 6 */
+ merge_uris (base_uri, uri);
+ }
+ }
+
+ /* step 7 */
+step_7:
+ network_path = TRUE;
+
+ uri->scheme = scheme;
+ if (uri->scheme == LAC_SCHEME_UNKNOWN)
+ lac_uri_unknown_to_known (uri);
+
+ return uri;
+}
+#endif
+
+LacUri *
+lac_uri_new_from_str (const LacUri *base, const gchar *str)
+{
+ LacUri *result;
+ gchar *base_str = NULL;
+ gchar *full_str;
+
+ g_return_val_if_fail (str != NULL, NULL);
+
+ if (base)
+ base_str = lac_uri_string (base);
+
+ if (base_str)
+ full_str = make_full_uri_from_relative (base_str, str);
+ else
+ full_str = g_strdup (str);
+
+ if (base_str)
+ g_free (base_str);
+
+ result = lac_uri_new_from_full_str (full_str);
+
+ g_free (full_str);
+
+ return result;
+}
+
+void
+lac_uri_free (LacUri *uri)
+{
+ g_return_if_fail (uri != NULL);
+ switch (uri->scheme)
+ {
+ case LAC_SCHEME_UNKNOWN:
+ if (uri->u.unknown.scheme != NULL)
+ g_free (uri->u.unknown.scheme);
+ if (uri->u.unknown.authority)
+ g_free (uri->u.unknown.authority);
+ if (uri->u.unknown.path)
+ g_free (uri->u.unknown.path);
+ if (uri->u.unknown.query)
+ g_free (uri->u.unknown.query);
+ break;
+ case LAC_SCHEME_HTTP:
+ if (uri->u.http.host)
+ g_free (uri->u.http.host);
+ if (uri->u.http.path)
+ g_free (uri->u.http.path);
+ if (uri->u.http.query)
+ g_free (uri->u.http.query);
+ break;
+ case LAC_SCHEME_FTP:
+ if (uri->u.ftp.host)
+ g_free (uri->u.ftp.host);
+ if (uri->u.ftp.username)
+ g_free (uri->u.ftp.username);
+ if (uri->u.ftp.password)
+ g_free (uri->u.ftp.password);
+ if (uri->u.ftp.path)
+ g_free (uri->u.ftp.path);
+ break;
+ }
+ if (uri->fragment)
+ g_free (uri->fragment);
+ g_free (uri);
+}
+
+LacUri *
+lac_uri_copy (const LacUri *uri)
+{
+ LacUri *copy;
+
+ copy = g_new(LacUri, 1);
+
+ copy->scheme = uri->scheme;
+ switch (uri->scheme)
+ {
+ case LAC_SCHEME_UNKNOWN:
+ copy->u.unknown.scheme = g_strdup (uri->u.unknown.scheme);
+ copy->u.unknown.authority = g_strdup (uri->u.unknown.authority);
+ copy->u.unknown.path = g_strdup (uri->u.unknown.path);
+ copy->u.unknown.query = g_strdup (uri->u.unknown.query);
+ break;
+ case LAC_SCHEME_HTTP:
+ copy->u.http.host = g_strdup (uri->u.http.host);
+ copy->u.http.port = uri->u.http.port;
+ copy->u.http.path = g_strdup (uri->u.http.path);
+ copy->u.http.query = g_strdup (uri->u.http.query);
+ break;
+ case LAC_SCHEME_FTP:
+ copy->u.ftp.host = g_strdup (uri->u.ftp.host);
+ copy->u.ftp.username = g_strdup (uri->u.ftp.username);
+ copy->u.ftp.password = g_strdup (uri->u.ftp.password);
+ copy->u.ftp.port = uri->u.ftp.port;
+ copy->u.ftp.path = g_strdup (uri->u.ftp.path);
+ copy->u.ftp.type = uri->u.ftp.type;
+ break;
+ }
+ copy->fragment = g_strdup (uri->fragment);
+
+ return copy;
+}
+
+gchar *
+lac_uri_string (const LacUri *uri)
+{
+ gchar *str = NULL;
+
+ switch (uri->scheme)
+ {
+ case LAC_SCHEME_UNKNOWN:
+ str = g_strconcat (uri->u.unknown.scheme,
+ uri->u.unknown.authority,
+ uri->u.unknown.path,
+ uri->u.unknown.query? "?" : "",
+ uri->u.unknown.query? uri->u.unknown.query : "",
+ NULL);
+ break;
+
+ case LAC_SCHEME_HTTP:
+ {
+ gchar *port_str;
+ gchar *query_str;
+
+ if (uri->u.http.port != LAC_HTTP_DEFAULT_PORT)
+ port_str = g_strdup_printf (":%d", uri->u.http.port);
+ else
+ port_str = g_strdup ("");
+
+ if (uri->u.http.query)
+ query_str = g_strdup_printf ("?%s", uri->u.http.query);
+ else
+ query_str = g_strdup ("");
+
+ str = g_strconcat ("http://",
+ uri->u.http.host, port_str,
+ uri->u.http.path, query_str,
+ NULL);
+
+ g_free (port_str);
+ g_free (query_str);
+ break;
+ }
+
+ case LAC_SCHEME_FTP:
+ /* FIXME */
+ g_warning ("ftp uri -> string is not implemented");
+
+ return NULL;
+ }
+
+ return str;
+}
+
+static gboolean
+my_strequal (const gchar *str1, const gchar *str2)
+{
+ if (str1 && !str2)
+ return FALSE;
+
+ if (str2 && !str1)
+ return FALSE;
+
+ if (str2 && str1)
+ return (strcmp (str1, str2) == 0);
+
+ if (!str1 && !str2)
+ return TRUE;
+
+ g_assert_not_reached();
+
+ return FALSE;
+}
+
+gboolean
+lac_uri_equal (const LacUri *uri1, const LacUri *uri2)
+{
+ if (uri1->scheme != uri2->scheme)
+ return FALSE;
+
+ switch (uri1->scheme)
+ {
+ case LAC_SCHEME_UNKNOWN:
+ if (!my_strequal (uri1->u.unknown.scheme, uri2->u.unknown.scheme))
+ return FALSE;
+ if (!my_strequal (uri1->u.unknown.authority, uri2->u.unknown.authority))
+ return FALSE;
+ if (!my_strequal (uri1->u.unknown.path, uri2->u.unknown.path))
+ return FALSE;
+ if (!my_strequal (uri1->u.unknown.query, uri2->u.unknown.query))
+ return FALSE;
+ break;
+
+ case LAC_SCHEME_HTTP:
+ if (!my_strequal (uri1->u.http.host, uri2->u.http.host))
+ return FALSE;
+ if (uri1->u.http.port != uri2->u.http.port)
+ return FALSE;
+ if (!my_strequal (uri1->u.http.path, uri2->u.http.path))
+ return FALSE;
+ if (!my_strequal (uri1->u.http.query, uri2->u.http.query))
+ return FALSE;
+ break;
+
+ case LAC_SCHEME_FTP:
+ g_assert_not_reached();
+ return FALSE;
+ break;
+ }
+
+ if (!my_strequal (uri1->fragment, uri2->fragment))
+ return FALSE;
+
+ return TRUE;
+}
diff --git a/src/lacwatch.c b/src/lacwatch.c
new file mode 100644
index 0000000..b85a3c7
--- /dev/null
+++ b/src/lacwatch.c
@@ -0,0 +1,328 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- */
+
+/* Lac - Library for asynchronous communication
+ * Copyright (C) 2001, 2002, 2003 Søren Sandmann (sandmann@daimi.au.dk)
+ *
+ * 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.
+ */
+
+#include "lac.h"
+
+typedef struct Watch Watch;
+
+struct Watch {
+ GSource source;
+ GPollFD poll_fd;
+ gboolean removed;
+
+ LacWatchCallback read_callback;
+ LacWatchCallback write_callback;
+ LacWatchCallback hangup_callback;
+ LacWatchCallback error_callback;
+ LacWatchCallback priority_callback;
+
+ gpointer data;
+};
+
+static GHashTable *watched_fds;
+
+static void
+init (void)
+{
+ if (!watched_fds)
+ watched_fds = g_hash_table_new (g_int_hash, g_int_equal);
+}
+
+static Watch *
+lookup_watch (gint fd)
+{
+ init ();
+
+ return g_hash_table_lookup (watched_fds, &fd);
+}
+
+static void
+internal_add_watch (Watch *watch)
+{
+ gpointer fd = &(watch->poll_fd.fd);
+
+ init ();
+
+ g_hash_table_insert (watched_fds, fd, watch);
+ g_source_add_poll ((GSource *)watch, &(watch->poll_fd));
+}
+
+static void
+internal_remove_watch (Watch *watch)
+{
+ gpointer fd = &(watch->poll_fd.fd);
+
+ init ();
+
+ watch->removed = TRUE;
+ g_source_remove_poll ((GSource *)watch, &(watch->poll_fd));
+ g_hash_table_remove (watched_fds, fd);
+}
+
+static gboolean
+watch_prepare (GSource *source,
+ gint *timeout)
+{
+ *timeout = -1;
+
+ return FALSE;
+}
+
+static gboolean
+watch_check (GSource *source)
+{
+ Watch *watch = (Watch *)source;
+ gint revents = watch->poll_fd.revents;
+
+ if (revents & (G_IO_NVAL))
+ {
+ /* This can happen if the user closes the file descriptor
+ * without first removing the watch. We silently ignore it
+ */
+ internal_remove_watch (watch);
+ g_source_unref (source);
+ return FALSE;
+ }
+
+ if ((revents & G_IO_HUP) && watch->hangup_callback)
+ return TRUE;
+
+ if ((revents & G_IO_IN) && watch->read_callback)
+ return TRUE;
+
+ if ((revents & G_IO_PRI) && watch->priority_callback)
+ return TRUE;
+
+ if ((revents & G_IO_ERR) && watch->error_callback)
+ return TRUE;
+
+ if ((revents & G_IO_OUT) && watch->write_callback)
+ return TRUE;
+
+ return FALSE;
+}
+
+static gboolean
+watch_dispatch (GSource *source,
+ GSourceFunc callback,
+ gpointer user_data)
+{
+ Watch *watch = (Watch *)source;
+ gint revents = watch->poll_fd.revents;
+ gboolean removed;
+
+ g_source_ref (source);
+
+ if (!watch->removed && (revents & G_IO_IN) && watch->read_callback)
+ watch->read_callback (watch->data);
+
+ if (!watch->removed && (revents & G_IO_PRI) && watch->priority_callback)
+ watch->priority_callback (watch->data);
+
+ if (!watch->removed && (revents & G_IO_OUT) && watch->write_callback)
+ watch->write_callback (watch->data);
+
+ if (!watch->removed && (revents & G_IO_ERR) && watch->error_callback)
+ watch->error_callback (watch->data);
+
+ if (!watch->removed && (revents & G_IO_HUP) && watch->hangup_callback)
+ watch->hangup_callback (watch->data);
+
+ removed = watch->removed;
+
+ g_source_unref (source);
+
+ if (removed)
+ return FALSE;
+
+ return TRUE;
+}
+
+static void
+watch_finalize (GSource *source)
+{
+}
+
+static void
+update_poll_mask (Watch *watch)
+{
+ gint events = 0;
+
+ g_source_remove_poll ((GSource *)watch, &(watch->poll_fd));
+
+ if (watch->read_callback)
+ events |= G_IO_IN;
+
+ if (watch->write_callback)
+ events |= G_IO_OUT;
+
+ if (watch->priority_callback)
+ events |= G_IO_PRI;
+
+ if (watch->hangup_callback)
+ events |= G_IO_HUP;
+
+ if (watch->error_callback)
+ events |= G_IO_ERR;
+
+ watch->poll_fd.events = events;
+
+ g_source_add_poll ((GSource *)watch, &(watch->poll_fd));
+}
+
+void
+lac_fd_add_watch (gint fd,
+ gpointer data)
+{
+ static GSourceFuncs watch_funcs = {
+ watch_prepare,
+ watch_check,
+ watch_dispatch,
+ watch_finalize,
+ };
+ Watch *watch = lookup_watch (fd);
+
+ g_return_if_fail (fd > 0);
+ g_return_if_fail (watch == NULL);
+
+ watch = (Watch *)g_source_new (&watch_funcs, sizeof (Watch));
+ g_source_set_can_recurse ((GSource *)watch, TRUE);
+ g_source_attach ((GSource *)watch, NULL);
+
+ watch->poll_fd.fd = fd;
+ watch->poll_fd.events = 0;
+ watch->removed = FALSE;
+
+ watch->read_callback = NULL;
+ watch->write_callback = NULL;
+ watch->hangup_callback = NULL;
+ watch->error_callback = NULL;
+ watch->priority_callback = NULL;
+
+ watch->data = data;
+
+ internal_add_watch (watch);
+}
+
+void
+lac_fd_set_read_callback (gint fd,
+ LacWatchCallback read_cb)
+{
+ Watch *watch = lookup_watch (fd);
+
+ g_return_if_fail (fd > 0);
+ g_return_if_fail (watch != NULL);
+
+ if (watch->read_callback != read_cb)
+ {
+ watch->read_callback = read_cb;
+ update_poll_mask (watch);
+ }
+}
+
+void
+lac_fd_set_write_callback (gint fd,
+ LacWatchCallback write_cb)
+{
+ Watch *watch = lookup_watch (fd);
+
+ g_return_if_fail (fd > 0);
+ g_return_if_fail (watch != NULL);
+
+ if (watch->write_callback != write_cb)
+ {
+ watch->write_callback = write_cb;
+ update_poll_mask (watch);
+ }
+}
+
+void
+lac_fd_set_hangup_callback (gint fd,
+ LacWatchCallback hangup_cb)
+{
+ Watch *watch = lookup_watch (fd);
+
+ g_return_if_fail (fd > 0);
+ g_return_if_fail (watch != NULL);
+
+ if (watch->hangup_callback != hangup_cb)
+ {
+ watch->hangup_callback = hangup_cb;
+ update_poll_mask (watch);
+ }
+}
+
+void
+lac_fd_set_error_callback (gint fd,
+ LacWatchCallback error_cb)
+{
+ Watch *watch = lookup_watch (fd);
+
+ g_return_if_fail (fd > 0);
+ g_return_if_fail (watch != NULL);
+
+ if (watch->error_callback != error_cb)
+ {
+ watch->error_callback = error_cb;
+ update_poll_mask (watch);
+ }
+}
+
+void
+lac_fd_set_priority_callback (gint fd,
+ LacWatchCallback priority_cb)
+{
+ Watch *watch = lookup_watch (fd);
+
+ g_return_if_fail (fd > 0);
+ g_return_if_fail (watch != NULL);
+
+ if (watch->priority_callback != priority_cb)
+ {
+ watch->priority_callback = priority_cb;
+ update_poll_mask (watch);
+ }
+}
+
+void
+lac_fd_remove_watch (gint fd)
+{
+ Watch *watch = lookup_watch (fd);
+
+ g_return_if_fail (fd > 0);
+
+ if (!watch)
+ return;
+
+ internal_remove_watch (watch);
+ g_source_unref ((GSource *)watch);
+}
+
+gboolean
+lac_fd_is_watched (gint fd)
+{
+ g_return_val_if_fail (fd > 0, FALSE);
+
+ if (lookup_watch (fd))
+ return TRUE;
+ else
+ return FALSE;
+}
diff --git a/src/makecopyright b/src/makecopyright
new file mode 100755
index 0000000..089e79c
--- /dev/null
+++ b/src/makecopyright
@@ -0,0 +1,128 @@
+#!/bin/sh
+
+
+copyright_glib ()
+{
+ cat << EOF
+/* GLIB - Library of useful routines for C programming
+ * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ *
+ * 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.
+ */
+EOF
+}
+
+copyright_gdk ()
+{
+ cat << EOF
+/* GDK - The GIMP Drawing Kit
+ * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ *
+ * 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.
+ */
+EOF
+}
+
+copyright_gtk ()
+{
+ cat << EOF
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ *
+ * 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.
+ */
+EOF
+}
+
+copyright_interp ()
+{
+ cat << EOF
+/* GTK Interp - The GTK Interpreter
+ * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ *
+ * 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.
+ */
+EOF
+}
+
+
+exclude_files="./glib/gconfig.h"
+
+for file in `find . -name "*.[ch]" -print`; do
+ exclude=`echo $exclude_files | grep $file`
+
+ if test "x$exclude" = "x"; then
+ dir=`dirname $file`
+ if test "x$dir" != "x."; then
+ subdir=`basename $dir`
+
+ grepout=`grep Copyright $file`
+ if test "x$grepout" = "x"; then
+ backup_dir="$dir/bak"
+ if test ! -d $backup_dir; then
+ echo "making directory: $backup_dir"
+ mkdir $backup_dir
+ fi
+
+ echo $file
+
+ filename=`basename $file`
+ cp $file $backup_dir/$filename
+ copyright_$subdir > $file
+ cat $backup_dir/$filename >> $file
+ fi
+ fi
+ fi
+done
diff --git a/tests/Makefile.am b/tests/Makefile.am
new file mode 100755
index 0000000..a18e4c2
--- /dev/null
+++ b/tests/Makefile.am
@@ -0,0 +1,42 @@
+TESTPROGRAMS = \
+ addr-test \
+ simple-test \
+ dns-test \
+ dns-test-async \
+ dns-test2 \
+ dns-wait-test \
+ rdns-test-async \
+ uri-test \
+ http-test \
+ connection-test \
+ connection2-test \
+ udp-test \
+ watch-test \
+ lacwget
+
+lacwget_SOURCES = \
+ lacwget.c \
+ htmlparser.c \
+ htmlparser.h
+
+noinst_PROGRAMS = $(TESTPROGRAMS)
+
+INCLUDES = -I$(top_srcdir)/src $(MODULES_XML_CFLAGS)
+
+addr_test_LDADD = $(top_builddir)/src/liblac-1.la
+simple_test_LDADD = $(top_builddir)/src/liblac-1.la
+dns_test_LDADD = $(top_builddir)/src/liblac-1.la
+dns_test_async_LDADD = $(top_builddir)/src/liblac-1.la
+dns_test2_LDADD = $(top_builddir)/src/liblac-1.la
+dns_wait_test_LDADD = $(top_builddir)/src/liblac-1.la
+rdns_test_async_LDADD = $(top_builddir)/src/liblac-1.la
+uri_test_LDADD = $(top_builddir)/src/liblac-1.la
+connection_test_LDADD = $(top_builddir)/src/liblac-1.la
+connection2_test_LDADD = $(top_builddir)/src/liblac-1.la
+http_test_LDADD = $(top_builddir)/src/liblac-1.la
+udp_test_LDADD = $(top_builddir)/src/liblac-1.la
+watch_test_LDADD = $(top_builddir)/src/liblac-1.la
+lacwget_LDADD = $(top_builddir)/src/liblac-1.la $(MODULES_XML_LIBS)
+lacwget_CFLAGS = $(MODULES_XML_CFLAGS)
+
+EXTRA_DIST = trouble-sites.sh
diff --git a/tests/activity.c b/tests/activity.c
new file mode 100644
index 0000000..6e80ee7
--- /dev/null
+++ b/tests/activity.c
@@ -0,0 +1,124 @@
+/* activity can be in one of four states:
+ *
+ * INIT
+ * RUNNING,
+ * CANCELED,
+ * TIMED_OUT,
+ * FINISHED
+ *
+ * lac_activity_wait() will return whenever the activity changes state, and
+ * whenever lac_activity_end() is called.
+ *
+ * it switches from INIT to RUNNING when lac_activity_begin() is called
+ * [what about time out?]
+ * it switches to FINISHED when lac_activity_end() is called the same
+ * number of times lac_activity_begin() has been called.
+ * it switches to TIMED_OUT when
+ * it switches to CANCELED when lac_activity_cancel has been called
+ * it switches from CANCELED or TIMED_OUT to RUNNING when
+ * lac_activity_continue() is called
+ *
+ * right after lac_actvity_new () the state of the activity is INIT
+ *
+ */
+
+void lac_address_new_lookup_from_name (name, activity, callback, data);
+void lac_address_new_from_name (name, activity, &addr, &err);
+
+/* trivial example */
+
+/* block until address (or error) arrives */
+lac_address_new_from_name ("slashdot.org", NULL, &addr, &err);
+
+
+/* simple example */
+static void
+on_cancel (GtkDialog *dialog, LacActivity *activity)
+{
+ lac_activity_cancel (activity);
+}
+
+activity = lac_activity_new ();
+
+g_signal_connect (dialog, "response", on_cancel, activity);
+
+lac_activity_set_timeout (activity, 5000);
+addr = lac_address_new_from_name ("slashdot.org", activity, &err);
+if (lac_activity_timed_out (activity) || lac_activity_canceled (activity))
+ /* canceled */;
+else
+ /* finished */;
+lac_activity_free (activity);
+
+
+/* callback example */
+activity = lac_activity_new ();
+lac_address_new_lookup_from_name (activity, "slashdot.org", cb, data);
+lac_activity_set_timeout (activity, 200);
+
+
+/* complex example */
+static void
+on_cancel (GtkDialog *dialog, LacActivity *activity)
+{
+ g_main_loop_quit (loop);
+}
+
+dialog = create_dialog ();
+
+activity = lac_activity_new ();
+f1 = lac_address_new_future_from_name (activity, "slashdot.org");
+f2 = lac_address_new_future_from_name (activity, "gnomedesktop.org");
+f3 = lac_address_new_future_from_name (activity, "news.kde.org");
+lac_activity_set_timeout (activity, 100);
+
+while (lac_activity_running (activity))
+{
+ lac_activity_wait (activity);
+
+ if (!addr1 && lac_future_ready (f1))
+ {
+ addr1 = lac_future_get_data (f1);
+ /* update label 1 */
+ }
+ else if (!addr2 && lac_future_ready (f2))
+ {
+ addr2 = lac_future_get_data (f2);
+ /* update label 2 */
+ }
+ else if (!addr3 && lac_future_ready (f3))
+ {
+ addr3 = lac_future_get_data (f3);
+ /* update label 3 */
+ }
+ else
+ {
+ elapsed = g_timer_elapsed (timer, NULL);
+ if (!lac_activity_canceled (activity) && elapsed < 5000)
+ {
+ gtk_progress_bar_set_percentage (pbar, 100 * (elapsed / 5000));
+ lac_activity_continue (activity);
+ }
+ }
+}
+
+gtk_widget_destroy (dialog);
+g_timer_destroy (timer);
+
+if (lac_activity_canceled (activity))
+{
+ /* user canceled */
+}
+else if (lac_activity_timed_out (activity))
+{
+ /* no response within 5000 seconds */
+}
+else
+{
+ /* activity finished normally */
+}
+
+lac_activity_free (activity);
+lac_future_free (future1);
+lac_future_free (future2);
+lac_future_free (future3);
diff --git a/tests/addr-test.c b/tests/addr-test.c
new file mode 100644
index 0000000..1553d44
--- /dev/null
+++ b/tests/addr-test.c
@@ -0,0 +1,41 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- */
+
+/* Lac - Library for asynchronous communication
+ * Copyright (C) 2002 Søren Sandmann (sandmann@daimi.au.dk)
+ *
+ * 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.
+ */
+#include <lac.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+int
+main (int argc, char *argv[])
+{
+ int fd = lac_socket_udp(NULL);
+ int port;
+ LacAddress *loopback = lac_address_new_from_a_b_c_d (127, 0, 0, 1);
+ gchar buf[128];
+ LacAddress *my_address;
+
+ lac_sendto (fd, "Hello", strlen ("Hello"), loopback, 128, NULL);
+ g_print ("sent\n");
+ lac_recvfrom (fd, buf, sizeof (buf), &my_address, &port, NULL);
+
+ g_print ("my address: %s\n", lac_address_to_string (my_address));
+ return 0;
+}
diff --git a/tests/cname-test.c b/tests/cname-test.c
new file mode 100644
index 0000000..a57d956
--- /dev/null
+++ b/tests/cname-test.c
@@ -0,0 +1,42 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- */
+
+/* Lac - Library for asynchronous communication
+ * Copyright (C) 2002 Søren Sandmann (sandmann@daimi.au.dk)
+ *
+ * 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.
+ */
+#include <lac.h>
+
+static void
+disaster (GError *err)
+{
+ g_print ("error: %s\n", err->message);
+ exit (1);
+}
+
+int
+main (int argc, char *argv[])
+{
+ GError *err = NULL
+ int fd = lac_socket_udp (&err);
+ if (fd < 0)
+ disaster (err);
+
+ if (lac_send (fd, "Hello", strlen ("Hello"), &err) < 0)
+ disaster (err);
+
+
+}
diff --git a/tests/connection-test.c b/tests/connection-test.c
new file mode 100644
index 0000000..e928f43
--- /dev/null
+++ b/tests/connection-test.c
@@ -0,0 +1,127 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- */
+
+/* Lac - Library for asynchronous communication
+ * Copyright (C) 2002 Søren Sandmann (sandmann@daimi.au.dk)
+ *
+ * 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.
+ */
+#include <lac.h>
+#include <string.h>
+
+GMainLoop *main_loop;
+int n_connections = 0;
+
+static void
+connection_write (LacConnection *connection, gchar *str)
+{
+#if 0
+ g_print ("%s", str);
+#endif
+ lac_connection_write (connection, str, strlen (str));
+}
+
+static void
+conn_callback (LacConnection *connection, const LacConnectionEvent *event)
+{
+ gchar *s;
+
+ g_print ("connection to %s:80: ", (gchar *)lac_connection_get_data (connection));
+ switch (event->type)
+ {
+ case LAC_CONNECTION_EVENT_CONNECT:
+ g_print ("CONNECT\n");
+ connection_write (connection, "GET / HTTP/1.1\r\n");
+ connection_write (connection, "Host: ");
+ connection_write (connection, lac_connection_get_data (connection));
+ connection_write (connection, "\r\n\r\n");
+ break;
+
+ case LAC_CONNECTION_EVENT_READ:
+ g_print ("READ (%d bytes)\n", event->read.len);
+ s = g_new (guchar, event->read.len + 1);
+ strncpy (s, event->read.data, event->read.len);
+ s[event->read.len] = '\0';
+ g_print ("%s\n", s);
+ g_free (s);
+ break;
+
+ case LAC_CONNECTION_EVENT_CLOSE:
+ g_print ("CLOSE (%s)\n", event->close.remote_closed?
+ "remote" : "local");
+ lac_connection_unref (connection);
+ if (--n_connections == 0)
+ g_main_loop_quit (main_loop);
+ break;
+
+ case LAC_CONNECTION_EVENT_ERROR:
+ g_print ("ERROR (%s)\n", event->error.err->message);
+ lac_connection_unref (connection);
+ if (--n_connections == 0)
+ g_main_loop_quit (main_loop);
+ break;
+
+ default:
+ g_print ("event->type == %d?\n", event->type);
+ g_assert_not_reached ();
+ break;
+ }
+}
+
+static void
+callback (const LacAddress *addr, gpointer data, const GError *err)
+{
+ if (err)
+ {
+ g_print (" (%p) ", err->message);
+ g_print ("%s\n", err->message);
+ }
+ else
+ {
+ LacConnection *connection;
+
+ connection = lac_connection_new (addr, 80, conn_callback, data);
+ }
+}
+
+int
+main (int argc, char *argv[])
+{
+ int i;
+
+#if 0
+ lac_set_verbose (TRUE);
+#endif
+
+ if (argc > 1)
+ {
+ for (i = 1; i < argc; ++i)
+ {
+ ++n_connections;
+ lac_address_new_lookup_from_name (argv[i], callback, argv[i]);
+ }
+ }
+ else
+ {
+ g_print ("usage %s <names>\n", argv[0]);
+ return 1;
+ }
+
+ main_loop = g_main_loop_new (NULL, TRUE);
+ g_assert (main_loop);
+ g_main_loop_run (main_loop);
+
+ return 0;
+}
diff --git a/tests/connection2-test.c b/tests/connection2-test.c
new file mode 100644
index 0000000..c2bc370
--- /dev/null
+++ b/tests/connection2-test.c
@@ -0,0 +1,70 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- */
+
+/* Lac - Library for asynchronous communication
+ * Copyright (C) 2002 Søren Sandmann (sandmann@daimi.au.dk)
+ *
+ * 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.
+ */
+#include <lac.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <netinet/tcp.h>
+#include <string.h>
+
+int
+main (int argc, char *argv[])
+{
+ LacAddress *addr =
+ lac_address_new_from_name_wait ("www.toyota.co.jp", NULL, NULL);
+ int fd;
+ guchar blah[60000];
+ GTimer *timer;
+ int len;
+
+ g_assert (addr);
+
+ fd = lac_socket_tcp (NULL);
+
+ g_assert (fd > 0);
+
+ g_assert (lac_connect (fd, addr, 80, NULL));
+
+#define line1 "GET / HTTP/1.1\r\n"
+#define line2 "host: www.toyota.co.jp\r\n\r\n"
+
+ timer = g_timer_new ();
+
+ lac_send (fd, line1, strlen (line1), NULL);
+ lac_send (fd, line2, strlen (line2), NULL);
+
+ lac_set_nagle (fd, FALSE, NULL);
+ lac_set_nagle (fd, TRUE, NULL);
+
+#if 0
+ lac_send (fd, line1 line2, strlen (line1) + strlen (line2), NULL);
+#endif
+
+ while ((len = lac_recv (fd, blah, sizeof (blah), NULL)) > 0)
+ {
+ int i;
+ for (i = 0; i < len; ++i)
+ g_print ("%c", blah[i]);
+ }
+
+ g_print ("elapsed: %f\n", g_timer_elapsed (timer, NULL));
+
+ return 0;
+}
diff --git a/tests/dns-test-async.c b/tests/dns-test-async.c
new file mode 100644
index 0000000..2896349
--- /dev/null
+++ b/tests/dns-test-async.c
@@ -0,0 +1,65 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- */
+
+/* Lac - Library for asynchronous communication
+ * Copyright (C) 2002 Søren Sandmann (sandmann@daimi.au.dk)
+ *
+ * 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.
+ */
+#include <lac.h>
+
+static void
+callback (const LacAddress *addr, gpointer data, const GError *err)
+{
+ gchar *name = data;
+
+ g_print ("%s: ", name);
+
+ if (err)
+ {
+ g_print (" (%p) ", err->message);
+ g_print ("%s\n", err->message);
+ }
+ else
+ {
+ g_print ("%s\n", lac_address_to_string (addr));
+ }
+}
+
+int
+main (int argc, char *argv[])
+{
+ int i;
+ GMainLoop *main_loop;
+
+ lac_set_verbose (TRUE);
+
+ if (argc > 1)
+ {
+ for (i = 1; i < argc; ++i)
+ lac_address_new_lookup_from_name (argv[i], callback, argv[i]);
+ }
+ else
+ {
+ g_print ("usage %s <names>\n", argv[0]);
+ return 1;
+ }
+
+ main_loop = g_main_loop_new (NULL, TRUE);
+ g_assert (main_loop);
+ g_main_loop_run (main_loop);
+
+ return 0;
+}
diff --git a/tests/dns-test.c b/tests/dns-test.c
new file mode 100644
index 0000000..7844fab
--- /dev/null
+++ b/tests/dns-test.c
@@ -0,0 +1,123 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- */
+
+/* Lac - Library for asynchronous communication
+ * Copyright (C) 2002 Søren Sandmann (sandmann@daimi.au.dk)
+ *
+ * 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.
+ */
+#include <lac.h>
+#include <string.h>
+
+static int n_outstanding;
+static GMainLoop *main_loop;
+
+static void
+name_callback (const gchar *name, gpointer data, const GError *err)
+{
+ gchar *addr_str = data;
+
+ if (err)
+ {
+ g_print ("reverse %s: %s\n", addr_str, err->message);
+ }
+ else
+ {
+ g_print ("reverse %s: %s\n", addr_str, name);
+ }
+ g_free (addr_str);
+}
+
+static void
+callback (const GPtrArray *addresses, gpointer data, const GError *err)
+{
+ gchar *name = data;
+
+ if (err)
+ {
+ g_print ("%s: %s\n", name, err->message);
+ }
+ else
+ {
+ int i;
+
+ if (addresses->len == 0)
+ g_print ("this should not happen\n");
+ for (i = 0; i < addresses->len; ++i)
+ {
+ gchar *addr = lac_address_to_string (addresses->pdata[i]);
+ g_print ("%s: %s\n", name, addr);
+
+ lac_address_lookup_name (addresses->pdata[i], name_callback, addr);
+ }
+
+ }
+
+ if (!strstr (name, "again"))
+ {
+ gchar *tmp;
+
+ n_outstanding += 2;
+ lac_address_new_lookup_all_from_name (name, callback, g_strdup_printf ("%s again", name));
+
+ tmp = g_ascii_strup (name, -1);
+ lac_address_new_lookup_all_from_name (tmp, callback, g_strdup_printf ("%s (UPPER) again", name));
+ g_free (tmp);
+ }
+
+#if 0
+ if (--n_outstanding == 0)
+ g_main_quit (main_loop);
+#endif
+ g_free (name);
+}
+
+int
+main (int argc, char *argv[])
+{
+ int i;
+
+ main_loop = g_main_loop_new (NULL, TRUE);
+ g_log_set_always_fatal (G_LOG_LEVEL_WARNING | G_LOG_LEVEL_ERROR |
+ G_LOG_LEVEL_CRITICAL);
+
+ lac_set_verbose (TRUE);
+
+#if 0
+ torture_test();
+#endif
+
+ if (argc > 1)
+ {
+ for (i = 1; i < argc; ++i)
+ {
+ ++n_outstanding;
+ lac_address_new_lookup_all_from_name (argv[i], callback, g_strdup (argv[i]));
+ }
+ }
+ else
+ {
+ g_print ("usage %s <names>\n", argv[0]);
+ return 1;
+ }
+
+ g_assert (main_loop);
+#if 0
+ g_timeout_add (12000, g_main_loop_quit, main_loop);
+#endif
+ g_main_loop_run (main_loop);
+
+ return 0;
+}
diff --git a/tests/dns-test2.c b/tests/dns-test2.c
new file mode 100644
index 0000000..c70b91b
--- /dev/null
+++ b/tests/dns-test2.c
@@ -0,0 +1,122 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- */
+
+/* Lac - Library for asynchronous communication
+ * Copyright (C) 2002, 2003 Søren Sandmann (sandmann@daimi.au.dk)
+ *
+ * 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.
+ */
+#include <lac.h>
+#include <string.h>
+
+static int n_outstanding;
+static GMainLoop *main_loop;
+
+static void
+name_callback (const gchar *name, gpointer data, const GError *err)
+{
+ gchar *addr_str = data;
+
+ if (err)
+ {
+ g_print ("reverse %s: %s\n", addr_str, err->message);
+ }
+ else
+ {
+ g_print ("reverse %s: %s\n", addr_str, name);
+ }
+ g_free (addr_str);
+}
+
+static void
+callback (const GPtrArray *addresses, gpointer data, const GError *err)
+{
+ const gchar *name = data;
+
+ if (err)
+ {
+ g_print ("%s: %s\n", name, err->message);
+ }
+ else
+ {
+ int i;
+
+ GQueue *reverse_lookups = g_queue_new ();
+
+ g_assert (addresses->len > 0);
+
+ g_print ("%s: ", name);
+ for (i = 0; i < addresses->len; ++i)
+ {
+ gchar *addr_str = lac_address_to_string (addresses->pdata[i]);
+
+ g_print ("%s ", addr_str);
+
+ g_queue_push_tail (reverse_lookups, addresses->pdata[i]);
+ g_queue_push_tail (reverse_lookups, addr_str);
+ }
+ g_print ("\n");
+
+ while (!g_queue_is_empty (reverse_lookups))
+ {
+ LacAddress *address = g_queue_pop_head (reverse_lookups);
+ gpointer addr_string = g_queue_pop_head (reverse_lookups);
+
+ lac_address_lookup_name (address, name_callback, addr_string);
+ }
+ g_queue_free (reverse_lookups);
+ }
+}
+
+int
+main (int argc, char *argv[])
+{
+ int i;
+
+ main_loop = g_main_loop_new (NULL, TRUE);
+ g_log_set_always_fatal (G_LOG_LEVEL_WARNING | G_LOG_LEVEL_ERROR |
+ G_LOG_LEVEL_CRITICAL);
+
+ lac_set_verbose (TRUE);
+
+ if (argc > 1)
+ {
+ for (i = 1; i < argc; ++i)
+ {
+ LacActivity activity;
+ ++n_outstanding;
+ activity = lac_address_new_lookup_all_from_name (
+ argv[i], callback, argv[i]);
+
+ if (i == 3 || i == 4)
+ {
+ g_print ("canceling\n");
+ lac_activity_cancel (activity);
+ }
+
+ }
+ }
+ else
+ {
+ g_print ("usage %s <names>\n", argv[0]);
+ return 1;
+ }
+
+ g_assert (main_loop);
+ g_timeout_add (500, g_main_loop_quit, main_loop);
+ g_main_loop_run (main_loop);
+
+ return 0;
+}
diff --git a/tests/dns-wait-test.c b/tests/dns-wait-test.c
new file mode 100644
index 0000000..224368c
--- /dev/null
+++ b/tests/dns-wait-test.c
@@ -0,0 +1,55 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- */
+
+/* Lac - Library for asynchronous communication
+ * Copyright (C) 2003 Søren Sandmann (sandmann@daimi.au.dk)
+ *
+ * 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.
+ */
+#include <lac.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <netinet/tcp.h>
+#include <string.h>
+
+static gboolean
+timeout (gpointer data)
+{
+ LacActivity activity = data;
+ g_print ("hej\n");
+ lac_activity_cancel (activity);
+ return FALSE;
+}
+
+int
+main (int argc, char *argv[])
+{
+ LacAddress *addr;
+ LacActivity activity;
+ int id;
+
+ if (argc < 2)
+ return 0;
+
+ id = g_timeout_add (10, lac_activity_cancel, activity);
+ addr = lac_address_new_from_name_wait (argv[1], &activity, NULL);
+
+ if (addr)
+ g_print ("address: %s\n", lac_address_to_string (addr));
+ else
+ g_print ("canceled\n");
+
+ return 0;
+}
diff --git a/tests/dns2-test.c b/tests/dns2-test.c
new file mode 100644
index 0000000..03ffa20
--- /dev/null
+++ b/tests/dns2-test.c
@@ -0,0 +1,56 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- */
+
+/* Lac - Library for asynchronous communication
+ * Copyright (C) 2002 Søren Sandmann (sandmann@daimi.au.dk)
+ *
+ * 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.
+ */
+#include <lac.h>
+
+int
+main (int argc, char *argv[])
+{
+ int i;
+ LacAddress *addr;
+
+ lac_set_verbose (TRUE);
+
+ if (argc > 1)
+ {
+ for (i = 1; i < argc; ++i)
+ {
+ GError *err = NULL;
+ addr = lac_gethostbyname (argv[i], &err);
+
+ if (!addr)
+ {
+ g_print ("%s: error (%s)\n", argv[i], err->message);
+ g_error_free (err);
+ }
+ else
+ {
+ g_print ("%s: %s\n", argv[i], lac_address_to_str (addr));
+ lac_address_unref (addr);
+ }
+ }
+ return 0;
+ }
+ else
+ {
+ g_print ("usage %s <names>\n", argv[0]);
+ return 1;
+ }
+}
diff --git a/tests/htmlparser.c b/tests/htmlparser.c
new file mode 100644
index 0000000..b36a862
--- /dev/null
+++ b/tests/htmlparser.c
@@ -0,0 +1,171 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- */
+
+/* Lac - Library for asynchronous communication
+ * Copyright (C) 2002 Søren Sandmann (sandmann@daimi.au.dk)
+ *
+ * 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.
+ */
+
+#include "htmlparser.h"
+#include <libxml/HTMLparser.h>
+
+struct HtmlParser {
+ HtmlParserFunc callback;
+ gpointer data;
+ GString * unparsed;
+ xmlParserCtxt * xml_context;
+};
+
+static void
+start_element_cb (void *ctx,
+ const xmlChar *name,
+ const xmlChar **atts)
+{
+ HtmlParser *parser = ctx;
+ HtmlParserEvent event;
+ GPtrArray *attributes = g_ptr_array_new ();
+ int i;
+
+ event.type = HTML_PARSER_BEGIN_ELEMENT;
+ event.begin_element.name = name;
+
+ if (atts)
+ {
+ const xmlChar **s;
+ for (s = atts; *s != NULL; )
+ {
+ HtmlAttribute *attr = g_new (HtmlAttribute, 1);
+
+ attr->name = *s++;
+ attr->content = *s++;
+
+ g_ptr_array_add (attributes, attr);
+ }
+ }
+
+ event.begin_element.attributes = attributes;
+
+ parser->callback (parser, &event);
+
+ for (i = 0; i < attributes->len; ++i)
+ g_free (attributes->pdata[i]);
+ g_ptr_array_free (attributes, TRUE);
+}
+
+static void
+end_element_cb (void *ctx,
+ const xmlChar *name)
+{
+ HtmlParser *parser = ctx;
+ HtmlParserEvent event;
+
+ event.type = HTML_PARSER_END_ELEMENT;
+ event.end_element.name = name;
+
+ parser->callback (parser, &event);
+}
+
+static void
+comment_cb (void *ctx,
+ const xmlChar *comment)
+{
+ HtmlParser *parser = ctx;
+ HtmlParserEvent event;
+
+ event.type = HTML_PARSER_COMMENT;
+ event.comment.text = comment;
+
+ parser->callback (parser, &event);
+}
+
+static void
+end_document_cb (void *ctx)
+{
+ HtmlParser *parser = ctx;
+ HtmlParserEvent event;
+
+ event.type = HTML_PARSER_END_DOCUMENT;
+
+ parser->callback (parser, &event);
+}
+
+static htmlSAXHandler html_sax_handler = {
+ NULL, /* internalSubset */
+ NULL, /* isStandalone */
+ NULL, /* hasInternalSubset */
+ NULL, /* hasExternalSubset */
+ NULL, /* resolveEntity */
+ NULL, /* getEntity */
+ NULL, /* entityDecl */
+ NULL, /* notationDecl */
+ NULL, /* attributeDecl */
+ NULL, /* elementDecl */
+ NULL, /* unparsedEntityDecl */
+ NULL, /* setDocumentLocator */
+
+ NULL, /* startDocument */
+ end_document_cb, /* endDocument */
+ start_element_cb, /* startElement */
+ end_element_cb, /* endElement */
+ NULL, /* reference */
+ NULL, /* characters */
+ NULL, /* ignorableWhitespace */
+ NULL, /* processingInstruction */
+ comment_cb, /* comment */
+ NULL, /* warning */
+ NULL, /* error */
+ NULL, /* fatalError */
+ NULL, /* getParameterEntity */
+ NULL, /* cdataBlock */
+ NULL /* externalSubset */
+};
+
+HtmlParser *
+html_parser_new (HtmlParserFunc f,
+ gpointer data)
+{
+ HtmlParser *html_parser = g_new (HtmlParser, 1);
+
+ g_return_val_if_fail (f != NULL, NULL);
+
+ html_parser->callback = f;
+ html_parser->data = data;
+ html_parser->unparsed = g_string_new ("");
+ html_parser->xml_context = htmlCreatePushParserCtxt (
+ &html_sax_handler, html_parser, NULL, 0, NULL, -1);
+
+ return html_parser;
+}
+
+void
+html_parser_push_data (HtmlParser *parser,
+ const gchar *data,
+ guint len)
+{
+ htmlParseChunk (parser->xml_context, data, len, 0);
+}
+
+void
+html_parser_push_eof (HtmlParser *parser)
+{
+ htmlParseChunk (parser->xml_context, NULL, 0, 1);
+}
+
+gpointer
+html_parser_get_data (HtmlParser *parser)
+{
+ return parser->data;
+}
diff --git a/tests/htmlparser.h b/tests/htmlparser.h
new file mode 100644
index 0000000..28758ea
--- /dev/null
+++ b/tests/htmlparser.h
@@ -0,0 +1,74 @@
+/* Lac - Library for asynchronous communication
+ * Copyright (C) 2002 Søren Sandmann (sandmann@daimi.au.dk)
+ *
+ * This library 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 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU 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
+ */
+
+#include <glib.h>
+
+typedef struct HtmlParser HtmlParser;
+
+typedef enum {
+ HTML_PARSER_BEGIN_ELEMENT,
+ HTML_PARSER_END_ELEMENT,
+ HTML_PARSER_COMMENT,
+ HTML_PARSER_END_DOCUMENT
+} HtmlParserEventType;
+
+typedef struct {
+ const char *name;
+ const char *content;
+} HtmlAttribute;
+
+typedef struct {
+ HtmlParserEventType type;
+ const gchar *name;
+ const GPtrArray *attributes; /* OF const HtmlAttribute * */
+} HtmlParserBeginElementEvent;
+
+typedef struct {
+ HtmlParserEventType type;
+ const gchar *name;
+} HtmlParserEndElementEvent;
+
+typedef struct {
+ HtmlParserEventType type;
+ const gchar *text;
+} HtmlParserComment;
+
+typedef struct {
+ HtmlParserEventType type;
+} HtmlParserEndDocument;
+
+typedef union HtmlParserEvent HtmlParserEvent;
+union HtmlParserEvent {
+ HtmlParserEventType type;
+ HtmlParserBeginElementEvent begin_element;
+ HtmlParserEndElementEvent end_element;
+ HtmlParserComment comment;
+ HtmlParserEndDocument end_document;
+};
+
+typedef void (* HtmlParserFunc) (HtmlParser *parser,
+ const HtmlParserEvent *event);
+
+HtmlParser *html_parser_new (HtmlParserFunc f,
+ gpointer data);
+void html_parser_push_data (HtmlParser *parser,
+ const gchar *data,
+ gsize len);
+void html_parser_push_eof (HtmlParser *parser);
+gpointer html_parser_get_data (HtmlParser *parser);
+
diff --git a/tests/http-test.c b/tests/http-test.c
new file mode 100644
index 0000000..240d563
--- /dev/null
+++ b/tests/http-test.c
@@ -0,0 +1,190 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- */
+
+/* Lac - Library for asynchronous communication
+ * Copyright (C) 2002 Søren Sandmann (sandmann@daimi.au.dk)
+ *
+ * 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.
+ */
+#include <lac.h>
+#include <string.h>
+#include <stdio.h>
+
+static int n_requests;
+GMainLoop *main_loop;
+
+#if 0
+static GPtrArray *outstanding_requests;
+static GPtrArray *error_requests;
+#endif
+
+static gboolean
+show_outstanding (gpointer data)
+{
+#if 0
+ int i;
+
+ g_print (">>>>> outstanding: %d\n", n_requests);
+ for (i = 0; i < outstanding_requests->len; ++i)
+ {
+ gchar *uri_str = lac_http_request_get_data (outstanding_requests->pdata[i]);
+ g_print (" %d: %s\n", i+1, uri_str);
+ }
+
+ g_print (">>>>> error requests: %d\n", error_requests->len);
+ for (i = 0; i < error_requests->len; ++i)
+ {
+ gchar *uri_str = lac_http_request_get_data (error_requests->pdata[i]);
+ g_print (" %d: %s\n", i+1, uri_str);
+ }
+
+#endif
+ return TRUE;
+}
+
+static void
+lac_http_callback (LacHttpRequest *request, const LacHttpEvent *event)
+{
+ gchar *uri_str = lac_http_request_get_data (request);
+ gchar *s;
+
+ switch (event->type)
+ {
+ case LAC_HTTP_EVENT_HOST_FOUND:
+ s = lac_address_to_string (event->host_found.address);
+ g_print ("%s: host found (%s)\n", uri_str, s);
+ g_free (s);
+ break;
+
+ case LAC_HTTP_EVENT_CONNECTING:
+ g_print ("%s: connecting\n", uri_str);
+ break;
+
+ case LAC_HTTP_EVENT_SENT:
+ g_print ("%s: sent (non-pipelined)\n", uri_str);
+ break;
+
+ case LAC_HTTP_EVENT_STATUS_LINE:
+ g_print ("%s: status line: %d %s\n", uri_str,
+ event->status_line.status_code, event->status_line.reason_phrase);
+ break;
+
+ case LAC_HTTP_EVENT_HEADER:
+ g_print ("%s: %s: %s\n", uri_str, event->header.header, event->header.value);
+ break;
+
+ case LAC_HTTP_EVENT_BEGIN_CONTENT:
+ g_print ("%s: begin content (length %d)\n", uri_str, event->begin_content.length);
+ break;
+
+ case LAC_HTTP_EVENT_CONTENT:
+ g_print ("%s: content chunk (size: %d)\n", uri_str, event->content.length);
+ break;
+
+ case LAC_HTTP_EVENT_END_CONTENT:
+ printf ("%s: end_content (total: %d)\n", uri_str, event->end_content.total);
+#if 0
+ g_ptr_array_remove (outstanding_requests, request);
+#endif
+ lac_http_request_unref (request);
+ if (--n_requests == 0)
+ g_main_loop_quit (main_loop);
+ break;
+
+ case LAC_HTTP_EVENT_ERROR:
+#if 0
+ g_ptr_array_remove (outstanding_requests, request);
+ g_ptr_array_add (error_requests, request);
+#endif
+ lac_http_request_unref (request);
+ g_print ("%s: error (%s)\n", uri_str, event->error.err->message);
+ if (--n_requests == 0)
+ g_main_loop_quit (main_loop);
+ break;
+
+ default:
+ g_print ("%s: UNKNOWN (%d)\n", uri_str, event->type);
+ break;
+ }
+}
+
+int
+main (int argc, char *argv[])
+{
+ int i;
+
+ g_log_set_always_fatal (G_LOG_LEVEL_WARNING | G_LOG_LEVEL_ERROR |
+ G_LOG_LEVEL_CRITICAL);
+
+
+ lac_set_verbose (TRUE);
+#if 0
+ outstanding_requests = g_ptr_array_new ();
+ error_requests = g_ptr_array_new ();
+#endif
+
+ if (argc > 1)
+ {
+ for (i = 1; i < argc; ++i)
+ {
+ LacHttpRequest *request;
+
+ LacUri *uri = lac_uri_new_from_str (NULL, argv[i]);
+ if (!uri)
+ {
+ g_print ("bad uri: %s\n", argv[i]);
+ continue;
+ }
+
+ request = lac_http_request_new_get (
+ uri, lac_http_callback, argv[i]);
+
+ lac_uri_free (uri);
+ lac_http_request_dispatch (request);
+
+#if 0
+ g_ptr_array_add (outstanding_requests, request);
+#endif
+
+ ++n_requests;
+ }
+ }
+ else
+ {
+ g_print ("usage %s <names>\n", argv[0]);
+ return 1;
+ }
+
+ main_loop = g_main_loop_new (NULL, TRUE);
+#if 0
+ g_timeout_add (38000, g_main_loop_quit, main_loop);
+#endif
+#if 0
+ g_timeout_add (5000, show_outstanding, NULL);
+#endif
+ g_print ("dispatched requests\n");
+ g_assert (main_loop);
+ g_main_loop_run (main_loop);
+ g_print ("after main loop\n");
+#if 0
+ g_print ("outstanding_requests->len = %d\n", outstanding_requests->len);
+ g_print ("error_requests->len = %d\n", error_requests->len);
+#endif
+#if 0
+ g_ptr_array_free (outstanding_requests, TRUE);
+ g_ptr_array_free (error_requests, TRUE);
+#endif
+ return 0;
+}
diff --git a/tests/lacwget.c b/tests/lacwget.c
new file mode 100644
index 0000000..f1eabbf
--- /dev/null
+++ b/tests/lacwget.c
@@ -0,0 +1,672 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- */
+
+/* Lac - Library for asynchronous communication
+ * Copyright (C) 2002, 2003 Søren Sandmann (sandmann@daimi.au.dk)
+ *
+ * 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.
+ */
+#include <lac.h>
+#include "htmlparser.h"
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+
+// #define g_print(...)
+
+static GMainLoop *main_loop;
+static GTimer *total = NULL;
+
+static gboolean do_download (LacUri *base_uri, const gchar *uri_str, gboolean try_parse);
+
+typedef struct Data {
+ gchar *uri_str;
+ gdouble time;
+ gdouble first_connect;
+ gdouble first_content;
+ gdouble first_image_tag;
+ gdouble first_idle;
+ gdouble first_parser_callback;
+ guint n_content_chunks;
+ guint n_parser_calls;
+ gdouble parser_eof;
+ gint n_images;
+ gint n_links;
+ gboolean try_parse;
+} Data;
+
+static GQueue *statistics = NULL;
+
+typedef struct PageInfo {
+ GTimer *timer;
+ gchar *main_uri;
+ LacUri *base_uri;
+ gdouble first_connect;
+ gdouble first_image_tag;
+ gdouble first_content;
+ gdouble first_idle;
+ gdouble first_parser_callback;
+ GString *unparsed;
+ gboolean eof;
+ HtmlParser *parser;
+ gint idle_id;
+ gint n_remaining_images;
+ gint n_images;
+ gint n_links;
+ gboolean done;
+ guint n_content_chunks;
+ guint n_parser_calls;
+ gdouble parser_eof;
+ gboolean is_redirect;
+ gboolean try_parse;
+} PageInfo;
+
+static GPtrArray *outstanding_page_infos;
+
+typedef struct ImageInfo {
+ FILE *f;
+ PageInfo *page_info;
+ gchar *uri_str;
+} ImageInfo;
+
+static gboolean
+print_outstanding (gpointer data)
+{
+ int i;
+
+ g_print ("outstanding:\n");
+ for (i = 0; i < outstanding_page_infos->len; ++i)
+ {
+ PageInfo *info = outstanding_page_infos->pdata[i];
+ if (info->try_parse)
+ g_print ("%s: %d images %s\n", info->main_uri, info->n_remaining_images, info->done? "- main page done" : "+ rest of main page");
+ }
+ return TRUE;
+}
+
+static gchar *
+truncat (gchar *str)
+{
+ enum {STRLEN = 200};
+ if (strlen (str) > STRLEN)
+ {
+ gchar *new_str = g_malloc (STRLEN + 1);
+ strncpy (new_str, str, STRLEN - 3);
+ strncpy (new_str + STRLEN - 3, "...", 3);
+ new_str[STRLEN] = '\0';
+ return new_str;
+ }
+ else
+ return str;
+}
+
+static void
+maybe_quit (PageInfo *info)
+{
+ if (info->n_remaining_images == 0 &&
+ ((info->try_parse && info->done && info->eof) ||
+ (!info->try_parse && info->eof)))
+ {
+ Data *data = g_new (Data, 1);
+
+ data->uri_str = info->main_uri;
+ data->time = g_timer_elapsed (info->timer, NULL);
+ data->first_connect = info->first_connect;
+ data->first_image_tag = info->first_image_tag;
+ data->first_content = info->first_content;
+ data->first_idle = info->first_idle;
+ data->first_parser_callback = info->first_parser_callback;
+ data->n_content_chunks = info->n_content_chunks;
+ data->n_parser_calls = info->n_parser_calls;
+ data->parser_eof = info->parser_eof;
+ data->n_images = info->n_images;
+ data->n_links = info->n_links;
+ data->try_parse = info->try_parse;
+
+ if (!statistics)
+ statistics = g_queue_new ();
+ g_queue_push_tail (statistics, data);
+
+ g_ptr_array_remove (outstanding_page_infos, info);
+ if (outstanding_page_infos->len == 0)
+ {
+ int total_requests = 0;
+ GList *list;
+
+#if 0
+ printf ("\n\n\n\n");
+#endif
+ for (list = statistics->head; list != NULL; list = list->next)
+ {
+ Data *data = list->data;
+ if (data->try_parse)
+ {
+ g_print ("%s:\n", data->uri_str);
+ g_print (" total: %f \n", data->time);
+ g_print (" connected: %f \n", data->first_connect);
+ g_print (" first content: %f \n", data->first_content);
+ g_print (" first idle: %f \n", data->first_idle);
+ g_print (" first parser callback: %f\n", data->first_parser_callback);
+ g_print (" first imgtag seen: %f\n", data->first_image_tag);
+ g_print (" push eof to parser: %f\n", data->parser_eof);
+ g_print (" content chunks: %d\n", data->n_content_chunks);
+ g_print (" parser calls: %d\n", data->n_parser_calls);
+ g_print (" number of images: %d\n", data->n_images);
+ g_print (" number of LINK/href elmts %d\n", data->n_links);
+ g_print ("\n");
+ total_requests += data->n_images + data->n_links + 1;
+ }
+ }
+ printf ("%d requests in ", total_requests);
+ printf ("total time: %f\n", g_timer_elapsed (total, NULL));
+ g_main_loop_quit (main_loop);
+#if 0
+ printf ("(press Ctrl-C to quit, or wait 5 seconds\n");
+ g_timeout_add (5000, g_main_loop_quit, main_loop);
+#endif
+ }
+ }
+}
+
+static gboolean
+parse_idle (gpointer data)
+{
+ PageInfo *page_info = data;
+
+ if (page_info->first_idle == -1.0)
+ page_info->first_idle = g_timer_elapsed (page_info->timer, NULL);
+
+ if (page_info->unparsed->len > 0)
+ {
+ page_info->n_parser_calls++;
+#if 0
+ g_print ("%f: push data (%d bytes)\n",
+ g_timer_elapsed (total, NULL), page_info->unparsed->len);
+#endif
+ if (page_info->try_parse)
+ html_parser_push_data (
+ page_info->parser, page_info->unparsed->str, page_info->unparsed->len);
+ g_string_truncate (page_info->unparsed, 0);
+ }
+
+ if (page_info->eof)
+ {
+ if (page_info->parser)
+ {
+ g_print ("%f: push eof\n",
+ g_timer_elapsed (total, NULL));
+ html_parser_push_eof (page_info->parser);
+ }
+ page_info->parser_eof = g_timer_elapsed (page_info->timer, NULL);
+ page_info->done = TRUE;
+ }
+
+ page_info->idle_id = 0;
+ maybe_quit (page_info);
+
+ return FALSE;
+}
+
+static void
+get_image (PageInfo *page_info, gchar *image_uri_str)
+{
+ if (do_download (page_info->base_uri, image_uri_str, FALSE))
+ ++page_info->n_images;
+}
+
+static void
+handle_img_element (PageInfo *page_info, const HtmlParserBeginElementEvent *event)
+{
+ int i;
+
+ if (page_info->first_image_tag == -1.0)
+ {
+ page_info->first_image_tag =
+ g_timer_elapsed (page_info->timer, NULL);
+ }
+
+ for (i = 0; i < event->attributes->len; ++i)
+ {
+ HtmlAttribute *attr =
+ event->attributes->pdata[i];
+
+ if (g_ascii_strcasecmp (attr->name, "src") == 0)
+ {
+ if (attr->content)
+ get_image (page_info, g_strdup (attr->content));
+ }
+ }
+}
+
+static void
+handle_link_element (PageInfo *page_info, const HtmlParserBeginElementEvent *event)
+{
+ int i;
+ gchar *uri = NULL;
+
+ for (i = 0; i < event->attributes->len; ++i)
+ {
+ HtmlAttribute *attr = event->attributes->pdata[i];
+
+ if (g_ascii_strcasecmp (attr->name, "href") == 0)
+ {
+ uri = g_strdup (attr->content);
+ break;
+ }
+ }
+
+ if (uri)
+ {
+ if (do_download (page_info->base_uri, uri, FALSE))
+ page_info->n_links++;
+ }
+}
+
+static void
+handle_base_element (PageInfo *page_info, const HtmlParserBeginElementEvent *event)
+{
+ int i;
+
+ gchar *uri = NULL;
+
+ for (i = 0; i < event->attributes->len; ++i)
+ {
+ HtmlAttribute *attr = event->attributes->pdata[i];
+
+ if (g_ascii_strcasecmp (attr->name, "href") == 0)
+ {
+ uri = g_strdup (attr->content);
+ break;
+ }
+ }
+
+ if (uri)
+ {
+ LacUri *new_base = lac_uri_new_from_str (page_info->base_uri, uri);
+ if (new_base)
+ {
+ printf ("new base: %s\n", lac_uri_string (new_base));
+ page_info->base_uri = new_base;
+ }
+ }
+}
+
+static void
+handle_frame_element (PageInfo *page_info, const HtmlParserBeginElementEvent *event)
+{
+ int i;
+
+ gchar *uri = NULL;
+
+ for (i = 0; i < event->attributes->len; ++i)
+ {
+ HtmlAttribute *attr = event->attributes->pdata[i];
+
+ if (g_ascii_strcasecmp (attr->name, "src") == 0)
+ {
+ uri = g_strdup (attr->content);
+ break;
+ }
+ }
+
+ if (uri)
+ {
+ do_download (page_info->base_uri, uri, TRUE);
+ }
+}
+
+
+static void
+handle_meta_element (PageInfo *page_info, const HtmlParserBeginElementEvent *event)
+{
+ int i;
+
+ gchar *uri = NULL;
+ gboolean is_refresh = FALSE;
+
+ for (i = 0; i < event->attributes->len; ++i)
+ {
+ HtmlAttribute *attr = event->attributes->pdata[i];
+
+ if (g_ascii_strcasecmp (attr->name, "http-equiv") == 0 &&
+ g_ascii_strcasecmp (attr->content, "refresh") == 0)
+ {
+ is_refresh = TRUE;
+ break;
+ }
+ }
+
+ if (!is_refresh)
+ return;
+
+ for (i = 0; i < event->attributes->len; ++i)
+ {
+ HtmlAttribute *attr = event->attributes->pdata[i];
+
+ if (g_ascii_strcasecmp (attr->name, "content") == 0)
+ {
+ char *lower_case = g_ascii_strdown (attr->content, -1);
+ const char *pos = strstr (lower_case, ";url=");
+
+ if (pos)
+ {
+ uri = g_strdup (attr->content + (pos - lower_case) + 5);
+ }
+
+ g_free (lower_case);
+ break;
+ }
+ }
+
+ if (uri)
+ {
+ do_download (page_info->base_uri, uri, TRUE);
+ }
+}
+
+static void
+parser_callback (HtmlParser *parser, const HtmlParserEvent *event)
+{
+ PageInfo *page_info = html_parser_get_data (parser);
+ if (page_info->first_parser_callback == -1.0)
+ page_info->first_parser_callback = g_timer_elapsed (page_info->timer, NULL);
+
+ switch (event->type)
+ {
+ case HTML_PARSER_BEGIN_ELEMENT:
+#if 0
+ g_print ("%f: begin %s\n",
+ g_timer_elapsed (total, NULL), event->begin_element.name);
+#endif
+ if (g_ascii_strcasecmp (event->begin_element.name, "img") == 0)
+ {
+ handle_img_element (page_info, &(event->begin_element));
+ }
+ else if (g_ascii_strcasecmp (event->begin_element.name, "link") == 0)
+ {
+ handle_link_element (page_info, &(event->begin_element));
+ }
+ else if (g_ascii_strcasecmp (event->begin_element.name, "frame") == 0)
+ {
+ handle_frame_element (page_info, &(event->begin_element));
+ }
+ else if (g_ascii_strcasecmp (event->begin_element.name, "base") == 0)
+ {
+ handle_base_element (page_info, &(event->begin_element));
+ }
+ else if (g_ascii_strcasecmp (event->begin_element.name, "meta") == 0)
+ {
+ handle_meta_element (page_info, &(event->begin_element));
+ }
+ break;
+
+ case HTML_PARSER_END_ELEMENT:
+#if 0
+ g_print ("%f: end %s\n",
+ g_timer_elapsed (total, NULL), event->end_element.name);
+#endif
+ break;
+
+ case HTML_PARSER_COMMENT:
+#if 0
+ g_print ("%f: comment\n", g_timer_elapsed (total, NULL));
+#endif
+#if 0
+ g_print ("%f: comment: %s\n",
+ g_timer_elapsed (total, NULL), event->comment.text);
+#endif
+ break;
+ case HTML_PARSER_END_DOCUMENT:
+ g_print ("END DOCUMENT REPORTED BY PARSER\n");
+ break;
+ }
+}
+
+static void
+queue_parse (PageInfo *info)
+{
+ if (!info->idle_id)
+ info->idle_id = g_idle_add (parse_idle, info);
+}
+
+static void
+http_callback (LacHttpRequest *request, const LacHttpEvent *event)
+{
+ PageInfo *page_info = lac_http_request_get_data (request);
+
+ switch (event->type)
+ {
+ case LAC_HTTP_EVENT_HOST_FOUND:
+ g_print ("%s: dns lookup complete at %f\n", page_info->main_uri,
+ g_timer_elapsed (total, NULL));
+ break;
+
+ case LAC_HTTP_EVENT_SENT:
+ page_info->first_connect = g_timer_elapsed (page_info->timer, NULL);
+ g_print ("%s: sent \n", page_info->main_uri);
+ break;
+
+ case LAC_HTTP_EVENT_CONNECTING:
+ break;
+
+ case LAC_HTTP_EVENT_STATUS_LINE:
+ g_print ("%s: status line: HTTP/%d.%d %d %s\n",
+ truncat (page_info->main_uri),
+ event->status_line.major,
+ event->status_line.minor,
+ event->status_line.status_code,
+ event->status_line.reason_phrase);
+ if (event->status_line.status_code == 302 ||
+ event->status_line.status_code == 301)
+ {
+ g_print ("IS REDIRECT!\n");
+ page_info->is_redirect = TRUE;
+ }
+ break;
+
+ case LAC_HTTP_EVENT_NO_STATUS_LINE:
+ g_print ("%s: no status line (HTTP/0.9 response)\n",
+ truncat (page_info->main_uri));
+ break;
+
+ case LAC_HTTP_EVENT_HEADER:
+ g_print ("%s: header (%s: %s)\n", page_info->main_uri, event->header.header, event->header.value);
+ if (page_info->is_redirect && g_ascii_strcasecmp (event->header.header, "Location") == 0)
+ {
+ do_download (page_info->base_uri, event->header.value, page_info->try_parse);
+ }
+ break;
+
+ case LAC_HTTP_EVENT_BEGIN_CONTENT:
+#if 0
+ g_print ("%s: begin content (length %d)\n", page_info->main_uri, event->begin_content.length);
+#endif
+ break;
+
+ case LAC_HTTP_EVENT_CONTENT:
+ g_print ("%s: content chunk (size: %d)\n", page_info->main_uri, event->content.length);
+#if 0
+ {
+ int i;
+ for (i = 0; i < event->content.length; ++i)
+ printf ("%c", event->content.data[i]);
+ }
+#endif
+ g_string_append_len (page_info->unparsed, event->content.data, event->content.length);
+ if (page_info->first_content == -1.0)
+ page_info->first_content = g_timer_elapsed (page_info->timer, NULL);
+
+ queue_parse (page_info);
+#if 0
+ parse_idle (page_info);
+#endif
+ page_info->n_content_chunks++;
+ break;
+
+ case LAC_HTTP_EVENT_END_CONTENT:
+#if 0
+ printf ("%s: end_content (total: %d)\n", page_info->main_uri, event->end_content.total);
+#endif
+ g_print ("%s: DONE (at total: %f, this page: %f\n",
+ truncat (page_info->main_uri),
+ g_timer_elapsed (total, NULL),
+ g_timer_elapsed (page_info->timer, NULL));
+ page_info->eof = TRUE;
+ queue_parse (page_info);
+
+#if 0
+ parse_idle (page_info);
+#endif
+ break;
+
+ case LAC_HTTP_EVENT_ERROR:
+ printf ("%s: error (%s)\n", truncat (page_info->main_uri), event->error.err->message);
+ page_info->eof = TRUE;
+ queue_parse (page_info);
+ break;
+
+ default:
+ g_warning ("%s: UNKNOWN (%d)\n", truncat (page_info->main_uri), event->type);
+ break;
+ }
+}
+
+static gboolean
+already_downloaded (const LacUri *uri)
+{
+ static GPtrArray *already;
+ int i;
+
+ if (!already)
+ already = g_ptr_array_new ();
+
+ for (i = 0; i < already->len; ++i)
+ {
+ if (lac_uri_equal (already->pdata[i], uri))
+ return TRUE;
+ }
+
+ g_ptr_array_add (already, lac_uri_copy (uri));
+ return FALSE;
+}
+
+static gboolean
+do_download (LacUri *base_uri, const gchar *uri_str, gboolean try_parse)
+{
+ LacHttpRequest *request;
+ PageInfo *page_info = g_new (PageInfo, 1);
+ LacUri *uri;
+ page_info->timer = g_timer_new ();
+ page_info->try_parse = try_parse;
+
+#if 0
+ printf ("uri: %s\n", uri_str);
+#endif
+
+ if (page_info->try_parse)
+ g_print ("DOWNLAOD TRUE %s ********\n", uri_str);
+
+ uri = lac_uri_new_from_str (base_uri, uri_str);
+ if (!uri)
+ {
+ printf ("bad uri: %s\n", uri_str);
+ return FALSE ;
+ }
+ else if (uri->scheme != LAC_SCHEME_HTTP)
+ {
+ printf ("not an http uri: %s\n", uri_str);
+ return FALSE;
+ }
+ if (already_downloaded (uri))
+ return FALSE;
+
+#if 0
+ printf ("download: %s\n", uri_str);
+#endif
+
+ request = lac_http_request_new_get (
+ uri, http_callback, page_info);
+
+ page_info->main_uri = g_strdup (uri_str);
+ page_info->base_uri = uri;
+ if (page_info->try_parse)
+ page_info->parser = html_parser_new (parser_callback, page_info);
+ else
+ page_info->parser = NULL;
+ page_info->n_remaining_images = 0;
+ page_info->n_images = 0;
+ page_info->n_links = 0;
+ page_info->done = FALSE;
+ page_info->idle_id = 0;
+ page_info->eof = FALSE;
+ page_info->unparsed = g_string_new ("");
+ page_info->first_connect = -1.0;
+ page_info->first_content = -1.0;
+ page_info->first_image_tag = -1.0;
+ page_info->first_idle = -1.0;
+ page_info->first_parser_callback = -1.0;
+ page_info->parser_eof = -1.0;
+ page_info->n_content_chunks = 0;
+ page_info->n_parser_calls = 0;
+ page_info->is_redirect = FALSE;
+
+ g_ptr_array_add (outstanding_page_infos, page_info);
+
+#if 0
+ printf ("length: %d\n", outstanding_page_infos->len);
+#endif
+
+ g_print ("dispatched %s at %f\n", page_info->main_uri, g_timer_elapsed (total, NULL));
+ lac_http_request_dispatch (request);
+ return TRUE;
+}
+
+int
+main (int argc, char *argv[])
+{
+ int i;
+
+ g_log_set_always_fatal (G_LOG_LEVEL_WARNING | G_LOG_LEVEL_ERROR |
+ G_LOG_LEVEL_CRITICAL);
+
+
+ lac_set_verbose (TRUE);
+
+ outstanding_page_infos = g_ptr_array_new ();
+ total = g_timer_new ();
+ if (argc > 1)
+ {
+ for (i = 1; i < argc; ++i)
+ do_download (NULL, argv[i], TRUE);
+ }
+ else
+ {
+ printf ("usage %s <uris>\n", argv[0]);
+ return 1;
+ }
+
+ main_loop = g_main_loop_new (NULL, TRUE);
+#if 0
+ g_timeout_add (5000, (GSourceFunc) print_outstanding, NULL);
+ g_timeout_add (38000, (GSourceFunc) g_main_loop_quit, main_loop);
+#endif
+ g_print ("dispatched requests\n");
+ g_assert (main_loop);
+ if (outstanding_page_infos->len > 0)
+ g_main_loop_run (main_loop);
+
+ return g_timer_elapsed (total, NULL) * 10000;
+}
diff --git a/tests/newdns-test.c b/tests/newdns-test.c
new file mode 100644
index 0000000..db02abe
--- /dev/null
+++ b/tests/newdns-test.c
@@ -0,0 +1,117 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- */
+
+/* Lac - Library for asynchronous communication
+ * Copyright (C) 2002 Søren Sandmann (sandmann@daimi.au.dk)
+ *
+ * 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.
+ */
+#include <lac.h>
+#include <string.h>
+
+static int n_outstanding;
+static GMainLoop *main_loop;
+
+static void
+name_callback (const gchar *name, gpointer data, const GError *err)
+{
+ gchar *addr_str = data;
+
+ if (err)
+ {
+ g_print ("reverse %s: %s\n", addr_str, err->message);
+ }
+ else
+ {
+ g_print ("reverse %s: %s\n", addr_str, name);
+ }
+ g_free (addr_str);
+}
+
+static void
+callback (const GPtrArray *addresses, gpointer data, const GError *err)
+{
+ gchar *name = data;
+
+ if (err)
+ {
+ g_print ("%s: %s\n", name, err->message);
+ }
+ else
+ {
+ int i;
+
+ if (addresses->len == 0)
+ g_print ("this should not happen\n");
+ for (i = 0; i < addresses->len; ++i)
+ {
+ gchar *addr = lac_address_to_str (addresses->pdata[i]);
+ g_print ("%s: %s\n", name, addr);
+
+ lac_dns_get_name (addresses->pdata[i], name_callback, addr);
+ }
+
+ }
+
+ if (!strstr (name, "again"))
+ {
+ gchar *tmp;
+
+ n_outstanding += 2;
+ lac_dns_get_addresses (name, callback, g_strdup_printf ("%s again", name));
+
+ tmp = g_ascii_strup (name, -1);
+ lac_dns_get_addresses (tmp, callback, g_strdup_printf ("%s (UPPER) again", name));
+ g_free (tmp);
+ }
+
+ if (--n_outstanding == 0)
+ g_main_quit (main_loop);
+ g_free (name);
+}
+
+int
+main (int argc, char *argv[])
+{
+ int i;
+
+ main_loop = g_main_loop_new (NULL, TRUE);
+ g_log_set_always_fatal (G_LOG_LEVEL_WARNING | G_LOG_LEVEL_ERROR |
+ G_LOG_LEVEL_CRITICAL);
+
+ lac_set_verbose (TRUE);
+
+ if (argc > 1)
+ {
+ for (i = 1; i < argc; ++i)
+ {
+ ++n_outstanding;
+ lac_dns_get_addresses (argv[i], callback, g_strdup (argv[i]));
+ }
+ }
+ else
+ {
+ g_print ("usage %s <names>\n", argv[0]);
+ return 1;
+ }
+
+ g_assert (main_loop);
+#if 0
+ g_timeout_add (200, g_main_loop_quit, main_loop);
+#endif
+ g_main_loop_run (main_loop);
+
+ return 0;
+}
diff --git a/tests/rdns-test-async.c b/tests/rdns-test-async.c
new file mode 100644
index 0000000..a16151b
--- /dev/null
+++ b/tests/rdns-test-async.c
@@ -0,0 +1,78 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- */
+
+/* Lac - Library for asynchronous communication
+ * Copyright (C) 2002 Søren Sandmann (sandmann@daimi.au.dk)
+ *
+ * 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.
+ */
+#include <lac.h>
+
+static void
+rcallback (const gchar *name, gpointer data, const GError *err)
+{
+ gchar *lookup_name = data;
+
+ if (err)
+ {
+ g_print ("%s: (error %s)\n", lookup_name, err->message);
+ }
+ else
+ {
+ g_print ("%s: %s\n", lookup_name, name);
+ }
+}
+
+static void
+callback (const LacAddress *addr, gpointer data, const GError *err)
+{
+ gchar *name = data;
+
+ if (err)
+ {
+ g_print ("%s: error: %s\n", name, err->message);
+ }
+ else
+ {
+ g_print ("%s: %s\n", name, lac_address_to_string (addr));
+ lac_address_lookup_name (addr, rcallback, name);
+ }
+}
+
+int
+main (int argc, char *argv[])
+{
+ int i;
+ GMainLoop *main_loop;
+
+ lac_set_verbose (TRUE);
+
+ if (argc > 1)
+ {
+ for (i = 1; i < argc; ++i)
+ lac_address_new_lookup_from_name (argv[i], callback, argv[i]);
+ }
+ else
+ {
+ g_print ("usage %s <names>\n", argv[0]);
+ return 1;
+ }
+
+ main_loop = g_main_loop_new (NULL, TRUE);
+ g_assert (main_loop);
+ g_main_loop_run (main_loop);
+
+ return 0;
+}
diff --git a/tests/rdns-test.c b/tests/rdns-test.c
new file mode 100644
index 0000000..b5fa0be
--- /dev/null
+++ b/tests/rdns-test.c
@@ -0,0 +1,56 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- */
+
+/* Lac - Library for asynchronous communication
+ * Copyright (C) 2002 Søren Sandmann (sandmann@daimi.au.dk)
+ *
+ * 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.
+ */
+#include <lac.h>
+
+int
+main (int argc, char *argv[])
+{
+ int i;
+ LacAddress *addr;
+
+ lac_verbose = TRUE;
+
+ if (argc > 1)
+ {
+ for (i = 1; i < argc; ++i)
+ {
+ GError *err = NULL;
+ addr = lac_gethostbyname (argv[i], &err);
+
+ if (!addr)
+ {
+ g_print ("%s: error (%s)\n", argv[i], err->message);
+ g_error_free (err);
+ }
+ else
+ {
+ g_print ("%s: %s\n", argv[i], lac_address_to_str (addr));
+ lac_address_unref (addr);
+ }
+ }
+ return 0;
+ }
+ else
+ {
+ g_print ("usage %s <names>\n", argv[0]);
+ return 1;
+ }
+}
diff --git a/tests/simple-test.c b/tests/simple-test.c
new file mode 100644
index 0000000..6f1189f
--- /dev/null
+++ b/tests/simple-test.c
@@ -0,0 +1,59 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- */
+
+/* Lac - Library for asynchronous communication
+ * Copyright (C) 2002 Søren Sandmann (sandmann@daimi.au.dk)
+ *
+ * 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.
+ */
+#include "lac.h"
+#include <string.h>
+
+GMainLoop *main_loop;
+
+static void
+callback (const LacAddress *addr, gpointer data, const GError *err)
+{
+ if (addr)
+ {
+ GError *err2 = NULL;
+ int fd = lac_socket_tcp (NULL);
+ if (!lac_connect (fd, addr, 80, &err2))
+ {
+ g_print ("huh: %s\n", err->message);
+ }
+ else
+ g_print ("ok\n");
+
+ addr = lac_address_new_from_a_b_c_d (130, 225, 18, 72);
+
+ g_print (
+ "This should be 130.225.18.72 -> %s\n", lac_address_to_string (addr));
+ }
+ else
+ {
+ g_print ("error: %s\n", err->message);
+ }
+ g_main_quit (main_loop);
+}
+
+int
+main (int argc, char *argv[])
+{
+ lac_address_new_lookup_from_name ("slashdot.org", callback, NULL);
+
+ g_main_loop_run (main_loop);
+ return 0;
+}
diff --git a/tests/trouble-sites.sh b/tests/trouble-sites.sh
new file mode 100755
index 0000000..b87c859
--- /dev/null
+++ b/tests/trouble-sites.sh
@@ -0,0 +1,8 @@
+echo ==================================== www.ssp.dk
+lacwget http://www.ssp.dk/
+echo ==================================== www.adobe.com
+lacwget http://www.adobe.com
+echo ==================================== www.bold.dk
+lacwget http://www.bold.dk
+echo ==================================== www.sandra-bullock.org
+lacwget http://www.sandra-bullock.org
diff --git a/tests/udp-test.c b/tests/udp-test.c
new file mode 100644
index 0000000..d91254d
--- /dev/null
+++ b/tests/udp-test.c
@@ -0,0 +1,88 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- */
+
+/* Lac - Library for asynchronous communication
+ * Copyright (C) 2002 Søren Sandmann (sandmann@daimi.au.dk)
+ *
+ * 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.
+ */
+#include <lac.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+int
+main (int argc, char *argv[])
+{
+ int i;
+ GMainLoop *main_loop;
+ gchar buffer[5000];
+
+ LacAddress *addr;
+ GError *err = NULL;
+ int fd;
+
+ addr = lac_address_new_from_localhost_wait (NULL, NULL);
+ if (!addr)
+ {
+ g_print ("can't find local IP address \n");
+ }
+ else
+ {
+ char *str = lac_address_to_string (addr);
+ g_print ("local IP address is: %s\n", str);
+ g_free (str);
+ }
+
+ addr = lac_address_new_from_a_b_c_d (130, 225, 18, 72);
+ fd = lac_socket_udp (NULL);
+ if (!lac_connect (fd, addr, 53, &err))
+ {
+ g_print ("connect: %s\n", err->message);
+ exit (1);
+ }
+ else
+ g_print ("connect ok (to %s)\n", lac_address_to_string (addr));
+
+#define MSG "asd jkl;faqpwoe8rusjldklsdf jklajks ls jkf ljkasdf jlsdfsdfa j"
+
+ i = lac_send (fd, MSG, strlen (MSG), &err);
+ if (err)
+ {
+ g_print ("send: %s\n", err->message);
+ exit (1);
+ }
+ g_print ("sent %d\n", i);
+
+ i = lac_recv (fd, buffer, sizeof (buffer), &err);
+ if (err)
+ {
+ g_print ("recv: %s\n", err->message);
+ exit (1);
+ }
+ else
+ {
+ if (i == 0)
+ g_print ("i == 0\n");
+ else
+ while (i > 0)
+ printf ("%c", buffer[i--]);
+ g_print ("\n");
+ }
+
+ lac_close (fd, &err);
+
+ return 0;
+}
diff --git a/tests/uri-test.c b/tests/uri-test.c
new file mode 100644
index 0000000..4285013
--- /dev/null
+++ b/tests/uri-test.c
@@ -0,0 +1,254 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- */
+
+/* Lac - Library for asynchronous communication
+ * Copyright (C) 2000 Søren Sandmann (sandmann@daimi.au.dk)
+ *
+ * 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.
+ */
+
+#include "lac.h"
+
+static void
+print_uri (LacUri *uri, gchar *base_uri_str, gchar *uri_str)
+{
+ g_print ("-----------------------------------------\n");
+ if (base_uri_str)
+ g_print ("base uri: %s\n", base_uri_str);
+ g_print ("uri: %s\n", uri_str);
+ g_print ("\n");
+
+ switch (uri->scheme)
+ {
+ case LAC_SCHEME_UNKNOWN:
+ g_print ("UNKNOWN\n");
+ g_print (" scheme: %s\n", uri->u.unknown.scheme);
+ g_print (" authority: %s\n", uri->u.unknown.authority);
+ g_print (" path: %s\n", uri->u.unknown.path);
+ g_print (" query: %s\n", uri->u.unknown.query);
+ g_print (" fragment: %s\n", uri->fragment);
+ break;
+ case LAC_SCHEME_HTTP:
+ g_print ("HTTP\n");
+ g_print (" host: %s\n", uri->u.http.host);
+ g_print (" port: %d\n", uri->u.http.port);
+ g_print (" path: %s\n", uri->u.http.path);
+ g_print (" query: %s\n", uri->u.http.query);
+ g_print (" fragment: %s\n", uri->fragment);
+ break;
+ case LAC_SCHEME_FTP:
+ g_print ("FTP\n");
+ g_print (" host: %s\n", uri->u.ftp.host);
+ g_print (" username: %s\n", uri->u.ftp.username);
+ g_print (" password: %s\n", uri->u.ftp.password);
+ g_print (" port: %d\n", uri->u.ftp.port);
+ g_print (" path: %s\n", uri->u.ftp.path);
+ g_print (" type: ");
+ switch (uri->u.ftp.type)
+ {
+ case LAC_FTP_TYPE_A:
+ g_print ("a\n");
+ break;
+ case LAC_FTP_TYPE_I:
+ g_print ("i\n");
+ break;
+ case LAC_FTP_TYPE_D:
+ g_print ("d\n");
+ break;
+ case LAC_FTP_TYPE_NONE:
+ g_print ("none\n");
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+ g_print (" fragment %s\n", uri->fragment);
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+int
+main ()
+{
+ LacUri *uri;
+ LacUri *base_uri;
+ gchar *uri_str;
+ gchar *base_uri_str;
+
+ uri_str = "//www.daimi.au.dk/baz.html?asdf#//qwer";
+ base_uri_str = NULL;
+ uri = lac_uri_new_from_str (NULL, uri_str);
+ print_uri (uri, base_uri_str, uri_str);
+
+ uri_str = "http:baz::?####";
+ base_uri_str = NULL;
+ uri = lac_uri_new_from_str (NULL, uri_str);
+ print_uri (uri, base_uri_str, uri_str);
+
+ uri_str = "/index.html#interesting";
+ base_uri_str = NULL;
+ uri = lac_uri_new_from_str (NULL, uri_str);
+ print_uri (uri, base_uri_str, uri_str);
+
+ uri_str = "//www:80/index.html#interesting";
+ base_uri_str = NULL;
+ uri = lac_uri_new_from_str (NULL, uri_str);
+ print_uri (uri, base_uri_str, uri_str);
+
+ uri_str = "#//www:80/index.html#interesting";
+ base_uri_str = NULL;
+ uri = lac_uri_new_from_str (NULL, uri_str);
+ print_uri (uri, base_uri_str, uri_str);
+
+ uri_str = ":/#//www:80/index.html#interesting";
+ base_uri_str = NULL;
+ uri = lac_uri_new_from_str (NULL, uri_str);
+ print_uri (uri, base_uri_str, uri_str);
+
+ uri_str = "mailto:sandmann@daimi.au.dk:80";
+ base_uri_str = NULL;
+ uri = lac_uri_new_from_str (NULL, uri_str);
+ print_uri (uri, base_uri_str, uri_str);
+
+ uri_str = "http://www.daimi.au.dk:1324786912387461234/baz.html";
+ base_uri_str = NULL;
+ uri = lac_uri_new_from_str (NULL, uri_str);
+ print_uri (uri, base_uri_str, uri_str);
+
+ uri_str = "http://www.daimi.au.dk:8080/baz.html?asdf#//qwer";
+ base_uri_str = NULL;
+ uri = lac_uri_new_from_str (NULL, uri_str);
+ print_uri (uri, base_uri_str, uri_str);
+
+ uri_str = "HtTp://www:-1/baz.html?asdf#asdff";
+ base_uri_str = NULL;
+ uri = lac_uri_new_from_str (NULL, uri_str);
+ print_uri (uri, base_uri_str, uri_str);
+
+ uri_str = "HtTp://wWWWWWWWWw.baz:::::as:lkj1/baz.html?asdf#asdff";
+ base_uri_str = NULL;
+ uri = lac_uri_new_from_str (NULL, uri_str);
+ print_uri (uri, base_uri_str, uri_str);
+
+ uri_str = "";
+ base_uri_str = NULL;
+ uri = lac_uri_new_from_str (NULL, uri_str);
+ print_uri (uri, base_uri_str, uri_str);
+
+ uri_str = "http:#?";
+ base_uri_str = NULL;
+ uri = lac_uri_new_from_str (NULL, uri_str);
+ print_uri (uri, base_uri_str, uri_str);
+
+ uri_str = "http://sandmann:baz@www.daimi.au.dk/index.html";
+ base_uri_str = NULL;
+ uri = lac_uri_new_from_str (NULL, uri_str);
+ print_uri (uri, base_uri_str, uri_str);
+
+ uri_str = "ftp://ftp.daimi.au.dk/~biasdf";
+ base_uri_str = NULL;
+ uri = lac_uri_new_from_str (NULL, uri_str);
+ print_uri (uri, base_uri_str, uri_str);
+
+ uri_str = "ftp://sandmann:asdf@ftp.daimi.au.dk/~biasdf";
+ base_uri_str = NULL;
+ uri = lac_uri_new_from_str (NULL, uri_str);
+ print_uri (uri, base_uri_str, uri_str);
+
+ uri_str = "ftp://:asdf@ftp.daimi.au.dk/~biasdf";
+ base_uri_str = NULL;
+ uri = lac_uri_new_from_str (NULL, uri_str);
+ print_uri (uri, base_uri_str, uri_str);
+
+ uri_str = "ftp://foo:bar@ftp.daimi.au.dk/~biasdf";
+ base_uri_str = NULL;
+ uri = lac_uri_new_from_str (NULL, uri_str);
+ print_uri (uri, base_uri_str, uri_str);
+
+ uri_str = "ftp://foo:bar@ftp.com/~biasdf;type=d";
+ base_uri_str = NULL;
+ uri = lac_uri_new_from_str (NULL, uri_str);
+ print_uri (uri, base_uri_str, uri_str);
+
+ uri_str = "ftp://foo:bar@ftp.com/~biasdf;type=x";
+ base_uri_str = NULL;
+ uri = lac_uri_new_from_str (NULL, uri_str);
+ print_uri (uri, base_uri_str, uri_str);
+
+ uri_str = "file://foo:bar@ftp.com/~biasdf;type=x";
+ base_uri_str = NULL;
+ uri = lac_uri_new_from_str (NULL, uri_str);
+ print_uri (uri, base_uri_str, uri_str);
+
+ uri_str = "file:///users/sandmann/public_html/index.html";
+ base_uri_str = NULL;
+ uri = lac_uri_new_from_str (NULL, uri_str);
+ print_uri (uri, base_uri_str, uri_str);
+
+ uri_str = "ftp:///users/sandmann/public_html/index.html";
+ base_uri_str = NULL;
+ uri = lac_uri_new_from_str (NULL, uri_str);
+ print_uri (uri, base_uri_str, uri_str);
+
+ uri_str = "/users/sandman/public_html/index.html";
+ base_uri_str = NULL;
+ uri = lac_uri_new_from_str (NULL, uri_str);
+ print_uri (uri, base_uri_str, uri_str);
+
+ uri_str = "//images.slashdot.org/topics/topictech2.gif";
+ base_uri_str = NULL;
+ uri = lac_uri_new_from_str (NULL, uri_str);
+ print_uri (uri, base_uri_str, uri_str);
+
+ uri_str = "../images/euroheader.gif";
+ base_uri_str = NULL;
+ uri = lac_uri_new_from_str (NULL, uri_str);
+ print_uri (uri, base_uri_str, uri_str);
+
+ uri_str = "Http:.................";
+ base_uri_str = NULL;
+ uri = lac_uri_new_from_str (NULL, uri_str);
+ print_uri (uri, base_uri_str, uri_str);
+
+ base_uri_str = "http://www.europa.com/";
+ base_uri = lac_uri_new_from_str (NULL, base_uri_str);
+ uri_str = "../images/euroheader.gif";
+ uri = lac_uri_new_from_str (base_uri, uri_str);
+ print_uri (uri, base_uri_str, uri_str);
+
+ base_uri_str = NULL;
+ uri_str = "ftp://birnan:emfle@birnan.com:345/images/euroheader.gif";
+ uri = lac_uri_new_from_str (NULL, uri_str);
+ print_uri (uri, base_uri_str, uri_str);
+
+ base_uri_str = NULL;
+ uri_str = "//../birnan.html";
+ uri = lac_uri_new_from_str (NULL, uri_str);
+ print_uri (uri, base_uri_str, uri_str);
+
+ base_uri_str = NULL;
+ uri_str = "///../birnan.html";
+ uri = lac_uri_new_from_str (NULL, uri_str);
+ print_uri (uri, base_uri_str, uri_str);
+
+ base_uri_str = NULL;
+ uri_str = "../birnan.html";
+ uri = lac_uri_new_from_str (NULL, uri_str);
+ print_uri (uri, base_uri_str, uri_str);
+
+ return 0;
+}
diff --git a/tests/watch-test.c b/tests/watch-test.c
new file mode 100644
index 0000000..91ccf42
--- /dev/null
+++ b/tests/watch-test.c
@@ -0,0 +1,108 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- */
+
+/* Lac - Library for asynchronous communication
+ * Copyright (C) 2002 Søren Sandmann (sandmann@daimi.au.dk)
+ *
+ * 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.
+ */
+#include <lac.h>
+#include <string.h>
+#include <errno.h>
+#include <stdio.h>
+
+GMainLoop *main_loop;
+
+static void
+do_write (gint fd)
+{
+ g_print ("write %d\n", fd);
+#if 0
+#define MSG "GET / HTTP/1.1\r\nhost: www.daimi.au.dk\r\n\r\n"
+#endif
+#define MSG "GET /\r\n\r\n"
+ lac_send (fd, MSG, strlen (MSG), NULL);
+ lac_fd_set_write_callback (fd, NULL);
+}
+
+static void
+do_read (gint fd)
+{
+ gchar buf [8192];
+ gint len;
+ gint i;
+
+ len = lac_recv (fd, buf, sizeof (buf), NULL);
+
+ if (len == 0)
+ {
+ g_print ("hangup in read()\n");
+ lac_fd_remove_watch (fd);
+ g_main_quit (main_loop);
+ }
+ else if (len > 0)
+ {
+ for (i = 0; i < len; ++i)
+ printf ("%c", buf[i]);
+ }
+ else if (len < 0)
+ {
+ g_print ("error: %s\n", g_strerror (errno));
+ }
+
+ lac_fd_remove_watch (fd);
+ lac_close (fd, NULL);
+}
+
+static void
+do_hangup (gint fd)
+{
+ g_print ("Abortive close (whatever that means for a TCP connection).\n");
+ lac_fd_remove_watch (fd);
+ g_main_quit (main_loop);
+}
+
+int
+main (int argc, char *argv[])
+{
+#if 0
+ int i;
+#endif
+
+ LacAddress *addr;
+ GError *err = NULL;
+ int fd;
+
+ addr = lac_address_new_from_name_wait ("www.daimi.au.dk.", NULL, NULL);
+ g_print ("address is: %s\n", lac_address_to_string (addr));
+ fd = lac_socket_tcp (NULL);
+ if (!lac_connect (fd, addr, 80, &err))
+ {
+ g_print ("huh: %s\n", err->message);
+ }
+ else
+ g_print ("ok\n");
+
+ lac_fd_add_watch (fd, GINT_TO_POINTER (fd));
+ lac_fd_set_write_callback (fd, do_write);
+ lac_fd_set_read_callback (fd, do_read);
+ lac_fd_set_hangup_callback (fd, do_hangup);
+ lac_fd_set_error_callback (fd, do_read);
+
+ main_loop = g_main_loop_new (NULL, TRUE);
+ g_main_loop_run (main_loop);
+
+ return 0;
+}