diff options
author | sandmann <sandmann> | 2004-05-18 07:48:50 +0000 |
---|---|---|
committer | sandmann <sandmann> | 2004-05-18 07:48:50 +0000 |
commit | cc477a23dc9d7fb4b534d67265b3e3b2a442bb0a (patch) | |
tree | 291c79830282b84f849615afcd009684a62ce794 |
Initial revision
59 files changed, 16428 insertions, 0 deletions
@@ -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 @@ -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 @@ -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 @@ -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 @@ -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) @@ -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; +} |