diff options
author | Nathan Angelacos <nangel@alpinelinux.org> | 2008-12-17 01:34:15 +0000 |
---|---|---|
committer | Nathan Angelacos <nangel@alpinelinux.org> | 2008-12-17 01:34:15 +0000 |
commit | 64b823303af9dcb002370d0611c5783a8c610442 (patch) | |
tree | 883d4d70a5a4f3c593063db13c1e1c2e19b52129 | |
parent | b6de69e02e1a4b2ffebc93cc609bbd000949591d (diff) | |
download | haserl-64b823303af9dcb002370d0611c5783a8c610442.tar.bz2 haserl-64b823303af9dcb002370d0611c5783a8c610442.tar.xz |
Reverted back to 0.9.24 codebase, releasing 0.9.25 off of it. 0.9.25 pre had new llist libraries that seem to be borked
-rw-r--r-- | AUTHORS | 5 | ||||
-rw-r--r-- | COPYING | 340 | ||||
-rw-r--r-- | ChangeLog | 395 | ||||
-rw-r--r-- | INSTALL | 36 | ||||
-rw-r--r-- | Makefile.am | 7 | ||||
-rw-r--r-- | NEWS | 0 | ||||
-rw-r--r-- | README | 19 | ||||
-rw-r--r-- | README.BashExtensions | 161 | ||||
-rw-r--r-- | THANKS | 34 | ||||
-rw-r--r-- | TODO | 18 | ||||
-rw-r--r-- | aclocal.m4 | 868 | ||||
-rw-r--r-- | configure.ac | 191 | ||||
-rw-r--r-- | doc/Makefile.am | 9 | ||||
-rw-r--r-- | doc/haserl.1 | 647 | ||||
-rw-r--r-- | haserl.doxy | 1357 | ||||
-rw-r--r-- | src/Makefile.am | 66 | ||||
-rw-r--r-- | src/common.c | 370 | ||||
-rw-r--r-- | src/common.h | 62 | ||||
-rw-r--r-- | src/config.h.in | 103 | ||||
-rw-r--r-- | src/h_bash.c | 651 | ||||
-rw-r--r-- | src/h_bash.h | 41 | ||||
-rw-r--r-- | src/h_error.c | 103 | ||||
-rw-r--r-- | src/h_error.h | 19 | ||||
-rw-r--r-- | src/h_lua.c | 155 | ||||
-rw-r--r-- | src/h_lua.h | 12 | ||||
-rw-r--r-- | src/h_lua_common.c | 117 | ||||
-rw-r--r-- | src/h_lua_common.h | 7 | ||||
-rw-r--r-- | src/h_luac.c | 55 | ||||
-rw-r--r-- | src/h_luac.h | 7 | ||||
-rw-r--r-- | src/h_script.c | 614 | ||||
-rw-r--r-- | src/h_script.h | 52 | ||||
-rw-r--r-- | src/haserl.c | 917 | ||||
-rw-r--r-- | src/haserl.h | 77 | ||||
-rw-r--r-- | src/haserl_lualib.inc | 90 | ||||
-rw-r--r-- | src/haserl_lualib.lua | 54 | ||||
-rw-r--r-- | src/lua2c.c | 97 | ||||
-rw-r--r-- | src/rfc2388.c | 508 | ||||
-rw-r--r-- | src/rfc2388.h | 28 | ||||
-rw-r--r-- | src/sliding_buffer.c | 180 | ||||
-rw-r--r-- | src/sliding_buffer.h | 27 | ||||
-rw-r--r-- | src/stamp-h.in | 1 | ||||
-rw-r--r-- | tests/Makefile.am | 14 | ||||
-rw-r--r-- | tests/minunit.h | 6 | ||||
-rw-r--r-- | tests/utest_common.c | 46 |
44 files changed, 8566 insertions, 0 deletions
@@ -0,0 +1,5 @@ +Authors of Haserl + + Nathan Angelacos <nangel@users.sourcefore.net> + + @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 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. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..3cfdc26 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,395 @@ +2008-12-16 + 0.9.25 + * Somehow version control failed, and the -d debug short + option reverted to -D again. fixed. + * haserl.c - "command-line" handling was broken on OSX and BSD + fixed. (Mark Blythe) + * haserl.c - fix bug where CONTENT_LENGTH=0 would hang haserl + (bug tracker bug #1959379) + text data bss dec hex filename + 18949 820 172 19941 4de5 haserl +2008-04-14 + 0.9.24 + * haserl.c - myputenv caused a segfault when a variable without + an = (assignment) was passed. (Scott) + * doc/haserl.1 - Misc typos fixed (Scott) + * scott's bash-extensions are included if configured with + --enable-bash-extensions + * running haserl with no args now tells you if lua or + the bash extensions are enabled. + * configure.ac - FORM_ now hardcoded + * tests/* - beginnings of unit tests (make check) + * haserl.c - varibles now stored as FORM_ + GET_, POST_, COOKIE_ + (scott) - WIP - need to clean up the putenv so that only one + copy is saved in the host, and the child gets all the + duplicates + text data bss dec hex filename + 18519 824 172 19515 4c3b src/haserl + +2008-03-22 + 0.9.23 + * haserl.c - short option for debug id -d (was incorrectly -D) + * haserl.c - remove use of legacy "index" clib function. + * haserl.c - myputenv makes a newline delimited "array" out + of variables that end in []. (php-style for multi-selects) + Lua variables will have same behavior - they are not treated + as true arrays yet. (WIP - need to handle the env variables + as abstracts until subshell startup for this to work) + text data bss dec hex filename + 18237 820 128 19185 4af1 src/haserl +2008-01-28 + 0.9.22 + * h_bash.c - close the input side of the pipe, so that if the + child dies early the parent doesn't deadlock (patch by Diego + Santa Cruz) + * rfc2388.c, sliding_buffer.c, haserl.c - read CONTENT_LENGTH, + and stop reading input when CONTENT_LENGTH reached (fixes odd + problems where the client may not close the connection + properly at the end of a request [guess which clients are + MiStaken?]) (patch by Diego Santa Cruz) + * Allow "-" to be used in a field identifier (e.g. FORM["-foo"]) + text data bss dec hex filename + 17813 812 128 18753 4941 src/haserl +2007-11-22 + 0.9.21 + * haserl.c - fixed a stupid test for strlen that caused CGI post + requests to take exponentially longer amounts of time based on + the size of the upload + * h_bash.c - removed open_bash function; found that a CGI post + of > ~150K will cause bash to fail on the subshell. This + appears to be a limitation of execv, not haserl. AFAICT + * h_script.c - Fixed stupid error where the leading html was + dropped if a comment tag was used. + * Update TODO list + text data bss dec hex filename + 17425 808 128 18361 47b9 src/haserl +2007-09-22 + 0.9.20 + * h_script.c - Added Comment tag (<%#) + * various - Removed syslog calls + text data bss dec hex filename + 17425 808 128 18361 47b9 src/haserl +2007-09-16 + 0.9.19 + * haserl.c - If CONTENT_LENGTH not set, don't try to read POST + data (caused haserl to hang on --accept-all) + * configure.ac - Don't require dl on BSD/OSX (N Copa) + * h_script.c - Don't switch <% %> <? ?> in include files. Check + only on the original script (Hinko Kocevar) + * Workaround for cross-compiling haserl_lualib.inc (reported by + Andreas Schultz) + * haserl.* - add extern definitions to shell function pointers + so uClinux (gcc 3.2.3) can compile haserl (glhs329 at gmail) + text data bss dec hex filename + 17584 784 128 18496 4840 src/haserl +2007-07-23 + 0.9.18 + * h_script.c, lua2c.c - Fixed two typos that prevented + compilation. + * configure.ac patch to compile with pkg-config, if found + (ubuntu calls it lua51, not lua) - submitted by Natanael Copa +2007-07-22 + 0.9.17 + * h_bash.c - rc3 commented out the signal call, so an <? exit ?> + would cause the script to hang. + * h_bash.c - don't wait for the echo statement to signal end of + script. Do a waitpid instead. This means fd 5 is now not + used. + * *lua.* - precompiled lua support, with reorganization of all + lua source code (Robin Haberkorn) + * configure.ac / makefile.am - Ability to turn off + shell/lua/luac (Robin Haberkorn) + * lua2c.c - simple luac + bin2c replacement. + * h_script.c / h_error.c - Use <% as the prefered tag element + use <? only when <% is not found in the script. + text data bss dec hex filename + 17580 784 136 18500 4844 src/haserl + +207-07-14 + 0.9.17_rc3 + * haserl.c - Setuid/gid security fix from Timo Teras + * rfc2388.c - upload-handler; fixed problem if boundary + is not last tag in the line. + * haserl.c - fix in myputenv (Robin Haberkorn) + * sliding_buffer.c - Fixes for short reads vs eof (Andreas + Shultz) + * h_bash.c - don't exit when child dies - prevents proper + cleanup; argv[19] was incorrectly overwritten. (Timo Teras) + * text data bss dec hex filename + 17581 784 136 18501 4845 src/haserl +2007-07-08 + 0.9.17_rc2 + * sliding_buffer added to reduce memory requirements on large + uploads + * rfc2388 completely replaces old mimedecoder; adds ability to + upload to a FIFO or program. (This may be a mistake) + * text data bss dec hex filename + size before 15612 716 132 16460 404c src/haserl + size after 17068 712 132 17912 45f8 src/haserl + +2007-02-19 + 0.9.16_rc + 2 code patches from Juris Kalnins based on a code audit + * h_errror.c : die_with_message does not write all error to stdout + when running under httpd + * haserl.c: decode_url oversteps string end on trailing or near trailing % + * haserl.c - If mime block doesn't have a content disposition, skip it + (workaround for bug in Opera 9.10) +2007-02-05 + 0.9.15 + * revert back to "\n" instead of ";" for echo & eval - too many edge cases + where it doesn't work + * haserl+lua compiles on FreeBSD (and possibly OSX - not tested yet) +2007-02-02 + 0.9.14 + * added lua info to manpage +2007-02-01 + 0.9.14_rc + * h_lua.c - ]] (or ]=] ]==] etc) are now "echo command" safe + * h_lua.c h_bash.c - don't add extra linebreaks in echo or eval commands. + use "; " instead + * haserl.c - fixed --accept-all (was --Accept-all) + * h_lua.* h_bash.* haserl.h - now prints the name of the file when + reporting syntax or runtime error. Previously, the script was an + anonymous string + * haserl.c - removed debug message in file-upload unlink code. (oops) + * h_script.c - shell_exec runs "code; ", not "code\n" + * h_script.c - make the error reporting match the line in the source script + (Not perfect, but should be closer than before) +2006-11-14 + 0.9.13 + * Public Release +2006-10-28 + 0.9.13_rc [major code refactoring] + * haserldb removed + * haserll removed; lua code is now part of the main haserl program. + * <?if <?el <?fi <?ab tags removed. + * removed the extras directory + * changed license to GPL2 only +2006-09-26 + 0.9.12 + * haserl.c Alexander Bigga pointed out the accept-all / accept-none short + options were wrong, and the optional argument for -u is a gnu extension. + * h_luascript.c if a form variable has a number component, force the variable + to be numeric -> foo.1.var = foo[1].var, not foo["1"].var +2006-09-12 + 0.9.11 + * a bash shell cannot be opened until AFTER all the environment variables are + placed in the environ. Now have a "pre_open..." set of functions to handle + the difference between lua and shell +2006-09-09 + 0.9.10_rc2 + * Special linking options so that lua "require" will work when linking c libraries + * remove <?if <?el <?fi from lua shell +2006-09-06 + 0.9.10_rc1 + * html outputting now uses io.write with [[ ]], so the html function + is much simpler and faster. + * added <?= eval instruction +2006-09-05 + 0.9.10_alpha1 + * FORM and ENV global tables are populated by haserl. The code will create a + nested table (e.g. FORM[eth][0][type] = static; FORM[eth][1][type] = dhcp + * configure now knows to inlcude libm for math. +2006-09-04 + 0.9.10_alpha0 version + * haserll is built if lua is found. This version will run lua scripts, + and handles printing html code, but does not create the FORM or ENV tables + yet. +2006-08-10 + 0.9.9 version + * Public Release (no changes from rc1) +2006-07-31 + 0.9.9_rc1 + * haserl.c remove check for LABEL and GOTO tags (they don't exist, and + cause segfaults on secure OS'es) + 0.9.9-rc0 + * haserldb.c - added strftime strptime functions +2006-05-19 + 0.9.8 version + * haserl.c - unlink all uploaded files at the end of the script + * haserl.c - --upload-none option added to prevent parsing stdin as + web server content (for haserl scripts called from haserl scripts) + * haserl.c - if its a GET request and --accept-all is set, then silently + ignore the post content if CONTENT_LENGTH is not set. +2006-05-17 + 0.9.7 version + * h_subshell.c - renable code to run user-specified shell +2006-05-15 + 0.9.6 version + * Fix memory overwrite bug when processing NULL length HTML tags + * Fix syntax typos in man page examples + (Both reported by Martin Begheyn) + +2006-05-03 + 0.9.5 version + * common.c, haserldb.c, haserl.c + argc_argv now passes a argv_t pointer around; which includes + an indicator if the string was quoted or not. This allows + keywords to be used as literal strings: + haserldb \"fetch"\ username store + * Large chunks of haserl were refactored. + * haserl.c, h_script.c + If an HTML token is entirely whitespace, output is suppressed. + The "Verbatim" flag is no longer supported. + * h_subshell.c + HTML tokens are passed as "echo -n" commands, so the "debug" + option can be used to actually print out the shell script that would + be used. + All tokens are passed to the subshell, and then the parent haserl waits + for the script to end. This means that the <?if <?el <?fi tokens are + no longer needed, although still supported (for now). + <? while ... ?>stuff<? do ?> + is now possible. + +2005-11-21 + 0.9.4 version + * haserldb.c - sort, rsort, merge and split functions + * haserl.c - fix off-by-one error on include files (included + files were incorrectly truncated) + * silently rejects argv beyond argv[2] + http://192.168.0.1/test.cgi?--upload-limit%3d2059&foo%3dbar + should not not reset the upload limit. This duplicates + 0.8.0 behaviour, broke in 0.9.3 + * haserl.c --silent option (don't print errors advertizing + we are haserl) + * haserldb.c - realloc might not allocate enough space for + the new token in getCommandString - fixed +2005-11-04 + 0.9.3 version + * haserl.c - command-line parsing now uses optarg - "haserl foo.txt" + now works; new command-line options supported. + * haserl.c new command-line options (upload-limit, verbatim, + etc) + * haserl.c - found some memory-overwrite errors in loadscript + (man valgrind) + * haserl.c - token parsing routine (BuildTokenList) refactored. + * HASERL_* vars are now populated from above command-line + options. + * haserl.c - --accept-all functionalitiy added + * haserl.c - <?ab?> command now causes non-zero return code + * haserl.c - <?include .... ?> function added + * haserl.1 - man page updated +2005-10-29 + 0.9.2 version + * extras dirs contains a example login system - + login.cgi, index.cgi, loginlib.sh, haserldb-howto.txt + * haserldb.c - RAM-db is now live throughout a run, so + the RAM-db is now extra storage through run of system + * Some language keyword changes to make the language a + little more orthogonal - repl->sub, clear->empty +2005-10-25 + 0.9.1 version (not released) + * common.c - Improved parsing - now handles empty strings + and arbitrary comment delimiters correctly. This improved + haserldb's command parsing considerably. + * haserldb.c - New functions: ifstack ifempty if +2005-10-23 + 0.9.0 version + * added haserldb (common.* lists.* sha256.* haserldb.*) + * An example of using haserldb is in the extras directory: + (haserldb-howto.txt, loginlib.sh, login.cgi) +2005-03-22 + 0.8.0 version + * configure.ac - Remove check for malloc, as it fails + building with uclibc + * No other changes, upgraded the version number to a + "stable" release because no real problems have been + reported with this code. +2004-12-14 + 0.7.3 version + * configure.ac / configure - include signal.h define in config.h + to compile properly with gcc 2.95.3 + * extras/* - tutorial removed; buttons and a few examples moved here + * doc/haserl.1 - a real manual page +2004-11-10 + 0.7.2 version + * haserl.c - misc fixes from Eric Titl to compile with gcc 2.95.3 + and glibc 2.0.7 + (include SIGNAL_H for sigchild; move variable declaration to + top of function in ReadMimeEncodedInput) +2004-11-02 + 0.7.1 version + No change from 0.7.0 - version # incremented because + of a sourceforge upload fault. + 0.7.0 version + * haserl.c - The interpreter now starts a single subshell, and + all commands are run from that shell. State is now + preserved between code blocks. (Thanks to Arne Bernin + for suggestions on getting this working.) + * haserl.c - a "u" must be on the command line (#!/usr/bin/haserl -u) + to allow file uploads. (Security feature - prevent + malicious clients from uploading abitrary data to /tmp) + * haserl.c - attempt to set uid/gid to the owner/group of the + script. + * tutorial/*.in - fix the tutorial to reflect changes above +2004-10-25 + 0.6.0 version + * haserl.c - HASERL_VAR_PREFIX (config.h) prefixed to + all user supplied strings. This is to prevent + the client from easily polluting global namespace, + such as "foo?SCRIPT_NAME=/blah.txt" + +2004-10-06 + 0.5.1 version + * haserl.c - <? (run) tags can now be delimited by + space, tab, or newline. This means <?\n will + now work correctly. +2004-09-28 + 0.5.0 version + * haserl.c - HTTP_COOKIE is now parsed and the contents + placed in env variables before any other + parsing is done. +2004-09-27 + 0.4.3 version + * haserl.c - "abort" doesn't follow the standard of 2 chars + (if/el/fi); changed to "ab" +2004-09-24 + 0.4.2 version + * haserl.c/.h: Added the "abort" directive. + * tutorial/language.cgi.in - documented the abort function + * tutorial/source.cgi.in - added the code to make the "source" + link at the bottom of each web page work. +2004-09-02 + 0.4.1 version + * haserl.c: The name of the tempfile created by a mime-upload + was not stored in the variable by that name. Fixed. + +2004-07-25 + 0.4.0 version ------------------------------------ + +2004-07-26 + * haserl.c: Fixed problem with POST data reading stdin "twice" + * tutorial/* all web pages are now in the tutorial + +2004-07-25 + * haserl.c: WCVER is now HASERLVER; fixed problem with POSTs returning + "unable to read from stdin" + + +2004-07-23 + * haserl.c / haserl.h: Project renamed to haserl + (Html And Shell Embedded Runtime Language) + (Html And Shell Extraction and Report Language) (a.k.a pERL) + + Added code to specify maximum upload size, to prevent + 20GB file uploads. Current compile default is 2MB + + * Makefile.am: + Make -Wall -O2 CFLAG defaults + + +2004-07-14 + * Added autoconf/automake support + + * webconf.c: added code to support and use autoconf/automake constructs, + + added code to protect from uploads of arbitrary size (compile-time + setting up MAX_UPLOAD_MB) + + added logic to allow a "zero-length" upload to proceed - its + /possible/ someone does a post without any form elements + selected. + +0.2.0 + Initial release. @@ -0,0 +1,36 @@ +To build this package, you will need the autoconf/automake tools. + +BUILDING FROM SVN SOURCES +-------------------------- +aclocal && autoheader && automake -a && autoconf + + +BUILDING HASERL (without lua) +----------------------------- +./configure +make +make install + +You might need to be root to do the 'make install'. + + +BUILDING WITH EMBEDDED LUA VM +----------------------------- +If lua 5.1.x dev libraries are already installed, then + +./configure --with-lua +make +make install + +If you don't have the lua 5.1.x dev libraries, or you have +a different version than 5.1.x, then: + +tar zxvf lua-5.1.1.tar.gz +cd lua-5.1.1 +make generic | make posix | make linux | ... +mkdir /tmp/lua +cp src/* /tmp/lua +cd <haserl path> +./configure --with-lua=/tmp/lua --with-lua-headers=/tmp/lua +make + diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..574b847 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,7 @@ +PACKAGE=@PACKAGE_TARNAME@ + +LIBS= +SUBDIRS = src doc tests +EXTRA_DIST = README.BashExtensions +docdir=$(datadir)/$(PACKAGE) +datarootdir=@datarootdir@ @@ -0,0 +1,19 @@ +haserl (Html And Shell Embedded Runtime Language) is a cgi +program that runs interpreted scripts. It combines three +elements into a single CGI interpreter: + +1. It parses POST and GET requests, placing form-elements as name=value +pairs into the environment for the CGI script to use. It is similar +to uncgi (http://www.midwinter.com/~koreth/uncgi.html) in this respect + +2. It prints the contents of the script as html, and conditionally +interpets text within <% ... %> as shell script. In this case haserl +scripts are like a poor-man's version of PHP (http://www.php.net) + +3. It is very small, and so can be used in embedded environments + + +Read INSTALL on how to compile haserl + + + diff --git a/README.BashExtensions b/README.BashExtensions new file mode 100644 index 0000000..6c961ad --- /dev/null +++ b/README.BashExtensions @@ -0,0 +1,161 @@ +April 2008 + +The following documents the modifications to the haserl parser +when --enable-bash-extensions is enabled. The following was +written by Scott, and should not be considered as a core part +of haserl. (Ask on the mailing list if you need help) + +-------------------------------------------------------------- + +Haserl supports four tags by default. These are generic and +suitable for use with a great variety of shells. If, like +me, you write your scripts with vi (vim) with the syntax +highlighting enabled, then you will have noted how badly +this looks in the editor. + +I also prefer the tags to be a bit more intelligent, as I +find it makes the source that much more readable. + +To this end I have added the following tags (again, these +are designed with bash in mind, may not work with other +sh-type shells, and are not at all supported if you are +using LUA/LUAC.) + + + Tag version: Expands to: + ------------------------ ------------------------ + <%if list %> if [[ list ]] ; then + <%elif list %> elif [[ list ]] ; then + <%else [comment] %> else [# comment] + <%endif [comment] %> fi [# comment] + + <%case word %> case word in + <%when pattern %> pattern) + <%otherwise [comment] %> *) : [comment] ;; + <%endcase [comment] %> esac [# comment] + + <%while list %> while list ; do + <%endwhile %> done [# comment] + + <%until list %> until list ; do + <%enduntil [comment] %> done [# comment] + + <%for name in word %> for name in word ; do + <%endfor [comment] %> done [# comment] + + <%unless list %> if [[ ! list ]] ; then + <%elun list %> elif [[ ! list ]] ; then + <%unelse [comment] %> else [#comment] + <%endunless [comment %> fi [# comment] + + +To simplify parsing, and to reduce confusion when reading +your source, unique words are used. For example, the use of +endwhile, enduntil and endfor instead of done. Also, for +clarity I have used endif/endcase instead of fi/esac. + +------ +UNLESS +------ + +Note the last command, the unless...endunless. I find it is +often more clear to express an unless condition than an if +not condition. For example, and to show the streamlining of +code these tags provide: + + <% if [[ ! IsLogged ]] ; then %> + something + <% fi %> + +This is the improved (IMHO) version: + + <%unless IsLogged %> + something + <%endunless %> + +Personally, I find no use for "else unless", but I decided +to include it for consistency. + +-------------------- +CONDITIONAL COMMANDS +-------------------- + +There is one syntactic problem, concerning the use of the +<%if %>, <%while %>, <%until %> and <%unless %> tags. The +default action is to create a test-style condition; however, +sometime you need to do this: + + <% if grep "text" file &>/dev/null ; then %> + +If you simply write this: + + <%if grep "text" file &>/dev/null %> + +Then it is expanded to: + + if [[ grep "text" file &>/dev/null ]] %> + +That is obviously incorrect. To allow for this the parser +provides a bit of syntactic sugar. It checks the first +character of the expression list and if it is, then it +rewrites the list as a command. For example: + + <%if |grep "text" file &>/dev/null %> + +Becomes: + + if grep "text" file &>/dev/null ; then + +-------------- +CASE STATMENTS +-------------- + +The case statement provided another challenge, namely how to +handle the pesky ;; required at the end of each case. I am +not a C person, so personally I find the next instance of a +case to be sufficient to terminate the first. Given that the +shell version of case can accept multiple conditions, there +is really no reason to worry with cases falling through as +they do in C. In other words, I'm all for ditching the ;; +construct altogether. + +So I did. + +The enhanced <%case %> tag operates without an explicit ;; +being required. It is perhaps easier to show than explain, +so here's an example: + + <%case "$FRUIT" %> + <%when apple %> + echo "It's an apple!" + <%when orange %> + echo "It's an orange!" + <%otherwise something else %> + echo "Not what I expected." + <%endcase %> + +The parser renders this as follows: + + case "$FRUIT" in + "\000\012\004") : + ;; + apple) + echo "It's an apple!" + ;; + orange) + echo "It's an orange!" + ;; + *) # something else + echo "Not what I expected." + ;; + esac + +Note the odd first case. The parser inserts this so ensure +that each subsequent when/otherwise/endcase can be preceded +by ;;. This eliminates the need to remember the ;;, and I +believe makes for cleaner code. There is, of course, a small +performance penalty for always having to evaluate this extra +case. In real life I find this not to be a problem, however, +if you are processing inside a loop and expect a large chunk +of items, then it is something to be aware of. + @@ -0,0 +1,34 @@ +Haserl THANKS file + +Haserl was written by Nathan Angelacos. + +It was the result of work done by Cameron Banta and David Krause, who +wrote a web-configuration engine using AWK scripts. + +Natanael Copa assisted with the autoconf scripts, as well as general +support of the project. + +Arne Bernin submitted the first patch to make the shell variables +survive code blocks. His comments and ideas got the whole sub-shell +thing going. + +Erich Titl helped with some of the configure automake checks. + +Vladislav Moskovets suggested the include directive. + +Andreas Brodmann provided the incentive to add lua support. + +Robin Haberkorn supplied the luac (precompiled lua) support, and then +did so a second time after the entire source tree was reformatted. + +Diego Santa Cruz from spinetix.com provided fixes to some really +annoying (and sporadic) deadlocks with IE, thttpd, POST requests. + +Scott did the COOKIE_, POST_, GET_ parsing (he's also responsible for +the bash extensions) + +Mark Blythe identified differences between BSD/OSX and Linux shell +handling of #! scripts, and how argv[1] looks. Then he supplied the +fix. + + @@ -0,0 +1,18 @@ +15 Feb 2008 +* multiselect needs to return an array of values. PHP handles it like this: + if the variable is foo[] then an array is returned. If the variable name + in the form is foo, then the last value is returned. For shell, make it + a \n delimited array, so that you can do for a in $foo; do (bb ash doesnt + support real arrays.) Lua could though. (Scott) - DONE - but lua is newline + terminated, not as a table. +25 Jan 2008 +* change sliding_buffer into a circular buffer to reduce mem moves. +* turn haserl core into a busybox applet +20 Nov 2007 +* Add option to separate FORM into COOKIE, GET, POST tables (DONE - Scott) +* Make it easier for people like Scott to make new haserl tags +* Release a 0.10.0 or 1.0.0 version already +12 Jul 2007 +* combine all the llist stuff into one generic library. (save space) +6 Feb 2007 +* Investigate fastcgi for the lua version of haserl diff --git a/aclocal.m4 b/aclocal.m4 new file mode 100644 index 0000000..eb3b99a --- /dev/null +++ b/aclocal.m4 @@ -0,0 +1,868 @@ +# generated automatically by aclocal 1.10.1 -*- Autoconf -*- + +# Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, +# 2005, 2006, 2007, 2008 Free Software Foundation, Inc. +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +m4_ifndef([AC_AUTOCONF_VERSION], + [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl +m4_if(AC_AUTOCONF_VERSION, [2.61],, +[m4_warning([this file was generated for autoconf 2.61. +You have another version of autoconf. It may work, but is not guaranteed to. +If you have problems, you may need to regenerate the build system entirely. +To do so, use the procedure documented by the package, typically `autoreconf'.])]) + +# Copyright (C) 2002, 2003, 2005, 2006, 2007 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_AUTOMAKE_VERSION(VERSION) +# ---------------------------- +# Automake X.Y traces this macro to ensure aclocal.m4 has been +# generated from the m4 files accompanying Automake X.Y. +# (This private macro should not be called outside this file.) +AC_DEFUN([AM_AUTOMAKE_VERSION], +[am__api_version='1.10' +dnl Some users find AM_AUTOMAKE_VERSION and mistake it for a way to +dnl require some minimum version. Point them to the right macro. +m4_if([$1], [1.10.1], [], + [AC_FATAL([Do not call $0, use AM_INIT_AUTOMAKE([$1]).])])dnl +]) + +# _AM_AUTOCONF_VERSION(VERSION) +# ----------------------------- +# aclocal traces this macro to find the Autoconf version. +# This is a private macro too. Using m4_define simplifies +# the logic in aclocal, which can simply ignore this definition. +m4_define([_AM_AUTOCONF_VERSION], []) + +# AM_SET_CURRENT_AUTOMAKE_VERSION +# ------------------------------- +# Call AM_AUTOMAKE_VERSION and AM_AUTOMAKE_VERSION so they can be traced. +# This function is AC_REQUIREd by AC_INIT_AUTOMAKE. +AC_DEFUN([AM_SET_CURRENT_AUTOMAKE_VERSION], +[AM_AUTOMAKE_VERSION([1.10.1])dnl +m4_ifndef([AC_AUTOCONF_VERSION], + [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl +_AM_AUTOCONF_VERSION(AC_AUTOCONF_VERSION)]) + +# AM_AUX_DIR_EXPAND -*- Autoconf -*- + +# Copyright (C) 2001, 2003, 2005 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# For projects using AC_CONFIG_AUX_DIR([foo]), Autoconf sets +# $ac_aux_dir to `$srcdir/foo'. In other projects, it is set to +# `$srcdir', `$srcdir/..', or `$srcdir/../..'. +# +# Of course, Automake must honor this variable whenever it calls a +# tool from the auxiliary directory. The problem is that $srcdir (and +# therefore $ac_aux_dir as well) can be either absolute or relative, +# depending on how configure is run. This is pretty annoying, since +# it makes $ac_aux_dir quite unusable in subdirectories: in the top +# source directory, any form will work fine, but in subdirectories a +# relative path needs to be adjusted first. +# +# $ac_aux_dir/missing +# fails when called from a subdirectory if $ac_aux_dir is relative +# $top_srcdir/$ac_aux_dir/missing +# fails if $ac_aux_dir is absolute, +# fails when called from a subdirectory in a VPATH build with +# a relative $ac_aux_dir +# +# The reason of the latter failure is that $top_srcdir and $ac_aux_dir +# are both prefixed by $srcdir. In an in-source build this is usually +# harmless because $srcdir is `.', but things will broke when you +# start a VPATH build or use an absolute $srcdir. +# +# So we could use something similar to $top_srcdir/$ac_aux_dir/missing, +# iff we strip the leading $srcdir from $ac_aux_dir. That would be: +# am_aux_dir='\$(top_srcdir)/'`expr "$ac_aux_dir" : "$srcdir//*\(.*\)"` +# and then we would define $MISSING as +# MISSING="\${SHELL} $am_aux_dir/missing" +# This will work as long as MISSING is not called from configure, because +# unfortunately $(top_srcdir) has no meaning in configure. +# However there are other variables, like CC, which are often used in +# configure, and could therefore not use this "fixed" $ac_aux_dir. +# +# Another solution, used here, is to always expand $ac_aux_dir to an +# absolute PATH. The drawback is that using absolute paths prevent a +# configured tree to be moved without reconfiguration. + +AC_DEFUN([AM_AUX_DIR_EXPAND], +[dnl Rely on autoconf to set up CDPATH properly. +AC_PREREQ([2.50])dnl +# expand $ac_aux_dir to an absolute path +am_aux_dir=`cd $ac_aux_dir && pwd` +]) + +# AM_CONDITIONAL -*- Autoconf -*- + +# Copyright (C) 1997, 2000, 2001, 2003, 2004, 2005, 2006 +# Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 8 + +# AM_CONDITIONAL(NAME, SHELL-CONDITION) +# ------------------------------------- +# Define a conditional. +AC_DEFUN([AM_CONDITIONAL], +[AC_PREREQ(2.52)dnl + ifelse([$1], [TRUE], [AC_FATAL([$0: invalid condition: $1])], + [$1], [FALSE], [AC_FATAL([$0: invalid condition: $1])])dnl +AC_SUBST([$1_TRUE])dnl +AC_SUBST([$1_FALSE])dnl +_AM_SUBST_NOTMAKE([$1_TRUE])dnl +_AM_SUBST_NOTMAKE([$1_FALSE])dnl +if $2; then + $1_TRUE= + $1_FALSE='#' +else + $1_TRUE='#' + $1_FALSE= +fi +AC_CONFIG_COMMANDS_PRE( +[if test -z "${$1_TRUE}" && test -z "${$1_FALSE}"; then + AC_MSG_ERROR([[conditional "$1" was never defined. +Usually this means the macro was only invoked conditionally.]]) +fi])]) + +# Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006 +# Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 9 + +# There are a few dirty hacks below to avoid letting `AC_PROG_CC' be +# written in clear, in which case automake, when reading aclocal.m4, +# will think it sees a *use*, and therefore will trigger all it's +# C support machinery. Also note that it means that autoscan, seeing +# CC etc. in the Makefile, will ask for an AC_PROG_CC use... + + +# _AM_DEPENDENCIES(NAME) +# ---------------------- +# See how the compiler implements dependency checking. +# NAME is "CC", "CXX", "GCJ", or "OBJC". +# We try a few techniques and use that to set a single cache variable. +# +# We don't AC_REQUIRE the corresponding AC_PROG_CC since the latter was +# modified to invoke _AM_DEPENDENCIES(CC); we would have a circular +# dependency, and given that the user is not expected to run this macro, +# just rely on AC_PROG_CC. +AC_DEFUN([_AM_DEPENDENCIES], +[AC_REQUIRE([AM_SET_DEPDIR])dnl +AC_REQUIRE([AM_OUTPUT_DEPENDENCY_COMMANDS])dnl +AC_REQUIRE([AM_MAKE_INCLUDE])dnl +AC_REQUIRE([AM_DEP_TRACK])dnl + +ifelse([$1], CC, [depcc="$CC" am_compiler_list=], + [$1], CXX, [depcc="$CXX" am_compiler_list=], + [$1], OBJC, [depcc="$OBJC" am_compiler_list='gcc3 gcc'], + [$1], UPC, [depcc="$UPC" am_compiler_list=], + [$1], GCJ, [depcc="$GCJ" am_compiler_list='gcc3 gcc'], + [depcc="$$1" am_compiler_list=]) + +AC_CACHE_CHECK([dependency style of $depcc], + [am_cv_$1_dependencies_compiler_type], +[if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then + # We make a subdir and do the tests there. Otherwise we can end up + # making bogus files that we don't know about and never remove. For + # instance it was reported that on HP-UX the gcc test will end up + # making a dummy file named `D' -- because `-MD' means `put the output + # in D'. + mkdir conftest.dir + # Copy depcomp to subdir because otherwise we won't find it if we're + # using a relative directory. + cp "$am_depcomp" conftest.dir + cd conftest.dir + # We will build objects and dependencies in a subdirectory because + # it helps to detect inapplicable dependency modes. For instance + # both Tru64's cc and ICC support -MD to output dependencies as a + # side effect of compilation, but ICC will put the dependencies in + # the current directory while Tru64 will put them in the object + # directory. + mkdir sub + + am_cv_$1_dependencies_compiler_type=none + if test "$am_compiler_list" = ""; then + am_compiler_list=`sed -n ['s/^#*\([a-zA-Z0-9]*\))$/\1/p'] < ./depcomp` + fi + for depmode in $am_compiler_list; do + # Setup a source with many dependencies, because some compilers + # like to wrap large dependency lists on column 80 (with \), and + # we should not choose a depcomp mode which is confused by this. + # + # We need to recreate these files for each test, as the compiler may + # overwrite some of them when testing with obscure command lines. + # This happens at least with the AIX C compiler. + : > sub/conftest.c + for i in 1 2 3 4 5 6; do + echo '#include "conftst'$i'.h"' >> sub/conftest.c + # Using `: > sub/conftst$i.h' creates only sub/conftst1.h with + # Solaris 8's {/usr,}/bin/sh. + touch sub/conftst$i.h + done + echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf + + case $depmode in + nosideeffect) + # after this tag, mechanisms are not by side-effect, so they'll + # only be used when explicitly requested + if test "x$enable_dependency_tracking" = xyes; then + continue + else + break + fi + ;; + none) break ;; + esac + # We check with `-c' and `-o' for the sake of the "dashmstdout" + # mode. It turns out that the SunPro C++ compiler does not properly + # handle `-M -o', and we need to detect this. + if depmode=$depmode \ + source=sub/conftest.c object=sub/conftest.${OBJEXT-o} \ + depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \ + $SHELL ./depcomp $depcc -c -o sub/conftest.${OBJEXT-o} sub/conftest.c \ + >/dev/null 2>conftest.err && + grep sub/conftst1.h sub/conftest.Po > /dev/null 2>&1 && + grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 && + grep sub/conftest.${OBJEXT-o} sub/conftest.Po > /dev/null 2>&1 && + ${MAKE-make} -s -f confmf > /dev/null 2>&1; then + # icc doesn't choke on unknown options, it will just issue warnings + # or remarks (even with -Werror). So we grep stderr for any message + # that says an option was ignored or not supported. + # When given -MP, icc 7.0 and 7.1 complain thusly: + # icc: Command line warning: ignoring option '-M'; no argument required + # The diagnosis changed in icc 8.0: + # icc: Command line remark: option '-MP' not supported + if (grep 'ignoring option' conftest.err || + grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else + am_cv_$1_dependencies_compiler_type=$depmode + break + fi + fi + done + + cd .. + rm -rf conftest.dir +else + am_cv_$1_dependencies_compiler_type=none +fi +]) +AC_SUBST([$1DEPMODE], [depmode=$am_cv_$1_dependencies_compiler_type]) +AM_CONDITIONAL([am__fastdep$1], [ + test "x$enable_dependency_tracking" != xno \ + && test "$am_cv_$1_dependencies_compiler_type" = gcc3]) +]) + + +# AM_SET_DEPDIR +# ------------- +# Choose a directory name for dependency files. +# This macro is AC_REQUIREd in _AM_DEPENDENCIES +AC_DEFUN([AM_SET_DEPDIR], +[AC_REQUIRE([AM_SET_LEADING_DOT])dnl +AC_SUBST([DEPDIR], ["${am__leading_dot}deps"])dnl +]) + + +# AM_DEP_TRACK +# ------------ +AC_DEFUN([AM_DEP_TRACK], +[AC_ARG_ENABLE(dependency-tracking, +[ --disable-dependency-tracking speeds up one-time build + --enable-dependency-tracking do not reject slow dependency extractors]) +if test "x$enable_dependency_tracking" != xno; then + am_depcomp="$ac_aux_dir/depcomp" + AMDEPBACKSLASH='\' +fi +AM_CONDITIONAL([AMDEP], [test "x$enable_dependency_tracking" != xno]) +AC_SUBST([AMDEPBACKSLASH])dnl +_AM_SUBST_NOTMAKE([AMDEPBACKSLASH])dnl +]) + +# Generate code to set up dependency tracking. -*- Autoconf -*- + +# Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 +# Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +#serial 3 + +# _AM_OUTPUT_DEPENDENCY_COMMANDS +# ------------------------------ +AC_DEFUN([_AM_OUTPUT_DEPENDENCY_COMMANDS], +[for mf in $CONFIG_FILES; do + # Strip MF so we end up with the name of the file. + mf=`echo "$mf" | sed -e 's/:.*$//'` + # Check whether this is an Automake generated Makefile or not. + # We used to match only the files named `Makefile.in', but + # some people rename them; so instead we look at the file content. + # Grep'ing the first line is not enough: some people post-process + # each Makefile.in and add a new line on top of each file to say so. + # Grep'ing the whole file is not good either: AIX grep has a line + # limit of 2048, but all sed's we know have understand at least 4000. + if sed -n 's,^#.*generated by automake.*,X,p' "$mf" | grep X >/dev/null 2>&1; then + dirpart=`AS_DIRNAME("$mf")` + else + continue + fi + # Extract the definition of DEPDIR, am__include, and am__quote + # from the Makefile without running `make'. + DEPDIR=`sed -n 's/^DEPDIR = //p' < "$mf"` + test -z "$DEPDIR" && continue + am__include=`sed -n 's/^am__include = //p' < "$mf"` + test -z "am__include" && continue + am__quote=`sed -n 's/^am__quote = //p' < "$mf"` + # When using ansi2knr, U may be empty or an underscore; expand it + U=`sed -n 's/^U = //p' < "$mf"` + # Find all dependency output files, they are included files with + # $(DEPDIR) in their names. We invoke sed twice because it is the + # simplest approach to changing $(DEPDIR) to its actual value in the + # expansion. + for file in `sed -n " + s/^$am__include $am__quote\(.*(DEPDIR).*\)$am__quote"'$/\1/p' <"$mf" | \ + sed -e 's/\$(DEPDIR)/'"$DEPDIR"'/g' -e 's/\$U/'"$U"'/g'`; do + # Make sure the directory exists. + test -f "$dirpart/$file" && continue + fdir=`AS_DIRNAME(["$file"])` + AS_MKDIR_P([$dirpart/$fdir]) + # echo "creating $dirpart/$file" + echo '# dummy' > "$dirpart/$file" + done +done +])# _AM_OUTPUT_DEPENDENCY_COMMANDS + + +# AM_OUTPUT_DEPENDENCY_COMMANDS +# ----------------------------- +# This macro should only be invoked once -- use via AC_REQUIRE. +# +# This code is only required when automatic dependency tracking +# is enabled. FIXME. This creates each `.P' file that we will +# need in order to bootstrap the dependency handling code. +AC_DEFUN([AM_OUTPUT_DEPENDENCY_COMMANDS], +[AC_CONFIG_COMMANDS([depfiles], + [test x"$AMDEP_TRUE" != x"" || _AM_OUTPUT_DEPENDENCY_COMMANDS], + [AMDEP_TRUE="$AMDEP_TRUE" ac_aux_dir="$ac_aux_dir"]) +]) + +# Do all the work for Automake. -*- Autoconf -*- + +# Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, +# 2005, 2006, 2008 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 13 + +# This macro actually does too much. Some checks are only needed if +# your package does certain things. But this isn't really a big deal. + +# AM_INIT_AUTOMAKE(PACKAGE, VERSION, [NO-DEFINE]) +# AM_INIT_AUTOMAKE([OPTIONS]) +# ----------------------------------------------- +# The call with PACKAGE and VERSION arguments is the old style +# call (pre autoconf-2.50), which is being phased out. PACKAGE +# and VERSION should now be passed to AC_INIT and removed from +# the call to AM_INIT_AUTOMAKE. +# We support both call styles for the transition. After +# the next Automake release, Autoconf can make the AC_INIT +# arguments mandatory, and then we can depend on a new Autoconf +# release and drop the old call support. +AC_DEFUN([AM_INIT_AUTOMAKE], +[AC_PREREQ([2.60])dnl +dnl Autoconf wants to disallow AM_ names. We explicitly allow +dnl the ones we care about. +m4_pattern_allow([^AM_[A-Z]+FLAGS$])dnl +AC_REQUIRE([AM_SET_CURRENT_AUTOMAKE_VERSION])dnl +AC_REQUIRE([AC_PROG_INSTALL])dnl +if test "`cd $srcdir && pwd`" != "`pwd`"; then + # Use -I$(srcdir) only when $(srcdir) != ., so that make's output + # is not polluted with repeated "-I." + AC_SUBST([am__isrc], [' -I$(srcdir)'])_AM_SUBST_NOTMAKE([am__isrc])dnl + # test to see if srcdir already configured + if test -f $srcdir/config.status; then + AC_MSG_ERROR([source directory already configured; run "make distclean" there first]) + fi +fi + +# test whether we have cygpath +if test -z "$CYGPATH_W"; then + if (cygpath --version) >/dev/null 2>/dev/null; then + CYGPATH_W='cygpath -w' + else + CYGPATH_W=echo + fi +fi +AC_SUBST([CYGPATH_W]) + +# Define the identity of the package. +dnl Distinguish between old-style and new-style calls. +m4_ifval([$2], +[m4_ifval([$3], [_AM_SET_OPTION([no-define])])dnl + AC_SUBST([PACKAGE], [$1])dnl + AC_SUBST([VERSION], [$2])], +[_AM_SET_OPTIONS([$1])dnl +dnl Diagnose old-style AC_INIT with new-style AM_AUTOMAKE_INIT. +m4_if(m4_ifdef([AC_PACKAGE_NAME], 1)m4_ifdef([AC_PACKAGE_VERSION], 1), 11,, + [m4_fatal([AC_INIT should be called with package and version arguments])])dnl + AC_SUBST([PACKAGE], ['AC_PACKAGE_TARNAME'])dnl + AC_SUBST([VERSION], ['AC_PACKAGE_VERSION'])])dnl + +_AM_IF_OPTION([no-define],, +[AC_DEFINE_UNQUOTED(PACKAGE, "$PACKAGE", [Name of package]) + AC_DEFINE_UNQUOTED(VERSION, "$VERSION", [Version number of package])])dnl + +# Some tools Automake needs. +AC_REQUIRE([AM_SANITY_CHECK])dnl +AC_REQUIRE([AC_ARG_PROGRAM])dnl +AM_MISSING_PROG(ACLOCAL, aclocal-${am__api_version}) +AM_MISSING_PROG(AUTOCONF, autoconf) +AM_MISSING_PROG(AUTOMAKE, automake-${am__api_version}) +AM_MISSING_PROG(AUTOHEADER, autoheader) +AM_MISSING_PROG(MAKEINFO, makeinfo) +AM_PROG_INSTALL_SH +AM_PROG_INSTALL_STRIP +AC_REQUIRE([AM_PROG_MKDIR_P])dnl +# We need awk for the "check" target. The system "awk" is bad on +# some platforms. +AC_REQUIRE([AC_PROG_AWK])dnl +AC_REQUIRE([AC_PROG_MAKE_SET])dnl +AC_REQUIRE([AM_SET_LEADING_DOT])dnl +_AM_IF_OPTION([tar-ustar], [_AM_PROG_TAR([ustar])], + [_AM_IF_OPTION([tar-pax], [_AM_PROG_TAR([pax])], + [_AM_PROG_TAR([v7])])]) +_AM_IF_OPTION([no-dependencies],, +[AC_PROVIDE_IFELSE([AC_PROG_CC], + [_AM_DEPENDENCIES(CC)], + [define([AC_PROG_CC], + defn([AC_PROG_CC])[_AM_DEPENDENCIES(CC)])])dnl +AC_PROVIDE_IFELSE([AC_PROG_CXX], + [_AM_DEPENDENCIES(CXX)], + [define([AC_PROG_CXX], + defn([AC_PROG_CXX])[_AM_DEPENDENCIES(CXX)])])dnl +AC_PROVIDE_IFELSE([AC_PROG_OBJC], + [_AM_DEPENDENCIES(OBJC)], + [define([AC_PROG_OBJC], + defn([AC_PROG_OBJC])[_AM_DEPENDENCIES(OBJC)])])dnl +]) +]) + + +# When config.status generates a header, we must update the stamp-h file. +# This file resides in the same directory as the config header +# that is generated. The stamp files are numbered to have different names. + +# Autoconf calls _AC_AM_CONFIG_HEADER_HOOK (when defined) in the +# loop where config.status creates the headers, so we can generate +# our stamp files there. +AC_DEFUN([_AC_AM_CONFIG_HEADER_HOOK], +[# Compute $1's index in $config_headers. +_am_arg=$1 +_am_stamp_count=1 +for _am_header in $config_headers :; do + case $_am_header in + $_am_arg | $_am_arg:* ) + break ;; + * ) + _am_stamp_count=`expr $_am_stamp_count + 1` ;; + esac +done +echo "timestamp for $_am_arg" >`AS_DIRNAME(["$_am_arg"])`/stamp-h[]$_am_stamp_count]) + +# Copyright (C) 2001, 2003, 2005 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_PROG_INSTALL_SH +# ------------------ +# Define $install_sh. +AC_DEFUN([AM_PROG_INSTALL_SH], +[AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl +install_sh=${install_sh-"\$(SHELL) $am_aux_dir/install-sh"} +AC_SUBST(install_sh)]) + +# Copyright (C) 2003, 2005 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 2 + +# Check whether the underlying file-system supports filenames +# with a leading dot. For instance MS-DOS doesn't. +AC_DEFUN([AM_SET_LEADING_DOT], +[rm -rf .tst 2>/dev/null +mkdir .tst 2>/dev/null +if test -d .tst; then + am__leading_dot=. +else + am__leading_dot=_ +fi +rmdir .tst 2>/dev/null +AC_SUBST([am__leading_dot])]) + +# Check to see how 'make' treats includes. -*- Autoconf -*- + +# Copyright (C) 2001, 2002, 2003, 2005 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 3 + +# AM_MAKE_INCLUDE() +# ----------------- +# Check to see how make treats includes. +AC_DEFUN([AM_MAKE_INCLUDE], +[am_make=${MAKE-make} +cat > confinc << 'END' +am__doit: + @echo done +.PHONY: am__doit +END +# If we don't find an include directive, just comment out the code. +AC_MSG_CHECKING([for style of include used by $am_make]) +am__include="#" +am__quote= +_am_result=none +# First try GNU make style include. +echo "include confinc" > confmf +# We grep out `Entering directory' and `Leaving directory' +# messages which can occur if `w' ends up in MAKEFLAGS. +# In particular we don't look at `^make:' because GNU make might +# be invoked under some other name (usually "gmake"), in which +# case it prints its new name instead of `make'. +if test "`$am_make -s -f confmf 2> /dev/null | grep -v 'ing directory'`" = "done"; then + am__include=include + am__quote= + _am_result=GNU +fi +# Now try BSD make style include. +if test "$am__include" = "#"; then + echo '.include "confinc"' > confmf + if test "`$am_make -s -f confmf 2> /dev/null`" = "done"; then + am__include=.include + am__quote="\"" + _am_result=BSD + fi +fi +AC_SUBST([am__include]) +AC_SUBST([am__quote]) +AC_MSG_RESULT([$_am_result]) +rm -f confinc confmf +]) + +# Fake the existence of programs that GNU maintainers use. -*- Autoconf -*- + +# Copyright (C) 1997, 1999, 2000, 2001, 2003, 2004, 2005 +# Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 5 + +# AM_MISSING_PROG(NAME, PROGRAM) +# ------------------------------ +AC_DEFUN([AM_MISSING_PROG], +[AC_REQUIRE([AM_MISSING_HAS_RUN]) +$1=${$1-"${am_missing_run}$2"} +AC_SUBST($1)]) + + +# AM_MISSING_HAS_RUN +# ------------------ +# Define MISSING if not defined so far and test if it supports --run. +# If it does, set am_missing_run to use it, otherwise, to nothing. +AC_DEFUN([AM_MISSING_HAS_RUN], +[AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl +AC_REQUIRE_AUX_FILE([missing])dnl +test x"${MISSING+set}" = xset || MISSING="\${SHELL} $am_aux_dir/missing" +# Use eval to expand $SHELL +if eval "$MISSING --run true"; then + am_missing_run="$MISSING --run " +else + am_missing_run= + AC_MSG_WARN([`missing' script is too old or missing]) +fi +]) + +# Copyright (C) 2003, 2004, 2005, 2006 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_PROG_MKDIR_P +# --------------- +# Check for `mkdir -p'. +AC_DEFUN([AM_PROG_MKDIR_P], +[AC_PREREQ([2.60])dnl +AC_REQUIRE([AC_PROG_MKDIR_P])dnl +dnl Automake 1.8 to 1.9.6 used to define mkdir_p. We now use MKDIR_P, +dnl while keeping a definition of mkdir_p for backward compatibility. +dnl @MKDIR_P@ is magic: AC_OUTPUT adjusts its value for each Makefile. +dnl However we cannot define mkdir_p as $(MKDIR_P) for the sake of +dnl Makefile.ins that do not define MKDIR_P, so we do our own +dnl adjustment using top_builddir (which is defined more often than +dnl MKDIR_P). +AC_SUBST([mkdir_p], ["$MKDIR_P"])dnl +case $mkdir_p in + [[\\/$]]* | ?:[[\\/]]*) ;; + */*) mkdir_p="\$(top_builddir)/$mkdir_p" ;; +esac +]) + +# Helper functions for option handling. -*- Autoconf -*- + +# Copyright (C) 2001, 2002, 2003, 2005 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 3 + +# _AM_MANGLE_OPTION(NAME) +# ----------------------- +AC_DEFUN([_AM_MANGLE_OPTION], +[[_AM_OPTION_]m4_bpatsubst($1, [[^a-zA-Z0-9_]], [_])]) + +# _AM_SET_OPTION(NAME) +# ------------------------------ +# Set option NAME. Presently that only means defining a flag for this option. +AC_DEFUN([_AM_SET_OPTION], +[m4_define(_AM_MANGLE_OPTION([$1]), 1)]) + +# _AM_SET_OPTIONS(OPTIONS) +# ---------------------------------- +# OPTIONS is a space-separated list of Automake options. +AC_DEFUN([_AM_SET_OPTIONS], +[AC_FOREACH([_AM_Option], [$1], [_AM_SET_OPTION(_AM_Option)])]) + +# _AM_IF_OPTION(OPTION, IF-SET, [IF-NOT-SET]) +# ------------------------------------------- +# Execute IF-SET if OPTION is set, IF-NOT-SET otherwise. +AC_DEFUN([_AM_IF_OPTION], +[m4_ifset(_AM_MANGLE_OPTION([$1]), [$2], [$3])]) + +# Check to make sure that the build environment is sane. -*- Autoconf -*- + +# Copyright (C) 1996, 1997, 2000, 2001, 2003, 2005 +# Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 4 + +# AM_SANITY_CHECK +# --------------- +AC_DEFUN([AM_SANITY_CHECK], +[AC_MSG_CHECKING([whether build environment is sane]) +# Just in case +sleep 1 +echo timestamp > conftest.file +# Do `set' in a subshell so we don't clobber the current shell's +# arguments. Must try -L first in case configure is actually a +# symlink; some systems play weird games with the mod time of symlinks +# (eg FreeBSD returns the mod time of the symlink's containing +# directory). +if ( + set X `ls -Lt $srcdir/configure conftest.file 2> /dev/null` + if test "$[*]" = "X"; then + # -L didn't work. + set X `ls -t $srcdir/configure conftest.file` + fi + rm -f conftest.file + if test "$[*]" != "X $srcdir/configure conftest.file" \ + && test "$[*]" != "X conftest.file $srcdir/configure"; then + + # If neither matched, then we have a broken ls. This can happen + # if, for instance, CONFIG_SHELL is bash and it inherits a + # broken ls alias from the environment. This has actually + # happened. Such a system could not be considered "sane". + AC_MSG_ERROR([ls -t appears to fail. Make sure there is not a broken +alias in your environment]) + fi + + test "$[2]" = conftest.file + ) +then + # Ok. + : +else + AC_MSG_ERROR([newly created file is older than distributed files! +Check your system clock]) +fi +AC_MSG_RESULT(yes)]) + +# Copyright (C) 2001, 2003, 2005 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_PROG_INSTALL_STRIP +# --------------------- +# One issue with vendor `install' (even GNU) is that you can't +# specify the program used to strip binaries. This is especially +# annoying in cross-compiling environments, where the build's strip +# is unlikely to handle the host's binaries. +# Fortunately install-sh will honor a STRIPPROG variable, so we +# always use install-sh in `make install-strip', and initialize +# STRIPPROG with the value of the STRIP variable (set by the user). +AC_DEFUN([AM_PROG_INSTALL_STRIP], +[AC_REQUIRE([AM_PROG_INSTALL_SH])dnl +# Installed binaries are usually stripped using `strip' when the user +# run `make install-strip'. However `strip' might not be the right +# tool to use in cross-compilation environments, therefore Automake +# will honor the `STRIP' environment variable to overrule this program. +dnl Don't test for $cross_compiling = yes, because it might be `maybe'. +if test "$cross_compiling" != no; then + AC_CHECK_TOOL([STRIP], [strip], :) +fi +INSTALL_STRIP_PROGRAM="\$(install_sh) -c -s" +AC_SUBST([INSTALL_STRIP_PROGRAM])]) + +# Copyright (C) 2006 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# _AM_SUBST_NOTMAKE(VARIABLE) +# --------------------------- +# Prevent Automake from outputting VARIABLE = @VARIABLE@ in Makefile.in. +# This macro is traced by Automake. +AC_DEFUN([_AM_SUBST_NOTMAKE]) + +# Check how to create a tarball. -*- Autoconf -*- + +# Copyright (C) 2004, 2005 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# serial 2 + +# _AM_PROG_TAR(FORMAT) +# -------------------- +# Check how to create a tarball in format FORMAT. +# FORMAT should be one of `v7', `ustar', or `pax'. +# +# Substitute a variable $(am__tar) that is a command +# writing to stdout a FORMAT-tarball containing the directory +# $tardir. +# tardir=directory && $(am__tar) > result.tar +# +# Substitute a variable $(am__untar) that extract such +# a tarball read from stdin. +# $(am__untar) < result.tar +AC_DEFUN([_AM_PROG_TAR], +[# Always define AMTAR for backward compatibility. +AM_MISSING_PROG([AMTAR], [tar]) +m4_if([$1], [v7], + [am__tar='${AMTAR} chof - "$$tardir"'; am__untar='${AMTAR} xf -'], + [m4_case([$1], [ustar],, [pax],, + [m4_fatal([Unknown tar format])]) +AC_MSG_CHECKING([how to create a $1 tar archive]) +# Loop over all known methods to create a tar archive until one works. +_am_tools='gnutar m4_if([$1], [ustar], [plaintar]) pax cpio none' +_am_tools=${am_cv_prog_tar_$1-$_am_tools} +# Do not fold the above two line into one, because Tru64 sh and +# Solaris sh will not grok spaces in the rhs of `-'. +for _am_tool in $_am_tools +do + case $_am_tool in + gnutar) + for _am_tar in tar gnutar gtar; + do + AM_RUN_LOG([$_am_tar --version]) && break + done + am__tar="$_am_tar --format=m4_if([$1], [pax], [posix], [$1]) -chf - "'"$$tardir"' + am__tar_="$_am_tar --format=m4_if([$1], [pax], [posix], [$1]) -chf - "'"$tardir"' + am__untar="$_am_tar -xf -" + ;; + plaintar) + # Must skip GNU tar: if it does not support --format= it doesn't create + # ustar tarball either. + (tar --version) >/dev/null 2>&1 && continue + am__tar='tar chf - "$$tardir"' + am__tar_='tar chf - "$tardir"' + am__untar='tar xf -' + ;; + pax) + am__tar='pax -L -x $1 -w "$$tardir"' + am__tar_='pax -L -x $1 -w "$tardir"' + am__untar='pax -r' + ;; + cpio) + am__tar='find "$$tardir" -print | cpio -o -H $1 -L' + am__tar_='find "$tardir" -print | cpio -o -H $1 -L' + am__untar='cpio -i -H $1 -d' + ;; + none) + am__tar=false + am__tar_=false + am__untar=false + ;; + esac + + # If the value was cached, stop now. We just wanted to have am__tar + # and am__untar set. + test -n "${am_cv_prog_tar_$1}" && break + + # tar/untar a dummy directory, and stop if the command works + rm -rf conftest.dir + mkdir conftest.dir + echo GrepMe > conftest.dir/file + AM_RUN_LOG([tardir=conftest.dir && eval $am__tar_ >conftest.tar]) + rm -rf conftest.dir + if test -s conftest.tar; then + AM_RUN_LOG([$am__untar <conftest.tar]) + grep GrepMe conftest.dir/file >/dev/null 2>&1 && break + fi +done +rm -rf conftest.dir + +AC_CACHE_VAL([am_cv_prog_tar_$1], [am_cv_prog_tar_$1=$_am_tool]) +AC_MSG_RESULT([$am_cv_prog_tar_$1])]) +AC_SUBST([am__tar]) +AC_SUBST([am__untar]) +]) # _AM_PROG_TAR + diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..1bcdadd --- /dev/null +++ b/configure.ac @@ -0,0 +1,191 @@ +# Process this file with autoconf to produce a configure script. +AC_INIT([haserl],[0.9.25],[Nathan Angelacos <nangel@users.sourceforge.net>],[haserl]) +AM_INIT_AUTOMAKE([haserl],[$PACKAGE_VERSION]) + +# Checks for programs. +AC_PROG_CC +AC_PROG_INSTALL +AC_PROG_LN_S + +# Checks for libraries. + +# Checks for header files. +AC_HEADER_STDC +AC_CONFIG_HEADERS(src/config.h) +AC_CHECK_HEADERS([stdlib.h string.h unistd.h signal.h]) + +# Checks for typedefs, structures, and compiler characteristics. +AC_C_CONST +AC_TYPE_SIZE_T + +# Checks for library functions. +AC_FUNC_MEMCMP +AC_CHECK_FUNCS([memset putenv strcasecmp strdup strncasecmp strstr]) + +AC_DEFINE([_GNU_SOURCE], [], [Enable GNU Extensions]) + + +dnl ************************************************************** +AC_ARG_WITH(lua-headers, + [ --with-lua-headers=DIR lua include files location], + [LUA_HDR_DIR="$withval"] + [CFLAGS="$CFLAGS -I$withval"] +) + + +dnl Checks for lua + +luashell=false +luacshell=false +ac_report_have_lua=disabled +ac_report_bash_extensions=disabled + + +AC_ARG_WITH(lua, + AC_HELP_STRING([--with-lua[[=DIR]]], [use lua in DIR]), +[ case "$withval" in + no) ac_report_have_lua=disabled + ;; + *) AC_SEARCH_LIBS(dlopen, dl) + # ubuntu has lua5.1 rather than just lua + if pkg-config --exists lua5.1; then + LUALIB=lua5.1 + else + LUALIB=lua + fi + if test -z "$LUA_HDR_DIR"; then + CFLAGS="$CFLAGS `pkg-config $LUALIB --cflags`" + fi + LIBS="$LIBS -lm" + LDFLAGS="$LDFLAGS -Wl,-E -L$withval" + AC_DEFINE(USE_LUA, , [Enable Lua]) + + AC_CHECK_LIB($LUALIB, luaL_newstate, , [ + AC_MSG_ERROR([The Lua runtime library cannot be found!]) + ], $LIBS) + luashell=true + luacshell=true + ac_report_have_lua=enabled + ;; + esac ], [ + ac_report_have_lua=disabled +]) +AM_CONDITIONAL(USE_LUA, test x$ac_report_have_lua = xenabled) + +# If Lua is enabled, the user can choose between two different shells + +# shell: ordinary Haserl code with embedded Lua +AC_ARG_ENABLE(luashell, + AC_HELP_STRING([--enable-luashell], [Includes the standard Lua shell - Haserl with embedded Lua (default is yes if Lua's enabled)]), +[case "${enableval}" in + yes) luashell=true ;; + no) luashell=false ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-luashell) ;; +esac], []) +AM_CONDITIONAL(INCLUDE_LUASHELL, test x$luashell = xtrue) +if test x$luashell = xtrue; then + AC_DEFINE(INCLUDE_LUASHELL, , [Include ordinary Lua shell]) +fi + +# shell: precompiled Haserl code - compiled Lua code +AC_ARG_ENABLE(luacshell, + AC_HELP_STRING([--enable-luacshell], [Includes the compiled Lua shell - precompiled Haserl/Lua (default is yes if Lua's enabled)]), +[case "${enableval}" in + yes) luacshell=true ;; + no) luacshell=false ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-luacshell) ;; +esac], []) +AM_CONDITIONAL(INCLUDE_LUACSHELL, test x$luacshell = xtrue) +if test x$luacshell = xtrue; then + AC_DEFINE(INCLUDE_LUACSHELL, , [Include shell for precompiled Haserl/Lua]) +fi + +if test \(x$luashell = xtrue -o x$luacshell = xtrue\) -a x$ac_report_have_lua = xdisabled; then + AC_MSG_ERROR([Lua is not enabled so you cannot build a Lua shell.]) +fi + +# the Linux shell is always available + +AC_ARG_ENABLE(bashshell, + AC_HELP_STRING([--enable-bashshell], [Includes the Bash or another Linux shell (default is yes)]), +[case "${enableval}" in + yes) bashshell=true ;; + no) bashshell=false ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-bashshell) ;; +esac], [bashshell=true]) +AM_CONDITIONAL(INCLUDE_BASHSHELL, test x$bashshell = xtrue) +if test x$bashshell = xtrue; then + AC_DEFINE(INCLUDE_BASHSHELL, , [Include Bash/Linux shell]) +fi + +# set additional macros + +if test x$luashell = xfalse -a x$bashshell = xfalse; then + if test x$luacshell = xtrue; then + AC_DEFINE(JUST_LUACSHELL, , [Include just the compiled Lua shell]) + else + AC_MSG_ERROR([All shells disabled.]) + fi +fi + +if test x$ac_report_have_lua = xenabled -a x$luashell = xfalse -a x$luacshell = xfalse; then + AC_MSG_ERROR([Cannot enable Lua if no Lua shell is included.]) +fi + + +# Do we include scott's bash extensions +AC_ARG_ENABLE(bash-extensions, + AC_HELP_STRING([--enable-bash-extensions], [Includes bash extensions to the haserl script language (default is no)]), +[case "${enableval}" in + yes) bashextensions=true + ac_report_bash_extensions=enabled + ;; + no) bashextensions=false + ac_report_bash_extensions=disabled + ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-bash-extensions) ;; +esac], [bashextensions=false]) +AM_CONDITIONAL(BASHEXTENSIONS, test x$bashextensions = xtrue) +if test x$bashextensions = xtrue; then + AC_DEFINE(BASHEXTENSIONS, , [Include bash extensions]) +fi + + +# What subshell should we use +AC_ARG_ENABLE(subshell, + [ --enable-subshell=cmd - enable the subshell to use (default is /bin/sh)]) + +# Handle "--disable-subshell" +case $enable_subshell in + no ) enable_subshell= + ;; + "" ) enable_subshell="/bin/sh" + ;; + esac + +AC_DEFINE_UNQUOTED( [SUBSHELL_CMD], "$enable_subshell", + [the subshell to start up]) + +CFLAGS="$CFLAGS -Wall" + +AC_CONFIG_FILES([Makefile + src/Makefile + doc/Makefile + tests/Makefile + ]) + + + + +AC_OUTPUT + +dnl report configuration +AC_MSG_RESULT([ +** Configuration summary for $PACKAGE $VERSION: + + Building haserl with with lua $ac_report_have_lua + Building haserl with with bash-extensions $ac_report_bash_extensions + libs: $LIBS +]) + + diff --git a/doc/Makefile.am b/doc/Makefile.am new file mode 100644 index 0000000..9127ef0 --- /dev/null +++ b/doc/Makefile.am @@ -0,0 +1,9 @@ +PACKAGE = @PACKAGE_TARNAME@ +EXTRA_DIST = haserl.1 + +docdir=$(datadir)/$(PACKAGE) +datarootdir=@datarootdir@ + +man_MANS = haserl.1 + + diff --git a/doc/haserl.1 b/doc/haserl.1 new file mode 100644 index 0000000..c683405 --- /dev/null +++ b/doc/haserl.1 @@ -0,0 +1,647 @@ +.\" Process with groff -man -Tascii haserl.1 +.TH haserl 1 "July 2007" +.SH NAME +haserl \- A cgi scripting program for embedded environments +.SH SYNOPSIS +.BI "#!/usr/bin/haserl [--shell=" pathspec "] [--upload-dir=" dirspec "] [--upload-handler=" handler "] [--upload-limit=" limit "] [--accept-all] [--accept-none] [--silent] [--debug]" + +[ text ] [ <% shell script %> ] [ text ] ... + +.SH DESCRIPTION +Haserl is a small cgi wrapper that allows "PHP" style cgi programming, +but uses a UNIX bash-like shell or Lua as the programming language. It is +very small, so it can be used in embedded environments, or where +something like PHP is too big. + +It combines three features into a small cgi engine: + +.IP +It parses POST and GET requests, placing form-elements as +name=value +pairs into the environment for the CGI script to use. This is somewhat like +the +.IR uncgi " wrapper." +.IP +It opens a shell, and translates all text into printable statements. +All text within <% ... %> constructs are passed verbatim to the shell. +This is somewhat like +.RI writing " PHP " "scripts." +.IP +It can optionally be installed to drop its permissions to the owner of the +script, giving +it some of the security features of +.IR suexec " or " cgiwrapper . +.SH OPTIONS SUMMARY + +This is a summary of the command-line options. Please see the +.B OPTIONS +section under the long option name for a complete description. + +-a --accept-all +.br +-n --accept-none +.br +-d --debug +.br +-s, --shell +.br +-S, --silent +.br +-U, --upload-dir +.br +-u, --upload-limit +.br +-H, --upload-handler +.br + +.SH OPTIONS + +.TP +.BI --accept-all +The program normally accepts POST data only when the REQUEST_METHOD is POST and only accepts +data on the URL data when the REQUEST_METHOD is GET. This option allows both POST and +URL data to be accepted regardless of the REQUEST_METHOD. When this option is set, +the REQUEST_METHOD takes precedence (e.g. if the method is POST, FORM_variables are taken +from COOKIE data, GET data, and POST data, in that order. If the method is GET, FORM_variables +are taken from COOKIE data, POST data, and GET data.) The default is not to accept all +input methods - just the COOKIE data and the REQUEST_METHOD. + +.TP +.BI --accept-none +If given, haserl will not parse standard input as http content before +processing the script. This is useful if calling a haserl script from +another haserl script. + +.TP +.BI --debug +Instead of executing the script, print out the script that would be executed. If the environment variable 'REQUEST_METHOD' is set, the data is sent with the plain/text content type. Otherwise, the shell script is printed verbatim. + +.TP +.BI --shell= "pathspec " +Specify an alternative bash-like shell to use. Defaults to "/bin/sh" + +To include shell parameters do not use the --shell=/bin/sh format. Instead, use the alternative format without the "=", as in --shell "/bin/bash --norc". Be sure to quote the option string to protect any special characters. + +If compiled with Lua libraries, then the string "lua" is used to use an integrated Lua vm. This string is case sensitive. Example: +.BI --shell= lua + +An alternative is "luac". This causes the haserl and lua parsers to be disabled, and the +script is assumed to be a precompiled lua chunk. See +.B LUAC +below for more information. + +.TP +.BI --silent +Haserl normally prints an informational message on error conditions. This +suppresses the error message, so that the use of haserl is not advertised. + +.TP +.BI --upload-dir= "dirspec " +Defaults to "/tmp". All uploaded files are created with temporary filename in this +directory +.BR FORM_xxx " contains the name of the temporary file." FORM_xxx_name +contains the original name of the file, as specified by the client. + +.TP +.BI --upload-handler= "pathspec " +When specified, file uploads are handled by this handler, rather than written +to temporary files. The full pathspec must be given (the PATH is not +searched), and the upload-handler is given one command-line parameter: +The name of the FIFO on which the upload file +will be sent. In addition, the handler may receive 3 environment variables: +.BR CONTENT_TYPE ", " FILENAME ", and " NAME . +These reflect the MIME content-disposition headers for the content. Haserl +will fork the handler for each file uploaded, and will send the contents +of the upload file to the specified FIFO. Haserl will then block until +the handler terminates. This method is for experts only. + +.TP +.BI --upload-limit= "limit " +Allow a mime-encoded file up to +.I limit KB +to be uploaded. The default is +.I 0KB +(no uploads allowed). +Note that mime-encoding adds 33% to the size of the data. + +.SH OVERVIEW OF OPERATION + +In general, the web server sets up several environment variables, and then uses +.I fork +or another method to run the CGI script. If the script uses the +.I haserl +interpreter, the following happens: + +.IP +If +.I haserl +is installed suid root, then uid/gid is set to the owner of the script. + +The environment is scanned for +.IR HTTP_COOKIE , +which may have been set by the web server. If it exists, the parsed contents +are placed in the local environment. + +The environment is scanned for +.IR REQUEST_METHOD , +which was set by the web server. Based on the request method, standard input +is read and parsed. The parsed contents are placed in the local environment. + +The script is tokenized, parsing +.I haserl +code blocks from raw text. Raw text is converted into "echo" statements, and +then all tokens are sent to the sub-shell. + +.I haserl +forks and a sub-shell (typically +.IR /bin/sh ) +is started. + +All tokens are sent to the STDIN of the sub-shell, with a trailing +.B exit +command. + +When the sub-shell terminates, the +.I haserl +interpreter performs final cleanup and then terminates. + + +.SH CLIENT SIDE INPUT +The +.I haserl +interpreter will decode data sent via the HTTP_COOKIE environment variable, and the GET or POST method from the client, +and store them as environment variables that can be accessed by haserl. +The name of the variable follows the name given in the source, except that a prefix ( +.IR FORM_ ) +is prepended. For example, if the client sends "foo=bar", the environment variable is +.BR FORM_foo = bar . + +For the HTTP_COOKIE method, variables are also stored with the prefix ( +.IR COOKIE_ ) +added. For example, if HTTP_COOKIE includes "foo=bar", the environment variable is +.BR COOKIE_foo = bar . + +For the GET method, data sent in the form %xx is translated into the characters +they represent, and variables are also stored with the prefix ( +.IR GET_ ) +added. For example, if QUERY_STRING includes "foo=bar", the environment variable is +.BR GET_foo = bar . + +For the POST method, variables are also stored with the prefix ( +.IR POST_ ) +added. For example, if the post stream includes "foo=bar", the environment variable is +.BR POST_foo = bar . + +Also, for the POST method, if the data is sent using +.I "multipart/form-data" +encoding, the data is automatically decoded. This is typically used when +files are uploaded from a web client using <input type=file>. + +.TP +.B NOTE +When a file is uploaded to the web server, it is stored in the +.I upload-target +directory. +.RI The FORM_variable= "variable contains the name of the file uploaded" +(as specified by the client.) The +.I FORM_variable_name= +variable contains the name of the file in +.I upload-target +that contains the uploaded content. To prevent malicious clients from +filling up +.I upload-target +on your web server, file uploads are only allowed when the +.I --upload-limit +option is used to specify how large a file can be uploaded. Haserl automatically +deletes the temporary file when the script is finished. To keep the file, move it +or rename it somewhere in the script. + +.P +If the client sends data +.I both +by POST and GET methods, then +.I haserl +will parse only the data that corresponds with the +.I REQUEST_METHOD +variable set by the web server, unless the +.I accept-all +option has been set. For example, a form called via POST method, but having a +URI of some.cgi?foo=bar&otherdata=something will have the POST data parsed, and the +.IR foo " and " otherdata +variables are ignored. + +.P +If the web server defines a +.I HTTP_COOKIE +environment variable, the cookie data is parsed. Cookie data is parsed +.I before +the GET or POST data, so in the event of two variables of the same name, the +GET or POST data overwrites the cookie information. + +.P +When multiple instances of the same variable are sent from different sources, the FORM_variable will be set according to the order in which variables are processed. HTTP_COOKIE is always processed first, followed by the REQUEST_METHOD. If the accept-all option has been set, then HTTP_COOKIE is processed first, followed by the method not specified by REQUEST_METHOD, followed by the REQUEST_METHOD. The last instance of the variable will be used to set FORM_variable. Note that the variables are also separately creates as COOKIE_variable, GET_variable and POST_variable. This allows the use of overlapping names from each source. + +.P +When multiple instances of the same variable are sent from the same source, +only the last one is saved. To keep all copies (for multi-selects, for +instance), add "[]" to the end of the +variable name. All results will be returned, separated by newlines. For example, +host=Enoch&host=Esther&host=Joshua results in "FORM_host=Joshua". +host[]=Enoch&host[]Esther&host[]=Joshua results in "FORM_host=Enoch\\nEsther\\nJoshua" + +.SH LANGUAGE +The following language structures are recognized by +.IR haserl . + +.TP +.B "RUN" +.nf +<% [shell script] %> +.sp +.fi +Anything enclosed by <% %> tags is sent to the sub-shell for execution. The +text is sent verbatim. + +.TP +.B "INCLUDE" +.nf +<%in pathspec %> +.sp +.fi +Include another file verbatim in this script. The file is included when the script is +initially parsed. + +.TP +.B "EVAL" +.nf +<%= expression %> +.sp +.fi +print the shell expression. Syntactic sugar for "echo expr". + +.TP +.B "COMMENT" +.nf +<%# comment %> +.sp +.fi +Comment block. Anything in a comment block is not parsed. Comments can be nested and can contain +other haserl elements. + +.SH EXAMPLES +.TP +.B WARNING +The examples below are simplified to show how to use +.IR haserl . +You should be familiar with basic web scripting security before using +.I haserl +(or any scripting language) in a production environment. + +.TP +.B Simple Command +.nf +#!/usr/local/bin/haserl +content-type: text/plain +.sp +<%# This is a sample "env" script %> +<% env %> +.fi + +Prints the results of the +.I env +command as a mime-type "text/plain" document. This is the +.I haserl +version of the common +.I printenv +cgi. + +.TP +.B Looping with dynamic output +.nf +#!/usr/local/bin/haserl +Content-type: text/html +.sp +<html> +<body> +<table border=1><tr> +<% for a in Red Blue Yellow Cyan; do %> + <td bgcolor="<% echo -n "$a" %>"><% echo -n "$a" %></td> + <% done %> +</tr></table> +</body> +</html> +.fi + +Sends a mime-type "text/html" document to the client, with an html table +of with elements labeled with the background color. + +.TP +.B Use Shell defined functions. +.nf +#!/usr/local/bin/haserl +content-type: text/html +.sp +<% # define a user function + table_element() { + echo "<td bgcolor=\\"$1\\">$1</td>" + } + %> +<html> +<body> +<table border=1><tr> +<% for a in Red Blue Yellow Cyan; do %> + <% table_element $a %> + <% done %> +</tr></table> +</body> +</html> +.fi + +Same as above, but uses a shell function instead of embedded html. + +.TP +.B Self Referencing CGI with a form +.nf +#!/usr/local/bin/haserl +content-type: text/html +.sp +<html><body> +<h1>Sample Form</h1> +<form action="<% echo -n $SCRIPT_NAME %>" method="GET"> +<% # Do some basic validation of FORM_textfield + # To prevent common web attacks + FORM_textfield=$( echo "$FORM_textfield" | sed "s/[^A-Za-z0-9 ]//g" ) + %> +<input type=text name=textfield + Value="<% echo -n "$FORM_textfield" | tr a-z A-Z %>" cols=20> +<input type=submit value=GO> +</form></html> +</body> +.fi + +Prints a form. If the client enters text in the form, the CGI is reloaded (defined by +.IR $SCRIPT_NAME ) +and the textfield is sanitized to prevent web attacks, then the form is redisplayed with the text the user entered. The text is uppercased. + +.TP +.B Uploading a File +.nf +#!/usr/local/bin/haserl --upload-limit=4096 --upload-target=/tmp +content-type: text/html +.sp +<html><body> +<form action="<% echo -n $SCRIPT_NAME %>" method=POST enctype="multipart/form-data" > +<input type=file name=uploadfile> +<input type=submit value=GO> +<br> +<% if test -n "$FORM_uploadfile"; then %> + <p> + You uploaded a file named <b><% echo -n $FORM_uploadfile_name %></b>, and it was + temporarily stored on the server as <i><% echo $FORM_uploadfile %></i>. The + file was <% cat $FORM_uploadfile | wc -c %> bytes long.</p> + <% rm -f $FORM_uploadfile %><p>Don't worry, the file has just been deleted + from the web server.</p> +<% else %> + You haven't uploaded a file yet. +<% fi %> +</form> +</body></html> +.fi + +Displays a form that allows for file uploading. This is accomplished by using the +.B --upload-limit +and by setting the form +.I enctype +.RI "to " multipart/form-data. +If the client sends a file, then some information regarding the file is printed, and then deleted. Otherwise, the form states that the client has not uploaded a file. + + + +.SH ENVIRONMENT +In addition to the environment variables inherited from the web server, the following environment variables are always defined at startup: + +.IP HASERLVER +.I haserl +version - an informational tag. +.IP SESSIONID +A hexadecimal tag that is unique for the life of the CGI (it is generated when the cgi starts; and does not change until another POST or GET query is generated.) +.IP HASERL_ACCEPT_ALL +.RI "If the " --accept-all " flag was set, " -1 ", otherwise " 0 "." +.IP HASERL_SHELL +The name of the shell haserl started to run sub-shell commands in. +.IP HASERL_UPLOAD_DIR +The directory haserl will use to store uploaded files. +.IP HASERL_UPLOAD_LIMIT +The number of KB that are allowed to be sent from the client to the server. + +.P +These variables can be modified or overwritten within the script, although the ones starting with +"HASERL_" are informational only, and do not affect the running script. + +.SH SAFETY FEATURES +There is much literature regarding the dangers of using shell to program CGI scripts. +.IR haserl " contains " some +protections to mitigate this risk. + +.TP +.B Environment Variables +The code to populate the environment variables is outside the scope of the sub-shell. It parses on the characters ? and &, so it is harder for a client to do "injection" attacks. As an example, +.I foo.cgi?a=test;cat /etc/passwd +could result in a variable being assigned the value +.B test +and then the results of running +.I cat /etc/passwd +being sent to the client. +.I Haserl +will assign the variable the complete value: +.B test;cat /etc/passwd + +It is safe to use this "dangerous" variable in shell scripts by enclosing it in quotes; although validation should be done on all input fields. + +.TP +.B Privilege Dropping +If installed as a suid script, +.I haserl +will set its uid/gid to that of the owner of the script. This can be used to have a set of CGI scripts that have various privilege. If the +.I haserl +binary is not installed suid, then the CGI scripts will run with the uid/gid of the web server. + +.TP +.B Reject command line parameters given on the URL +If the URL does not contain an unencoded "=", then the CGI spec states the options are to be +used as command-line parameters to the program. For instance, according to the CGI spec: +.I http://192.168.0.1/test.cgi?--upload-limit%3d2000&foo%3dbar +Should set the upload-limit to 2000KB in addition to setting "Foo=bar". +To protect against clients enabling their own uploads, +.I haserl +rejects any command-line options beyond argv[2]. If invoked as a #! +script, the interpreter is argv[0], all command-line options listed in the #! line are +combined into argv[1], and the script name is argv[2]. + +.SH LUA + +If compiled with lua support, +.B --shell=lua +will enable lua as the script language instead of bash shell. The environment variables +(SCRIPT_NAME, SERVER_NAME, etc) are placed in the ENV table, and the form variables are +placed in the FORM table. For example, the self-referencing form above can be written like this: + +.RS +.nf +#!/usr/local/bin/haserl --shell=lua +content-type: text/html +.sp +<html><body> +<h1>Sample Form</h1> +<form action="<% io.write(ENV["SCRIPT_NAME"]) %>" method="GET"> +<% # Do some basic validation of FORM_textfield + # To prevent common web attacks + FORM.textfield=string.gsub(FORM.textfield, "[^%a%d]", "") + %> +<input type=text name=textfield + Value="<% io.write (string.upper(FORM.textfield)) %>" cols=20> +<input type=submit value=GO> +</form></html> +</body> +.fi +.RE + +The <%= operator is syntactic sugar for +.I io.write (tostring( ... )) +So, for example, the Value= line above could be written: +.B Value="<%= string.upper(FORM.textfield) %>" cols=20> + +haserl lua scripts can use the function +.BI haserl.loadfile( filename ) +to process a target script as a haserl (lua) script. The function returns a type of "function". + +For example, + +bar.lsp +.RS +.nf +<% io.write ("Hello World" ) %> +.sp +Your message is <%= gvar %> +.sp +-- End of Include file -- +.fi +.RE + +foo.haserl +.RS +.nf +#!/usr/local/bin/haserl --shell=lua +<% m = haserl.loadfile("bar.lsp") + gvar = "Run as m()" + m() + + gvar = "Load and run in one step" + haserl.loadfile("bar.lsp")() +%> +.fi +.RE + +Running +.I foo +will produce: + +.RS +.nf +Hello World +Your message is Run as m() +-- End of Include file -- +Hello World +Your message is Load and run in one step +-- End of Include file -- +.fi +.TE + +This function makes it possible to have nested haserl server pages - page snippets that are +processed by the haserl tokenizer. + +.SH LUAC + +The +.I luac +"shell" is a precompiled lua chunk, so interactive editing and testing of scripts is +not possible. However, haserl can be compiled with luac support only, and this allows +lua support even in a small memory environment. All haserl lua features listed above +are still available. (If luac is the only shell built into haserl, the haserl.loadfile is +disabled, as the haserl parser is not compiled in.) + +Here is an example of a trivial script, converted into a luac cgi script: + +Given the file test.lua: +.RS +.nf +print ("Content-Type: text/plain\n\n") +print ("Your UUID for this run is: " .. ENV.SESSIONID) +.fi +.RE + +It can be compiled with luac: +.RS +luac -o test.luac -s test.lua +.RE + +And then the haserl header added to it: +.RS +echo '#!/usr/bin/haserl --shell=luac' | cat - test.luac >luac.cgi +.RE + +Alternatively, it is possible to develop an entire website using the standard lua shell, +and then have haserl itself preprocess the scripts for the luac compiler as part of a build +process. To do this, use --shell=lua, and develop the website. When ready to build +the runtime environment, add the --debug line to your lua scripts, and run them outputting +the results to .lua source files. For example: + +Given the haserl script test.cgi: +.RS +.nf +#!/usr/bin/haserl --shell=lua --debug +Content-Type: text/plain + +Your UUID for this run is <%= ENV.SESSIONID %> +.fi +.RE + +Precompile, compile, and add the haserl luac header: +.RS +.nf +./test.cgi > test.lua +luac -s -o test.luac test.lua +echo '#!/usr/bin/haserl --shell=luac' | cat - test.luac >luac.cgi +.fi +.RS + +.SH BUGS +Old versions of haserl used <? ?> as token markers, instead of <% %>. Haserl +will fall back to using <? ?> +.I if <% does not appear anywhere in the script. + +.SH NAME +The name "haserl" comes from the Bavarian word for "bunny." At first glance it +may be small and cute, but +.I haserl +is more like the bunny from +.IR "Monty Python & The Holy Grail" . +In the words of Tim the Wizard, +.I That's the most foul, cruel & bad-tempered rodent you ever set eyes on! + +Haserl can be thought of the cgi equivalent to +.IR netcat . +Both are small, powerful, and have very little in the way of extra features. Like +.IR netcat ", " haserl +attempts to do its job with the least amount of extra "fluff". + + +.SH AUTHOR +Nathan Angelacos <nangel@users.sourceforge.net> + +.SH SEE ALSO + +.BR php (http://www.php.net) +.BR uncgi (http://www.midwinter.com/~koreth/uncgi.html) +.BR cgiwrapper (http://cgiwrapper.sourceforge.net) + diff --git a/haserl.doxy b/haserl.doxy new file mode 100644 index 0000000..77596f7 --- /dev/null +++ b/haserl.doxy @@ -0,0 +1,1357 @@ +# Doxyfile 1.5.5 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# http://www.gnu.org/software/libiconv for the list of possible encodings. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# by quotes) that should identify the project. + +PROJECT_NAME = haserl + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = /var/www/haserl + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, +# Croatian, Czech, Danish, Dutch, Farsi, Finnish, French, German, Greek, +# Hungarian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, Polish, +# Portuguese, Romanian, Russian, Serbian, Slovak, Slovene, Spanish, Swedish, +# and Ukrainian. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = YES + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful is your file systems +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like regular Qt-style comments +# (thus requiring an explicit @brief command for a brief description.) + +JAVADOC_AUTOBRIEF = NO + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will +# interpret the first line (until the first dot) of a Qt-style +# comment as the brief description. If set to NO, the comments +# will behave just like regular Qt-style comments (thus requiring +# an explicit \brief command for a brief description.) + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the DETAILS_AT_TOP tag is set to YES then Doxygen +# will output the detailed description near the top, like JavaDoc. +# If set to NO, the detailed description appears after the member +# documentation. + +DETAILS_AT_TOP = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 8 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for +# Java. For instance, namespaces will be presented as packages, qualified +# scopes will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources only. Doxygen will then generate output that is more tailored for +# Fortran. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for +# VHDL. + +OPTIMIZE_OUTPUT_VHDL = NO + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. +# Doxygen will parse them like normal C++ but will assume all classes use public +# instead of private inheritance when no explicit protection keyword is present. + +SIP_SUPPORT = NO + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum +# is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically +# be useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. + +TYPEDEF_HIDES_STRUCT = NO + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = YES + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base +# name of the file that contains the anonymous namespace. By default +# anonymous namespace are hidden. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the +# hierarchy of group names into alphabetical order. If set to NO (the default) +# the group names will appear in their defined order. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or define consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and defines in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +# If the sources in your project are distributed over multiple directories +# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy +# in the documentation. The default is NO. + +SHOW_DIRECTORIES = NO + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command <command> <input-file>, where <command> is the value of +# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be abled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = ./src + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is +# also the default input encoding. Doxygen uses libiconv (or the iconv built +# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for +# the list of possible encodings. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx +# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90 + +FILE_PATTERNS = *.c \ + *.h + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used select whether or not files or +# directories that are symbolic links (a Unix filesystem feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command <filter> <input-file>, where <filter> +# is the value of the INPUT_FILTER tag, and <input-file> is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER +# is applied to all files. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES (the default) +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES (the default) +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. Otherwise they will link to the documentstion. + +REFERENCES_LINK_SOURCE = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = NO + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# stylesheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_DOCSET tag is set to YES, additional index files +# will be generated that can be used as input for Apple's Xcode 3 +# integrated development environment, introduced with OSX 10.5 (Leopard). +# To create a documentation set, doxygen will generate a Makefile in the +# HTML output directory. Running make will produce the docset in that +# directory and running "make install" will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find +# it at startup. + +GENERATE_DOCSET = NO + +# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the +# feed. A documentation feed provides an umbrella under which multiple +# documentation sets from a single provider (such as a company or product suite) +# can be grouped. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that +# should uniquely identify the documentation set bundle. This should be a +# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen +# will append .docset to the name. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. For this to work a browser that supports +# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox +# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). + +HTML_DYNAMIC_SECTIONS = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = NO + +# This tag can be used to set the number of enum values (range [1..20]) +# that doxygen will group on one line in the generated HTML documentation. + +ENUM_VALUES_PER_LINE = 4 + +# If the GENERATE_TREEVIEW tag is set to YES, a side panel will be +# generated containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, +# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are +# probably better off using the HTML help feature. + +GENERATE_TREEVIEW = NO + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, a4wide, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. This is useful +# if you want to understand what is going on. On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# in the INCLUDE_PATH (see below) will be search if a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all function-like macros that are alone +# on a line, have an all uppercase name, and do not end with a semicolon. Such +# function macros are typically used for boiler-plate code, and will confuse +# the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. +# Optionally an initial location of the external documentation +# can be added for each tagfile. The format of a tag file without +# this location is as follows: +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths or +# URLs. If a location is present for each tag, the installdox tool +# does not have to be run to correct the links. +# Note that each tag file must have a unique name +# (where the name does NOT include the path) +# If a tag file is not located in the directory in which doxygen +# is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option is superseded by the HAVE_DOT option below. This is only a +# fallback. It is recommended to install and use dot, since it yields more +# powerful graphs. + +CLASS_DIAGRAMS = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see +# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = NO + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT options are set to YES then +# doxygen will generate a call dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable call graphs +# for selected functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then +# doxygen will generate a caller dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable caller +# graphs for selected functions only using the \callergraph command. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are png, jpg, or gif +# If left blank png will be used. + +DOT_IMAGE_FORMAT = png + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The MAX_DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the +# number of direct children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is enabled by default, which results in a transparent +# background. Warning: Depending on the platform used, enabling this option +# may lead to badly anti-aliased labels on the edges of a graph (i.e. they +# become hard to read). + +DOT_TRANSPARENT = YES + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to the search engine +#--------------------------------------------------------------------------- + +# The SEARCHENGINE tag specifies whether or not a search engine should be +# used. If set to NO the values of all tags below this one will be ignored. + +SEARCHENGINE = NO diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..5d38878 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,66 @@ +PACKAGE=@PACKAGE_TARNAME@ + +EXTRA_DIST = haserl_lualib.lua haserl_lualib.inc lua2c.c + +datarootdir=@datarootdir@ + +if USE_LUA +LUASOURCE = haserl_lualib.inc h_lua_common.c h_lua_common.h +if INCLUDE_LUASHELL +LUASOURCE += h_lua.c h_lua.h +endif +if INCLUDE_LUACSHELL +LUASOURCE += h_luac.c h_luac.h +endif +endif + +if INCLUDE_BASHSHELL +BASHSOURCE = h_bash.c h_bash.h +endif + +if USE_LUA + +haserl_lualib.inc : haserl_lualib.lua + @echo '-----------------------------------------------------' + @echo 'Whoops. haserl_lualib.inc is old. You will need' + @echo 'to compile lua2c by hand, or help the maintainer' + @echo 'get automake to do it for you.' + @echo '' + @echo 'For now, to compile lua2c:' + @echo '' + @echo 'gcc -I<luaheaderdir> -Wl,-E -L<lualibdir> -o lua2c lua2c.c -llua -ldl -lm' + @echo '' + @echo '' + @echo 'Then follow the instructions in lua2c.c to create a' + @echo 'new haserl_lualib.inc' + @echo '' + @echo 'Sorry.' + @echo '-----------------------------------------------------' + @exit 1 + +h_lua_common.c : haserl_lualib.inc + +endif + +bin_PROGRAMS = haserl + +haserl_SOURCES = common.c common.h sliding_buffer.c sliding_buffer.h \ + h_error.c h_error.h h_script.c h_script.h rfc2388.c rfc2388.h \ + $(BASHSOURCE) $(LUASOURCE) haserl.c haserl.h + +install-strip: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install + + +#--------- Unit Tests ---------------------- +#TESTS = utest_common + +#check_PROGRAMS = utest_common + +#utest_common: utest_common.c + +#--------- End Unit Tests ------------------- + diff --git a/src/common.c b/src/common.c new file mode 100644 index 0000000..2ee4afb --- /dev/null +++ b/src/common.c @@ -0,0 +1,370 @@ +/* + * -------------------------------------------------------------------------- + * Common library functions for the haserl suite + * Copyright (c) 2005-2007 Nathan Angelacos (nangel@users.sourceforge.net) + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 2, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * ------------------------------------------------------------------------- + */ + +#if HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> + +#include "common.h" +#include "h_error.h" + + +/* we define this here, but should use the header files instead. */ +void *xrealloc (void *buf, size_t size); + + +/* + * split a string into an argv[] array, and return the number of elements. + * Warning: Overwrites instr with nulls (to make ASCIIZ strings) and mallocs + * space for the argv array. The argv array will point to the offsets in + * instr where the elements occur. The calling function must free the argv + * array, and should do so before freeing instr memory. + * + * If comment points to a non-null string, then any character in the string + * will mark the beginning of a comment until the end-of-line: + * + * comment="#;" + * foo bar # This is a comment + * foo baz ; This is a comment as well + * + * Example of use: + * + * int argc, count; argv_t *argv; char string[2000]; + * + * strcpy (string, "This\\ string will be \"separated into\" '6' elements.", ""); + * argc = argc_argv (string, &argv); + * for (count = 0; count < argc; count++) { + * printf ("%03d: %s\n", count, argv[count].string); + * } + * free (argv); + * + */ + + +int +argc_argv (char *instr, argv_t ** argv, char *commentstr) +{ + char quote = '\0'; + int arg_count = 0; + enum state_t + { + WHITESPACE, WORDSPACE, TOKENSTART + } state = WHITESPACE; + argv_t *argv_array = NULL; + int argc_slots = 0; + size_t len, pos; + + len = strlen (instr); + pos = 0; + + while (pos < len) + { + // printf ("%3d of %3d: %s\n", pos, len, instr); + + /* Comments are really, really special */ + + if ((state == WHITESPACE) && (strchr (commentstr, *instr))) + { + while ((*instr != '\n') && (*instr != '\0')) + { + instr++; + pos++; + } + } + + switch (*instr) + { + + /* quoting */ + case '"': + case '\'': + /* Begin quoting */ + if (state == WHITESPACE) + { + quote = *instr; + state = TOKENSTART; + if (*(instr + 1) == quote) + { /* special case for NULL quote */ + quote = '\0'; + *instr = '\0'; + argv_array[arg_count].quoted = -1; + } + else + { + instr++; + pos++; + } + } + else + { /* WORDSPACE, so quotes end or quotes within quotes */ + + /* Is it the same kind of quote? */ + if (*instr == quote) + { + argv_array[arg_count - 1].quoted = -1; + quote = '\0'; + *instr = '\0'; + state = WHITESPACE; + } + } + break; + + /* backslash - if escaping a quote within a quote */ + case '\\': + if ((quote) && (*(instr + 1) == quote)) + { + memmove (instr, instr + 1, strlen (instr)); + len--; + } + /* otherwise, its just a normal character */ + else + { + if (state == WHITESPACE) + { + state = TOKENSTART; + } + } + break; + + + /* whitepsace */ + case ' ': + case '\t': + case '\r': + case '\n': + if ((state == WORDSPACE) && (quote == '\0')) + { + state = WHITESPACE; + *instr = '\0'; + } + break; + + case '\0': + break; + + default: + if (state == WHITESPACE) + { + state = TOKENSTART; + } + + } /* end switch */ + + if (state == TOKENSTART) + { + arg_count++; + if (arg_count > argc_slots) + { + argc_slots += ALLOC_CHUNK; + argv_array = + (argv_t *) xrealloc (argv_array, + sizeof (argv_t) * (argc_slots + + ALLOC_CHUNK)); + } + + if (argv_array == NULL) + { + return (-1); + } + argv_array[arg_count - 1].string = instr; + argv_array[arg_count].quoted = 0; + state = WORDSPACE; + } + + instr++; + pos++; + } + + argv_array[arg_count].string = NULL; + *argv = argv_array; + return (arg_count); +} + +/* Expandable Buffer is a reimplementation based on buffer.c in GCC + originally by Per Bother */ + +void +buffer_init (buffer_t * buf) +{ + buf->data = NULL; + buf->ptr = NULL; + buf->limit = NULL; +} + +void +buffer_destroy (buffer_t * buf) +{ + if (buf->data) + { + free (buf->data); + } + buffer_init (buf); +} + +/* don't reallocate - just forget about the current contents */ +void +buffer_reset (buffer_t * buf) +{ + if (buf->data) + { + buf->ptr = buf->data; + } + else + { + buf->ptr = NULL; + } +} + + +void +buffer_add (buffer_t * buf, const void *data, unsigned long size) +{ + unsigned long newsize; + unsigned long index; + + /* if we need to grow the buffer, do so now */ + if ((buf->ptr + size) >= buf->limit) + { + index = (buf->limit - buf->data); + newsize = index; + while (newsize <= index + size) + { + newsize += 1024; + } + index = buf->ptr - buf->data; + buf->data = realloc (buf->data, newsize); + buf->limit = buf->data + newsize; + buf->ptr = buf->data + index; + } + + memcpy (buf->ptr, data, size); + buf->ptr += size; +} + +#ifndef JUST_LUACSHELL + +/* uppercase an entire string, using toupper */ +void +uppercase (char *instr) +{ + while (*instr != '\0') + { + *instr = toupper (*instr); + instr++; + } +} + + +/* lowercase an entire string, using tolower */ +void +lowercase (char *instr) +{ + while (*instr != '\0') + { + *instr = tolower (*instr); + instr++; + } +} + +/* return ptr to first non-whitespace character */ +char * +skip_whitespace (char *instr) +{ + while (isspace (*instr) && *instr) + instr++; + return instr; +} + + +/* return ptr to first whitespace character */ +char * +find_whitespace (char *instr) +{ + while (!isspace (*instr) && *instr) + instr++; + return instr; +} + + + +/* Counts the number of newlines in a buffer */ +int +count_lines (char *instr, size_t len, char *where) +{ + size_t line = 1; + while ((where > instr) && (len)) + { + if (*instr == '\n') + line++; + len--; + instr++; + } + return line; +} + +#endif + +#ifdef TEST_FRAMEWORK + +main () +{ + + int argc, count; + argv_t *argv; + char string[2000]; + + + strcpy (string, + "\\This\\ string will be '' \"separated into\" \"'\\\"'\" ' 15 ' elements.\n" + "' including a multi-line\n" + "element' with a comment. # This should not be parsed\n" + ";Nor should this\n" "The End."); + + printf ("%s\n", string); + + argc = argc_argv (string, &argv, "#;"); + for (count = 0; count < argc; count++) + { + printf ("%03d: [%s] ", count, argv[count].string, ""); + if (argv[count].quoted) + { + printf ("(it was quoted)"); + } + printf ("\n"); + } + if (argc != 15) + { + puts ("Test FAILED"); + } + else + { + puts ("Test PASSED"); + } + free (argv); +} + +#endif diff --git a/src/common.h b/src/common.h new file mode 100644 index 0000000..dbf33aa --- /dev/null +++ b/src/common.h @@ -0,0 +1,62 @@ +/* + * $Id: common.h,v 1.5 2005/11/21 22:05:34 nangel Exp $ + */ +#ifndef _COMMON_H +#define _COMMON_H 1 + + +/* how many argv slots to allocate at once */ +#define ALLOC_CHUNK 10 + + +#define STDIN 0 +#define STDOUT 1 +#define STDERR 2 + + +#define TRUE -1 +#define FALSE 0 +#define NONE 1 + +/* linked list */ +typedef struct +{ + char *buf; + void *next; +} list_t; + +/* name/value pairs */ +typedef struct +{ + char *string; /* the string */ + unsigned char quoted; /* non-zero if the string was quoted */ +} argv_t; + +/* expandable buffer structure */ +typedef struct +{ + unsigned char *data; /* the data */ + unsigned char *ptr; /* where to write to next */ + unsigned char *limit; /* maximal allocated buffer pos */ +} buffer_t; + + +/* common.c */ + +int argc_argv (char *instr, argv_t ** argv, char *commentstr); +void buffer_init (buffer_t * buf); +void buffer_reset (buffer_t * buf); +void buffer_destroy (buffer_t * buf); +void buffer_add (buffer_t * buf, const void *data, unsigned long size); + +#ifndef JUST_LUACSHELL + +void uppercase (char *instr); +void lowercase (char *instr); +char *skip_whitespace (char *instr); +char *find_whitespace (char *instr); +int count_lines (char *instr, size_t len, char *where); + +#endif + +#endif /* !_COMMON_H */ diff --git a/src/config.h.in b/src/config.h.in new file mode 100644 index 0000000..215b591 --- /dev/null +++ b/src/config.h.in @@ -0,0 +1,103 @@ +/* src/config.h.in. Generated from configure.ac by autoheader. */ + +/* Include bash extensions */ +#undef BASHEXTENSIONS + +/* Define to 1 if you have the <inttypes.h> header file. */ +#undef HAVE_INTTYPES_H + +/* Define to 1 if you have the <memory.h> header file. */ +#undef HAVE_MEMORY_H + +/* Define to 1 if you have the `memset' function. */ +#undef HAVE_MEMSET + +/* Define to 1 if you have the `putenv' function. */ +#undef HAVE_PUTENV + +/* Define to 1 if you have the <signal.h> header file. */ +#undef HAVE_SIGNAL_H + +/* Define to 1 if you have the <stdint.h> header file. */ +#undef HAVE_STDINT_H + +/* Define to 1 if you have the <stdlib.h> header file. */ +#undef HAVE_STDLIB_H + +/* Define to 1 if you have the `strcasecmp' function. */ +#undef HAVE_STRCASECMP + +/* Define to 1 if you have the `strdup' function. */ +#undef HAVE_STRDUP + +/* Define to 1 if you have the <strings.h> header file. */ +#undef HAVE_STRINGS_H + +/* Define to 1 if you have the <string.h> header file. */ +#undef HAVE_STRING_H + +/* Define to 1 if you have the `strncasecmp' function. */ +#undef HAVE_STRNCASECMP + +/* Define to 1 if you have the `strstr' function. */ +#undef HAVE_STRSTR + +/* Define to 1 if you have the <sys/stat.h> header file. */ +#undef HAVE_SYS_STAT_H + +/* Define to 1 if you have the <sys/types.h> header file. */ +#undef HAVE_SYS_TYPES_H + +/* Define to 1 if you have the <unistd.h> header file. */ +#undef HAVE_UNISTD_H + +/* Include Bash/Linux shell */ +#undef INCLUDE_BASHSHELL + +/* Include shell for precompiled Haserl/Lua */ +#undef INCLUDE_LUACSHELL + +/* Include ordinary Lua shell */ +#undef INCLUDE_LUASHELL + +/* Include just the compiled Lua shell */ +#undef JUST_LUACSHELL + +/* Name of package */ +#undef PACKAGE + +/* Define to the address where bug reports for this package should be sent. */ +#undef PACKAGE_BUGREPORT + +/* Define to the full name of this package. */ +#undef PACKAGE_NAME + +/* Define to the full name and version of this package. */ +#undef PACKAGE_STRING + +/* Define to the one symbol short name of this package. */ +#undef PACKAGE_TARNAME + +/* Define to the version of this package. */ +#undef PACKAGE_VERSION + +/* Define to 1 if you have the ANSI C header files. */ +#undef STDC_HEADERS + +/* the subshell to start up */ +#undef SUBSHELL_CMD + +/* Enable Lua */ +#undef USE_LUA + +/* Version number of package */ +#undef VERSION + +/* Enable GNU Extensions */ +#undef _GNU_SOURCE + +/* Define to empty if `const' does not conform to ANSI C. */ +#undef const + +/* Define to `unsigned int' if <sys/types.h> does not define. */ +#undef size_t diff --git a/src/h_bash.c b/src/h_bash.c new file mode 100644 index 0000000..dcb371c --- /dev/null +++ b/src/h_bash.c @@ -0,0 +1,651 @@ +/*----------------------------------------------------------------- + * haserl functions specific to a bash/ash/dash shell + * Copyright (c) 2003-2007 Nathan Angelacos (nangel@users.sourceforge.net) + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 2, as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + ------------------------------------------------------------------------- */ + +#if HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <unistd.h> +#include <time.h> +#include <getopt.h> +#include <sys/mman.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <sys/fcntl.h> +#include <stdlib.h> +#include <string.h> + +#if HAVE_SIGNAL_H +#include <signal.h> +#endif + +#include "common.h" +#include "h_error.h" +#include "h_bash.h" +#include "h_script.h" +#include "haserl.h" + +/* Local subshell variables */ +static int subshell_pipe[2]; +static int subshell_pid; + + +void +bash_setup (char *shell, list_t * env) +{ + int retcode = 0; + int count; + argv_t *argv; + char *av[20]; + list_t *next; + + if (shell == NULL) + return; + + retcode = pipe (&subshell_pipe[PARENT_IN]); + if (retcode == 0) + { + subshell_pid = fork (); + if (subshell_pid == -1) + { + die_with_message (NULL, NULL, g_err_msg[E_SUBSHELL_FAIL]); + } + + if (subshell_pid == 0) + { + /* I'm the child, connect stdin to the parent */ + dup2 (subshell_pipe[PARENT_IN], STDIN_FILENO); + close (subshell_pipe[PARENT_IN]); + close (subshell_pipe[PARENT_OUT]); + count = argc_argv (shell, &argv, ""); + if (count > 19) + { + /* over 20 command line args, silently truncate */ + av[19] = "\0"; + count = 18; + } + while (count >= 0) + { + av[count] = argv[count].string; + count--; + } + + + /* populate the environment */ + while (env) + { + next = env->next; + putenv (env->buf); + env = next; + } + + execv (argv[0].string, av); + free (argv); + + /* if we get here, we had a failure */ + die_with_message (NULL, NULL, g_err_msg[E_SUBSHELL_FAIL]); + } + else + { + /* I'm parent, move along please */ + close (subshell_pipe[PARENT_IN]); + } + } + + /* control should get to this point only in the parent. + */ +} + +void +bash_destroy (void) +{ + int status; + waitpid (subshell_pid, &status, 0); +} + + +void +bash_exec (buffer_t * buf, char *str) +{ + buffer_add (buf, str, strlen (str)); + return; +} + +/* Run the echo command in a subshell */ +void +bash_echo (buffer_t * buf, char *str, size_t len) +{ +/* limits.h would tell us the ARG_MAX characters we COULD send to the echo command, but + * we will take the (ancient) POSIX1 standard of 4K, subtract 1K from it and use that + * as the maxmimum. The Linux limit appears to be 128K, so 3K will fit. */ + + static char echo_start[] = "echo -n '"; + static char echo_quote[] = "'\\''"; + static char echo_end[] = "'\n"; + const size_t maxlen = 3096; + size_t pos; + + if (len == 0) + return; + pos = 0; + + buffer_add (buf, echo_start, strlen (echo_start)); + while (pos < len) + { + if (str[pos] == '\'') + buffer_add (buf, echo_quote, strlen (echo_quote)); + else + buffer_add (buf, str + pos, 1); + pos++; + if ((pos % maxlen) == 0) + { + buffer_add (buf, echo_end, strlen (echo_end)); + buffer_add (buf, echo_start, strlen (echo_start)); + } + } + buffer_add (buf, echo_end, strlen (echo_end)); +} + + +/* do an evaluation in a subshell */ +void +bash_eval (buffer_t * buf, char *str, size_t len) +{ + static char echo_start[] = "echo -n "; + static char echo_end[] = "\n"; + if (len == 0) + return; + + buffer_add (buf, echo_start, strlen (echo_start)); + buffer_add (buf, str, len); + buffer_add (buf, echo_end, strlen (echo_end)); +} + +#ifdef BASHEXTENSIONS +/* generate an IF statment */ +void +bash_if (buffer_t * buf, char *str, size_t len) +{ + static char err_msg[] = + "echo 'error: missing expression for if'\nexit 99\n"; + static char if_start[] = "if [[ "; + static char if_end[] = " ]]\nthen\n"; + static char ex_start[] = "if "; + static char ex_end[] = "\nthen\n"; + + if (len == 0) + { + buffer_add (buf, err_msg, strlen (err_msg)); + } + else if (str[0] == '|') + { + str[0] = ' '; + buffer_add (buf, ex_start, strlen (ex_start)); + buffer_add (buf, str, len); + buffer_add (buf, ex_end, strlen (ex_end)); + } + else + { + buffer_add (buf, if_start, strlen (if_start)); + buffer_add (buf, str, len); + buffer_add (buf, if_end, strlen (if_end)); + } +} + + +/* generate an ELIF statment */ +void +bash_elif (buffer_t * buf, char *str, size_t len) +{ + static char err_msg[] = + "echo 'error: missing expression for elif'\nexit 99\n"; + static char elif_start[] = "elif [[ "; + static char elif_end[] = " ]]\nthen\n"; + static char ex_start[] = "elif "; + static char ex_end[] = "\nthen\n"; + + if (len == 0) + { + buffer_add (buf, err_msg, strlen (err_msg)); + } + else if (str[0] == '|') + { + str[0] = ' '; + buffer_add (buf, ex_start, strlen (ex_start)); + buffer_add (buf, str, len); + buffer_add (buf, ex_end, strlen (ex_end)); + } + else + { + buffer_add (buf, elif_start, strlen (elif_start)); + buffer_add (buf, str, len); + buffer_add (buf, elif_end, strlen (elif_end)); + } +} + + +/* generate an ELSE statment */ +void +bash_else (buffer_t * buf, char *str, size_t len) +{ + static char else_start[] = "else"; + static char else_start2[] = "else #"; + static char else_end[] = "\n"; + + if (len == 0) + { + buffer_add (buf, else_start, strlen (else_start)); + } + else + { + buffer_add (buf, else_start2, strlen (else_start2)); + buffer_add (buf, str, len); + } + buffer_add (buf, else_end, strlen (else_end)); +} + + +/* generate a FI statment */ +void +bash_endif (buffer_t * buf, char *str, size_t len) +{ + static char fi_start[] = "fi"; + static char fi_start2[] = "fi #"; + static char fi_end[] = "\n"; + + if (len == 0) + { + buffer_add (buf, fi_start, strlen (fi_start)); + } + else + { + buffer_add (buf, fi_start2, strlen (fi_start2)); + buffer_add (buf, str, len); + } + buffer_add (buf, fi_end, strlen (fi_end)); +} + + +/* generate a CASE statment */ +void +bash_case (buffer_t * buf, char *str, size_t len) +{ + static char err_msg[] = + "echo 'error: missing expression for case'\nexit 99\n"; + static char case_start[] = "case "; + static char case_end[] = " in\n"; + /* + create a bogus case condition, nul+esc+eof, so nl+;;+nl + can be prepended to each when/otherwise/endcase, which + eliminates the need for ;; or <% ;; %> in the page source + */ + static char case_bogus[] = "\"\\000\\040\\004\") :\n"; + + if (len == 0) + { + buffer_add (buf, err_msg, strlen (err_msg)); + } + else + { + buffer_add (buf, case_start, strlen (case_start)); + buffer_add (buf, str, len); + buffer_add (buf, case_end, strlen (case_end)); + buffer_add (buf, case_bogus, strlen (case_bogus)); + } +} + + +/* generate a WHEN statment */ +void +bash_when (buffer_t * buf, char *str, size_t len) +{ + static char err_msg[] = + "echo 'error: missing expression for when'\nexit 99\n"; + static char when_start[] = "\n;;\n"; + static char when_end[] = ")\n"; + + if (len == 0) + { + buffer_add (buf, err_msg, strlen (err_msg)); + } + else + { + buffer_add (buf, when_start, strlen (when_start)); + buffer_add (buf, str, len - 1); + buffer_add (buf, when_end, strlen (when_end)); + } +} + + +/* generate an OTHERWISE statment */ +void +bash_otherwise (buffer_t * buf, char *str, size_t len) +{ + static char otherwise_start[] = "\n;;\n"; + static char otherwise_start1[] = "*)"; + static char otherwise_start2[] = "*) #"; + static char otherwise_end[] = "\n"; + + buffer_add (buf, otherwise_start, strlen (otherwise_start)); + + if (len == 0) + { + buffer_add (buf, otherwise_start1, strlen (otherwise_start1)); + } + else + { + buffer_add (buf, otherwise_start2, strlen (otherwise_start2)); + buffer_add (buf, str, len); + } + buffer_add (buf, otherwise_end, strlen (otherwise_end)); +} + + +/* generate a ENDCASE statment */ +void +bash_endcase (buffer_t * buf, char *str, size_t len) +{ + static char endcase_start[] = "\n;;\n"; + static char endcase_start1[] = "esac"; + static char endcase_start2[] = "esac #"; + static char endcase_end[] = "\n"; + + buffer_add (buf, endcase_start, strlen (endcase_start)); + + if (len == 0) + { + buffer_add (buf, endcase_start1, strlen (endcase_start1)); + } + else + { + buffer_add (buf, endcase_start2, strlen (endcase_start2)); + buffer_add (buf, str, len); + } + buffer_add (buf, endcase_end, strlen (endcase_end)); +} + + +/* generate a WHILE statment */ +void +bash_while (buffer_t * buf, char *str, size_t len) +{ + static char err_msg[] = + "echo 'error: missing expression for while'\nexit 99\n"; + static char while_start[] = "while [[ "; + static char while_end[] = " ]]\ndo\n"; + static char ex_start[] = "while "; + static char ex_end[] = "\ndo\n"; + + if (len == 0) + { + buffer_add (buf, err_msg, strlen (err_msg)); + } + else if (str[0] == '|') + { + str[0] = ' '; + buffer_add (buf, ex_start, strlen (ex_start)); + buffer_add (buf, str, len); + buffer_add (buf, ex_end, strlen (ex_end)); + } + else + { + buffer_add (buf, while_start, strlen (while_start)); + buffer_add (buf, str, len); + buffer_add (buf, while_end, strlen (while_end)); + } +} + + +/* generate an ENDWHILE statment */ +void +bash_endwhile (buffer_t * buf, char *str, size_t len) +{ + static char endwhile_start[] = "done"; + static char endwhile_start2[] = "done #"; + static char endwhile_end[] = "\n"; + + if (len == 0) + { + buffer_add (buf, endwhile_start, strlen (endwhile_start)); + } + else + { + buffer_add (buf, endwhile_start2, strlen (endwhile_start2)); + buffer_add (buf, str, len); + } + buffer_add (buf, endwhile_end, strlen (endwhile_end)); +} + + +/* generate an UNTIL statment */ +void +bash_until (buffer_t * buf, char *str, size_t len) +{ + static char err_msg[] = + "echo 'error: missing expression for until'\nexit 99\n"; + static char until_start[] = "until [[ "; + static char until_end[] = " ]]\ndo\n"; + static char ex_start[] = "until "; + static char ex_end[] = "\ndo\n"; + + if (len == 0) + { + buffer_add (buf, err_msg, strlen (err_msg)); + } + else if (str[0] == '|') + { + str[0] = ' '; + buffer_add (buf, ex_start, strlen (ex_start)); + buffer_add (buf, str, len); + buffer_add (buf, ex_end, strlen (ex_end)); + } + else + { + buffer_add (buf, until_start, strlen (until_start)); + buffer_add (buf, str, len); + buffer_add (buf, until_end, strlen (until_end)); + } +} + + +/* generate an ENDUNTIL statment */ +void +bash_enduntil (buffer_t * buf, char *str, size_t len) +{ + static char enduntil_start[] = "done"; + static char enduntil_start2[] = "done #"; + static char enduntil_end[] = "\n"; + + if (len == 0) + { + buffer_add (buf, enduntil_start, strlen (enduntil_start)); + } + else + { + buffer_add (buf, enduntil_start2, strlen (enduntil_start2)); + buffer_add (buf, str, len); + } + buffer_add (buf, enduntil_end, strlen (enduntil_end)); +} + + +/* generate a FOR statment */ +void +bash_for (buffer_t * buf, char *str, size_t len) +{ + static char err_msg[] = + "echo 'error: missing expression for for'\nexit 99\n"; + static char for_start[] = "for "; + static char for_end[] = "\ndo\n"; + + if (len == 0) + { + buffer_add (buf, err_msg, strlen (err_msg)); + } + else + { + buffer_add (buf, for_start, strlen (for_start)); + buffer_add (buf, str, len); + buffer_add (buf, for_end, strlen (for_end)); + } +} + + +/* generate an ENDFOR statment */ +void +bash_endfor (buffer_t * buf, char *str, size_t len) +{ + static char endfor_start[] = "done"; + static char endfor_start2[] = "done #"; + static char endfor_end[] = "\n"; + + if (len == 0) + { + buffer_add (buf, endfor_start, strlen (endfor_start)); + } + else + { + buffer_add (buf, endfor_start2, strlen (endfor_start2)); + buffer_add (buf, str, len); + } + buffer_add (buf, endfor_end, strlen (endfor_end)); +} + + +/* generate an UNLESS statment */ +void +bash_unless (buffer_t * buf, char *str, size_t len) +{ + static char err_msg[] = + "echo 'error: missing expression for unless'\nexit 99\n"; + static char unless_start[] = "if [[ ! ( "; + static char unless_end[] = " ) ]]\nthen\n"; + static char ex_start[] = "if ! "; + static char ex_end[] = "\nthen\n"; + + if (len == 0) + { + buffer_add (buf, err_msg, strlen (err_msg)); + } + else if (str[0] == '|') + { + str[0] = ' '; + buffer_add (buf, ex_start, strlen (ex_start)); + buffer_add (buf, str, len); + buffer_add (buf, ex_end, strlen (ex_end)); + } + else + { + buffer_add (buf, unless_start, strlen (unless_start)); + buffer_add (buf, str, len); + buffer_add (buf, unless_end, strlen (unless_end)); + } +} + + +/* generate an ELUN statment */ +void +bash_elun (buffer_t * buf, char *str, size_t len) +{ + static char err_msg[] = + "echo 'error: missing expression for elun'\nexit 99\n"; + static char elun_start[] = "elif [[ ! ( "; + static char elun_end[] = " ) ]]\nthen\n"; + static char ex_start[] = "elif ! "; + static char ex_end[] = "\nthen\n"; + + if (len == 0) + { + buffer_add (buf, err_msg, strlen (err_msg)); + } + else if (str[0] == '|') + { + str[0] = ' '; + buffer_add (buf, ex_start, strlen (ex_start)); + buffer_add (buf, str, len); + buffer_add (buf, ex_end, strlen (ex_end)); + } + else + { + buffer_add (buf, elun_start, strlen (elun_start)); + buffer_add (buf, str, len); + buffer_add (buf, elun_end, strlen (elun_end)); + } +} + + +/* generate an UNELSE statment */ +void +bash_unelse (buffer_t * buf, char *str, size_t len) +{ + static char unelse_start[] = "else"; + static char unelse_start2[] = "else #"; + static char unelse_end[] = "\n"; + + if (len == 0) + { + buffer_add (buf, unelse_start, strlen (unelse_start)); + } + else + { + buffer_add (buf, unelse_start2, strlen (unelse_start2)); + buffer_add (buf, str, len); + } + buffer_add (buf, unelse_end, strlen (unelse_end)); +} + + +/* generate a ENDUNLESS statment */ +void +bash_endunless (buffer_t * buf, char *str, size_t len) +{ + static char endunless_start[] = "fi"; + static char endunless_start2[] = "fi #"; + static char endunless_end[] = "\n"; + + if (len == 0) + { + buffer_add (buf, endunless_start, strlen (endunless_start)); + } + else + { + buffer_add (buf, endunless_start2, strlen (endunless_start2)); + buffer_add (buf, str, len); + } + buffer_add (buf, endunless_end, strlen (endunless_end)); +} +#endif + +void +bash_doscript (buffer_t * script, char *name) +{ + static char postfix[] = "\nexit\n"; + + /* dump the script to the subshell */ + write (subshell_pipe[PARENT_OUT], script->data, script->ptr - script->data); + + /* write the postfix */ + write (subshell_pipe[PARENT_OUT], postfix, strlen (postfix)); + + + return; + +} diff --git a/src/h_bash.h b/src/h_bash.h new file mode 100644 index 0000000..d827aa6 --- /dev/null +++ b/src/h_bash.h @@ -0,0 +1,41 @@ +/* + * $Id: haserl.h,v 1.14 2005/11/18 14:43:10 nangel Exp $ + */ +#ifndef H_SUBSHELL_H +#define H_SUBSHELL_H 1 + + +/* the "names" for the pipes to the subshell */ +enum pipe_t { PARENT_IN, PARENT_OUT }; + +/* h_bash.c */ +void bash_destroy(void); +void bash_exec(buffer_t *buf, char *str); +void bash_wait(buffer_t *buf, char *str); +void bash_echo(buffer_t *buf, char *str, size_t len); +void bash_eval(buffer_t *buf, char *str, size_t len); +void bash_setup(char *shell, list_t *env); +void bash_doscript(buffer_t *script, char *name); + +#ifdef BASHEXTENSIONS +void bash_if(buffer_t *buf, char *str, size_t len); +void bash_elif(buffer_t *buf, char *str, size_t len); +void bash_else(buffer_t *buf, char *str, size_t len); +void bash_endif(buffer_t *buf, char *str, size_t len); +void bash_case(buffer_t *buf, char *str, size_t len); +void bash_when(buffer_t *buf, char *str, size_t len); +void bash_otherwise(buffer_t *buf, char *str, size_t len); +void bash_endcase(buffer_t *buf, char *str, size_t len); +void bash_while(buffer_t *buf, char *str, size_t len); +void bash_endwhile(buffer_t *buf, char *str, size_t len); +void bash_until(buffer_t *buf, char *str, size_t len); +void bash_enduntil(buffer_t *buf, char *str, size_t len); +void bash_for(buffer_t *buf, char *str, size_t len); +void bash_endfor(buffer_t *buf, char *str, size_t len); +void bash_unless(buffer_t *buf, char *str, size_t len); +void bash_elun(buffer_t *buf, char *str, size_t len); +void bash_unelse(buffer_t *buf, char *str, size_t len); +void bash_endunless(buffer_t *buf, char *str, size_t len); +#endif + +#endif /* !H_SUBSHELL_H */ diff --git a/src/h_error.c b/src/h_error.c new file mode 100644 index 0000000..46bd6e1 --- /dev/null +++ b/src/h_error.c @@ -0,0 +1,103 @@ +/* + * -------------------------------------------------------------------------- + * Error functions for haserl + * Copyright (c) 2003-2006 Nathan Angelacos (nangel@users.sourceforge.net) + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 2, as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * ------------------------------------------------------------------------- + */ + +#if HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <stdarg.h> + +#include "common.h" +#include "h_script.h" +#include "haserl.h" +#include "h_error.h" + +char *g_err_msg[] = { + "", + "Memory Allocation Failure", + "Unable to open file %s", + "%c> before <%c", + "Missing %c>", + "Unknown operation", + "Unable to start subshell", + "Unspecified Error", +}; + + + +/* + * abort the program + */ +void +die_with_error (char *msg) +{ + fprintf (stderr, "Error: %s\n", msg); + exit (-1); +} + + +/* print an error message and die. If sp or where are non-null pointers, then + a line is added saying where in the script buffer the error occured. If + there's a request method, then http headers are added. + */ +void +die_with_message (void *sp, char *where, const char *s, ...) +{ + #ifndef JUST_LUACSHELL + script_t *script = sp; + #endif + va_list p; + FILE *fo = stderr; + + if (global.silent == FALSE) + { + if (getenv ("REQUEST_METHOD")) + { + fo = stdout; + fprintf (fo, "HTTP/1.0 500 Server Error\n" + "Content-Type: text/html\n\n" + "<html><body><b><font color=#CC0000>" PACKAGE_NAME + " CGI Error</font></b><br><pre>\n"); + } + va_start (p, s); + vfprintf (fo, s, p); + va_end (p); + #ifndef JUST_LUACSHELL + if (where && sp) + { + fprintf (fo, " near line %d of %s\n", + count_lines (script->buf, script->size, where), + script->name); + } + #endif + printf ("\n"); + + if (getenv ("REQUEST_METHOD")) + fprintf (fo, "</pre></body></html>\n"); + } + exit (-1); + +} diff --git a/src/h_error.h b/src/h_error.h new file mode 100644 index 0000000..af8d9aa --- /dev/null +++ b/src/h_error.h @@ -0,0 +1,19 @@ +/* + * $Id: haserl.h,v 1.14 2005/11/18 14:43:10 nangel Exp $ + */ +#ifndef H_ERROR_H +#define H_ERROR_H 1 + + +enum error_types { E_NO_ERROR, E_MALLOC_FAIL, E_FILE_OPEN_FAIL, + E_END_BEFORE_BEGIN, E_NO_END_MARKER , + E_NO_OP, E_SUBSHELL_FAIL, E_WHATEVER }; + +extern char *g_err_msg[]; + +/* h_error.c */ +void die_with_error(char *msg); +void die_with_syntax(void *script, char *where, int error); +void +die_with_message ( void *sp, char *where, const char *s, ...); +#endif /* !H_ERROR_H */ diff --git a/src/h_lua.c b/src/h_lua.c new file mode 100644 index 0000000..6bee6e2 --- /dev/null +++ b/src/h_lua.c @@ -0,0 +1,155 @@ +/* -------------------------------------------------------------------------- + * lua language specific functions + * Copyright (c) 2003-2007 Nathan Angelacos (nangel@users.sourceforge.net) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + ------------------------------------------------------------------------- */ + +#if HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <unistd.h> +#include <time.h> +#include <getopt.h> +#include <sys/mman.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <sys/fcntl.h> +#include <stdlib.h> +#include <string.h> + +#include <lua.h> +#include <lauxlib.h> +#include <lualib.h> + +#include "common.h" +#include "h_error.h" +#include "h_lua.h" +#include "h_script.h" +#include "haserl.h" + +extern lua_State *lua_vm; + +/* attempts to open a file, tokenize and then process it as a haserl script */ +int +h_lua_loadfile (lua_State * L) +{ + script_t *scriptchain; + token_t *tokenchain; + buffer_t script_text; + int status; + + /* get the filename */ + const char *filename = luaL_checkstring (L, 1); + + + scriptchain = load_script ((char *) filename, NULL); + tokenchain = build_token_list (scriptchain, NULL); + preprocess_token_list (tokenchain); + process_token_list (&script_text, tokenchain); + free_token_list (tokenchain); + free_script_list (scriptchain); + + /* script_text has the include file */ + status = luaL_loadbuffer (L, (char *) script_text.data, + script_text.ptr - script_text.data, filename); + buffer_destroy (&script_text); + if (status) + { + lua_error (L); + } + return (1); /* we return one value, the buffer, as a function */ +} + +void +lua_exec (buffer_t * buf, char *str) +{ + buffer_add (buf, str, strlen (str)); +} + +void +lua_doscript (buffer_t * script, char *name) +{ + int status; + /* force the string to be null terminated */ + + buffer_add (script, "\0", 1); + + status = luaL_loadbuffer (lua_vm, (char *) script->data, + strlen ((char *) script->data), name) || + lua_pcall (lua_vm, 0, LUA_MULTRET, 0); + + if (status && !lua_isnil (lua_vm, -1)) + { + const char *msg = lua_tostring (lua_vm, -1); + if (msg == NULL) + msg = "(error object is not a string)"; + die_with_message (NULL, NULL, msg); + } +} + + +/* Run the echo command in a subshell */ +void +lua_echo (buffer_t * buf, char *str, size_t len) +{ + static char echo_start[] = " io.write "; + char quote[200] = "]=]"; /* 197 nested comments is a problem */ + + if (len == 0) + return; + + /* figure out if the string uses ]] ]=] ]==] etc in it */ + while ((strstr (str, quote)) && (strlen (quote) < 198)) + { + memmove (quote + strlen (quote) - 1, quote + strlen (quote) - 2, 3); + } + + /* As of 5.1, nested comments are depreciated... sigh */ + quote[0] = '['; + quote[strlen (quote) - 1] = quote[0]; + while ((strstr (str, quote)) && (strlen (quote) < 198)) + { + memmove (quote + strlen (quote) - 1, quote + strlen (quote) - 2, 3); + } + + buffer_add (buf, echo_start, strlen (echo_start)); + buffer_add (buf, quote, strlen (quote)); + buffer_add (buf, str, len); + quote[0] = ']'; + quote[strlen (quote) - 1] = quote[0]; + buffer_add (buf, quote, strlen (quote)); + buffer_add (buf, "\n", 1); + +} + + +/* do an evaluation */ +void +lua_eval (buffer_t * buf, char *str, size_t len) +{ + static char start[] = " io.write(tostring("; + static char end[] = "))\n"; + if (len == 0) + return; + + buffer_add (buf, start, strlen (start)); + buffer_add (buf, str, len); + buffer_add (buf, end, strlen (end)); +} + diff --git a/src/h_lua.h b/src/h_lua.h new file mode 100644 index 0000000..1b1e8af --- /dev/null +++ b/src/h_lua.h @@ -0,0 +1,12 @@ +#ifndef H_LUA_H +#define H_LUA_H 1 + + +void lua_exec(buffer_t *buf, char *str); +void lua_echo(buffer_t *buf, char *str, size_t len); +void lua_eval(buffer_t *buf, char *str, size_t len); +void lua_doscript(buffer_t *script, char *name); +int h_lua_loadfile(lua_State *L); + + +#endif diff --git a/src/h_lua_common.c b/src/h_lua_common.c new file mode 100644 index 0000000..e29c97b --- /dev/null +++ b/src/h_lua_common.c @@ -0,0 +1,117 @@ +/* -------------------------------------------------------------------------- + * functions shared among both Lua shells + * Copyright (c) 2003-2007 Nathan Angelacos (nangel@users.sourceforge.net) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + ------------------------------------------------------------------------- */ + +#if HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> + +#include <lua.h> +#include <lauxlib.h> +#include <lualib.h> + +#include "common.h" +#include "h_lua_common.h" +#ifdef INCLUDE_LUASHELL +#include "h_lua.h" +#endif +#ifdef INCLUDE_LUACSHELL +#include "h_luac.h" +#endif +#include "h_error.h" + +/* this is not a mistake. We are including the + * definition of the lualib here + */ +#include "haserl_lualib.inc" + +lua_State *lua_vm = NULL; + +void +lua_common_putenv (char *str) +{ + char *value; + value = memchr (str, '=', strlen (str)); + if (value) + { + *value = (char) '\0'; + value++; + } + else + { + value = str + strlen (str); + } + + + lua_getglobal (lua_vm, "haserl"); + lua_pushstring (lua_vm, "myputenv"); + lua_gettable (lua_vm, -2); + lua_pushstring (lua_vm, str); + lua_pushstring (lua_vm, value); + lua_call (lua_vm, 2, 0); + return; +} + +void +lua_common_setup (char *shell, list_t * env) +{ + /* create a lua instance */ + lua_vm = luaL_newstate (); + luaL_openlibs (lua_vm); + + /* and load our haserl library */ + if (luaL_loadbuffer + (lua_vm, (const char *) &haserl_lualib, sizeof (haserl_lualib), + "luascript.lua") || lua_pcall (lua_vm, 0, 0, 0)) + { + die_with_message (NULL, NULL, + "Error passing the lua library to the lua vm: %s", + lua_tostring (lua_vm, -1)); + } + + /* and put the vars in the vm */ + while (env) + { + lua_common_putenv (env->buf); + env = env->next; + } + + + /* register our open function in the haserl table */ + lua_getglobal (lua_vm, "haserl"); + lua_pushstring (lua_vm, "loadfile"); +#if defined(INCLUDE_LUASHELL) && defined(INCLUDE_LUACSHELL) + lua_pushcfunction (lua_vm, + shell[3] == 'c' ? h_luac_loadfile : h_lua_loadfile); +#elif defined(INCLUDE_LUASHELL) + lua_pushcfunction (lua_vm, h_lua_loadfile); +#else /* INCLUDE_LUACSHELL */ + lua_pushcfunction (lua_vm, h_luac_loadfile); +#endif + lua_settable (lua_vm, -3); + +} + +void +lua_common_destroy (void) +{ + /* close the lua instance */ + lua_close (lua_vm); +} diff --git a/src/h_lua_common.h b/src/h_lua_common.h new file mode 100644 index 0000000..18b0e68 --- /dev/null +++ b/src/h_lua_common.h @@ -0,0 +1,7 @@ +#ifndef _H_LUA_COMMON_H +#define _H_LUA_COMMON_H + +void lua_common_setup(char *shell, list_t *env); +void lua_common_destroy(void); + +#endif diff --git a/src/h_luac.c b/src/h_luac.c new file mode 100644 index 0000000..55e4083 --- /dev/null +++ b/src/h_luac.c @@ -0,0 +1,55 @@ +/* -------------------------------------------------------------------------- + * lua language specific functions (for compiled Lua chunks) + * Copyright (c) 2003-2007 Nathan Angelacos (nangel@users.sourceforge.net) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + ------------------------------------------------------------------------- */ + +#if HAVE_CONFIG_H +#include <config.h> +#endif + +#include <lua.h> +#include <lauxlib.h> +#include <lualib.h> + +#include "common.h" +#include "h_luac.h" +#include "h_error.h" + +extern lua_State *lua_vm; + + /* tokenizer was not used, so script is NULL, but name is the filename to process */ +void +luac_doscript (buffer_t *script, char *name) +{ + if (luaL_loadfile (lua_vm, name) || lua_pcall(lua_vm, 0, LUA_MULTRET, 0)) + { + die_with_message (NULL, NULL, "Cannot load lua and execute chunk: %s", lua_tostring(lua_vm, -1)); + } +} + +int +h_luac_loadfile (lua_State *L) +{ + const char *filename = luaL_checkstring (L, 1); + + if (luaL_loadfile (L, filename)) + { + die_with_message (NULL, NULL, "Cannot load file '%s': %s", filename, lua_tostring(L, -1)); + } /* no error: function is on the stack */ + + return 1; +} diff --git a/src/h_luac.h b/src/h_luac.h new file mode 100644 index 0000000..37c6536 --- /dev/null +++ b/src/h_luac.h @@ -0,0 +1,7 @@ +#ifndef _H_LUAC_H +#define _H_LUAC_H + +void luac_doscript(buffer_t *script, char *name); +int h_luac_loadfile(lua_State * L); + +#endif diff --git a/src/h_script.c b/src/h_script.c new file mode 100644 index 0000000..029fc41 --- /dev/null +++ b/src/h_script.c @@ -0,0 +1,614 @@ +/*-------------------------------------------------------------------------- + * functions related to opening, tokenizing and parsing a haserl script + * Copyright (c) 2006-2007 Nathan Angelacos (nangel@users.sourceforge.net) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * + ------------------------------------------------------------------------- */ + +#if HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/fcntl.h> +#include <string.h> +#include <stdlib.h> +#include <ctype.h> + +#include "common.h" +#include "h_error.h" +#include "h_script.h" +#include "h_bash.h" +#include "haserl.h" + +#ifdef BASHEXTENSIONS +/* HTML, RUN, INCLUDE, EVAL, COMMENT, IF, ELIF, ELSE, ENDIF, CASE, WHEN, + OTHERWISE, ENDCASE, WHILE, ENDWHILE, UNTIL, ENDUNTIL, FOR, ENDFOR, + UNLESS, ELUN, UNELSE, ENDUNLESS, NOOP }; */ +#else +/* HTML, RUN, INCLUDE, EVAL, COMMENT, NOOP */ +#endif + +const char *g_tag[] = { + "", + "", + "in", + "=", + "#", +#ifdef BASHEXTENSIONS + "if", + "elif", + "else", + "endif", + "case", + "when", + "otherwise", + "endcase", + "while", + "endwhile", + "until", + "enduntil", + "for", + "endfor", + "unless", + "elun", + "unelse", + "endunless", +#endif + "" +}; + +/* the open and close tags */ +char open_tag[3] = "<%"; +char close_tag[3] = "%>"; + + +/* Open a script and return a populated script_t structure + */ +script_t * +load_script (char *filename, script_t * scriptlist) +{ + script_t *scriptbuf; + int scriptfp; + struct stat filestat; + + scriptfp = open (filename, O_NONBLOCK + O_RDONLY); + if (scriptfp == -1) + { /* open failed */ + die_with_message (NULL, NULL, g_err_msg[E_FILE_OPEN_FAIL], filename); + } + + fstat (scriptfp, &filestat); + scriptbuf = (script_t *) xmalloc (sizeof (script_t)); + scriptbuf->name = (char *) xmalloc (strlen (filename) + 1); + scriptbuf->buf = (char *) xmalloc (filestat.st_size + 1); + + memset (scriptbuf->name, 0, strlen (filename) + 1); + memcpy (scriptbuf->name, filename, strlen (filename)); + memset (scriptbuf->buf, 0, filestat.st_size + 1); + read (scriptfp, scriptbuf->buf, filestat.st_size); + + scriptbuf->size = filestat.st_size; + scriptbuf->uid = filestat.st_uid; + scriptbuf->gid = filestat.st_gid; + scriptbuf->curpos = 0; + scriptbuf->next = NULL; + + /* if we already have scripts, add this one to the end */ + if (scriptlist != NULL) + { + while (scriptlist->next) + scriptlist = scriptlist->next; + scriptlist->next = scriptbuf; + } + + /* skip over the first line, if the file starts with a #! + * This means that offset will start at the beginning of + * the second line in most cases. + */ + if (memcmp (scriptbuf->buf, "#!", 2) == 0) + { + while ((scriptbuf->curpos < scriptbuf->size) && + ((char) scriptbuf->buf[scriptbuf->curpos] != '\n')) + { + (scriptbuf->curpos)++; + } + (scriptbuf->curpos)++; + } + + /* If this is the first script, switch to <? ?> mode only + * if <% is not used anywhere else in the script. Otherwise + * don't change the tagging method + */ + if (scriptlist == NULL) + { + if (strstr (scriptbuf->buf + scriptbuf->curpos, open_tag) == NULL) + { + open_tag[1] = '?'; + close_tag[0] = '?'; + } + } + close (scriptfp); + return (scriptbuf); +} + +/* Free the script structures */ +void +free_script_list (script_t * script) +{ + script_t *next; + while (script) + { + next = script->next; + if (script->name) + free (script->name); + if (script->buf) + free (script->buf); + free (script); + script = next; + } +} + +/* Insert this token into the token chain. + * If tokenlist is null, create a new one + */ +token_t * +push_token_on_list (token_t * tokenlist, script_t * scriptbuf, + char *start, size_t len) +{ + token_t *me, *next; + if (len == 0) + return (tokenlist); + + me = (token_t *) xmalloc (sizeof (token_t)); + + if (tokenlist == NULL) + { + next = NULL; + } + else + { + next = tokenlist->next; + tokenlist->next = me; + } + + me->next = next; + me->script = scriptbuf; + me->buf = start; + me->len = len; + + return (me); +} + +/* Free a token chain */ +void +free_token_list (token_t * tokenlist) +{ + token_t *next; + + while (tokenlist) + { + next = tokenlist->next; + free (tokenlist); + tokenlist = next; + } +} + +#ifndef JUST_LUACSHELL + +/* return the point in a script where the "comment" ends */ +char * +skip_comment (char *startbuf, char *endbuf) +{ + unsigned int c_lev = 1; + char *s_tag, *e_tag; + + startbuf += 2; + while (startbuf < endbuf) + { + s_tag = strstr (startbuf, open_tag); + e_tag = strstr (startbuf, close_tag); + + if (!e_tag) + { + break; + } + + if ((s_tag) && (s_tag < e_tag)) + { + c_lev++; + startbuf = s_tag + 2; + continue; + } + /* otherwise, we have an end tag first */ + c_lev--; + startbuf = e_tag; + if (c_lev == 0) + { + return (startbuf); + } + startbuf += 2; + + } + return NULL; +} + +/* build a tokenchain from a script. This step just + * splits a script buf into parts that are separated + * by <% %>. If the <% %> are out of order, then it + * flags that, but doesn't try to do anything else. + * + * For nesting comments, this function IS aware of + * the comment tag, and will try accomodate commented + * tags. Commented script never enters the token list + */ +token_t * +build_token_list (script_t * scriptbuf, token_t * tokenlist) +{ + + char *start, *end, *curpos, *endpos; + token_t *curtoken, *firsttoken; + + curtoken = tokenlist; + firsttoken = tokenlist; + + curpos = scriptbuf->buf + scriptbuf->curpos; + endpos = scriptbuf->buf + scriptbuf->size; + + while (curpos < endpos) + { + start = strstr (curpos, open_tag); + end = strstr (curpos, close_tag); + + /* if this is a comment tag, the end is at the end of the comment */ + if ((start) && (memcmp (start + 2, g_tag[COMMENT], 1) == 0)) + { + /* save any bare html before the comment */ + curtoken = push_token_on_list (curtoken, scriptbuf, curpos, + start - curpos); + if (firsttoken == NULL) + firsttoken = curtoken; + /* push start of token to end of token */ + end = skip_comment (start, endpos); + if (end) + { + curpos = end + 2; + continue; + } + } + + if (start && !end) + die_with_message (scriptbuf, start, g_err_msg[E_NO_END_MARKER], + open_tag[1]); + + if ((start > end) || (!start && end)) + die_with_message (scriptbuf, end, g_err_msg[E_END_BEFORE_BEGIN], + open_tag[1], open_tag[1]); + + if (start && (strstr (start + 1, open_tag) + && (strstr (start + 1, open_tag) < end))) + die_with_message (scriptbuf, start, g_err_msg[E_NO_END_MARKER], + open_tag[1]); + if (end) + { + /* push curpos to the start of the token */ + curtoken = push_token_on_list (curtoken, scriptbuf, curpos, + start - curpos); + if (firsttoken == NULL) + firsttoken = curtoken; + /* push start of token to end of token */ + curtoken = + push_token_on_list (curtoken, scriptbuf, start, end - start); + if (firsttoken == NULL) + firsttoken = curtoken; + curpos = end + 2; + } + else + { + /* push curpos to end of script */ + curtoken = + push_token_on_list (curtoken, scriptbuf, curpos, endpos - curpos); + if (firsttoken == NULL) + firsttoken = curtoken; + curpos = endpos; + } + } + + return (firsttoken); +} + +/* syntax check a script */ +void +preprocess_token_list (token_t * tokenlist) +{ + script_t *newscript; + token_t *me; + char *cp; + + me = tokenlist; + /* walk the chain to fill in the tags */ + while (me) + { + if (memcmp (me->buf, open_tag, 2)) + { + me->tag = HTML; + } + else + { + me->tag = NOOP; + me->buf[me->len] = '\0'; + cp = me->buf + 2; /* skip <? */ + if (memcmp (cp, g_tag[INCLUDE], 2) == 0) + { + me->tag = INCLUDE; + /* skip the first word - in, include, include-file, etc */ + me->buf = find_whitespace (me->buf); + me->buf = skip_whitespace (me->buf); + cp = find_whitespace (me->buf); + *cp = '\0'; + me->len = strlen (me->buf) + 1; + newscript = load_script (me->buf, me->script); + build_token_list (newscript, me); + } + else if (memcmp (cp, g_tag[EVAL], 1) == 0) + + { + me->tag = EVAL; + me->buf = find_whitespace (me->buf); + me->len = strlen (me->buf); + } +#ifdef BASHEXTENSIONS + else if (memcmp (cp, g_tag[IF], 2) == 0) + { + me->tag = IF; + me->buf = find_whitespace (me->buf); + me->buf = skip_whitespace (me->buf); + me->len = strlen (me->buf); + } + else if (memcmp (cp, g_tag[ELIF], 4) == 0) + { + me->tag = ELIF; + me->buf = find_whitespace (me->buf); + me->buf = skip_whitespace (me->buf); + me->len = strlen (me->buf); + } + else if (memcmp (cp, g_tag[ELSE], 4) == 0) + { + me->tag = ELSE; + me->buf = find_whitespace (me->buf); + me->buf = skip_whitespace (me->buf); + me->len = strlen (me->buf); + } + else if (memcmp (cp, g_tag[ENDIF], 5) == 0) + { + me->tag = ENDIF; + me->buf = find_whitespace (me->buf); + me->buf = skip_whitespace (me->buf); + me->len = strlen (me->buf); + } + else if (memcmp (cp, g_tag[CASE], 4) == 0) + { + me->tag = CASE; + me->buf = find_whitespace (me->buf); + me->buf = skip_whitespace (me->buf); + me->len = strlen (me->buf); + } + else if (memcmp (cp, g_tag[WHEN], 4) == 0) + { + me->tag = WHEN; + me->buf = find_whitespace (me->buf); + me->buf = skip_whitespace (me->buf); + me->len = strlen (me->buf); + } + else if (memcmp (cp, g_tag[OTHERWISE], 9) == 0) + { + me->tag = OTHERWISE; + me->buf = find_whitespace (me->buf); + me->buf = skip_whitespace (me->buf); + me->len = strlen (me->buf); + } + else if (memcmp (cp, g_tag[ENDCASE], 7) == 0) + { + me->tag = ENDCASE; + me->buf = find_whitespace (me->buf); + me->buf = skip_whitespace (me->buf); + me->len = strlen (me->buf); + } + else if (memcmp (cp, g_tag[WHILE], 5) == 0) + { + me->tag = WHILE; + me->buf = find_whitespace (me->buf); + me->buf = skip_whitespace (me->buf); + me->len = strlen (me->buf); + } + else if (memcmp (cp, g_tag[ENDWHILE], 8) == 0) + { + me->tag = ENDWHILE; + me->buf = find_whitespace (me->buf); + me->buf = skip_whitespace (me->buf); + me->len = strlen (me->buf); + } + else if (memcmp (cp, g_tag[UNTIL], 5) == 0) + { + me->tag = UNTIL; + me->buf = find_whitespace (me->buf); + me->buf = skip_whitespace (me->buf); + me->len = strlen (me->buf); + } + else if (memcmp (cp, g_tag[ENDUNTIL], 8) == 0) + { + me->tag = ENDUNTIL; + me->buf = find_whitespace (me->buf); + me->buf = skip_whitespace (me->buf); + me->len = strlen (me->buf); + } + else if (memcmp (cp, g_tag[FOR], 3) == 0) + { + me->tag = FOR; + me->buf = find_whitespace (me->buf); + me->buf = skip_whitespace (me->buf); + me->len = strlen (me->buf); + } + else if (memcmp (cp, g_tag[ENDFOR], 6) == 0) + { + me->tag = ENDFOR; + me->buf = find_whitespace (me->buf); + me->buf = skip_whitespace (me->buf); + me->len = strlen (me->buf); + } + else if (memcmp (cp, g_tag[UNLESS], 6) == 0) + { + me->tag = UNLESS; + me->buf = find_whitespace (me->buf); + me->buf = skip_whitespace (me->buf); + me->len = strlen (me->buf); + } + else if (memcmp (cp, g_tag[ELUN], 4) == 0) + { + me->tag = ELUN; + me->buf = find_whitespace (me->buf); + me->buf = skip_whitespace (me->buf); + me->len = strlen (me->buf); + } + else if (memcmp (cp, g_tag[UNELSE], 6) == 0) + { + me->tag = UNELSE; + me->buf = find_whitespace (me->buf); + me->buf = skip_whitespace (me->buf); + me->len = strlen (me->buf); + } + else if (memcmp (cp, g_tag[ENDUNLESS], 9) == 0) + { + me->tag = ENDUNLESS; + me->buf = find_whitespace (me->buf); + me->buf = skip_whitespace (me->buf); + me->len = strlen (me->buf); + } +#endif /* BASHEXTENSIONS */ + if (isspace (*cp)) + { + me->tag = RUN; + me->buf = cp; + } + if (me->tag == NOOP) + { + die_with_message (me->script, cp, g_err_msg[E_NO_OP]); + } + me->len = strlen (me->buf); + } + me = me->next; + } +} + + +token_t * +process_token_list (buffer_t * buf, token_t * token) +{ + char *c; + + buffer_init (buf); + shell_exec (buf, "\n"); /* try to get the error reporting match the line number */ + + while (token) + { + switch (token->tag) + { + case HTML: + /* Change from 0.8.0 - if the whole thing is just + whitespace, don't print it */ + c = token->buf; + while ((c < (token->buf + token->len)) && (isspace (*c))) + c++; + if (c != token->buf + token->len) + shell_echo (buf, token->buf, token->len); + break; + case RUN: + shell_exec (buf, token->buf); + shell_exec (buf, "\n"); + break; + case EVAL: + shell_eval (buf, token->buf, token->len); + break; +#ifdef BASHEXTENSIONS + case IF: + shell_if (buf, token->buf, token->len); + break; + case ELIF: + shell_elif (buf, token->buf, token->len); + break; + case ELSE: + shell_else (buf, token->buf, token->len); + break; + case ENDIF: + shell_endif (buf, token->buf, token->len); + break; + case CASE: + shell_case (buf, token->buf, token->len); + break; + case WHEN: + shell_when (buf, token->buf, token->len); + break; + case OTHERWISE: + shell_otherwise (buf, token->buf, token->len); + break; + case ENDCASE: + shell_endcase (buf, token->buf, token->len); + break; + case WHILE: + shell_while (buf, token->buf, token->len); + break; + case ENDWHILE: + shell_endwhile (buf, token->buf, token->len); + break; + case UNTIL: + shell_until (buf, token->buf, token->len); + break; + case ENDUNTIL: + shell_enduntil (buf, token->buf, token->len); + break; + case FOR: + shell_for (buf, token->buf, token->len); + break; + case ENDFOR: + shell_endfor (buf, token->buf, token->len); + break; + case UNLESS: + shell_unless (buf, token->buf, token->len); + break; + case ELUN: + shell_elun (buf, token->buf, token->len); + break; + case UNELSE: + shell_unelse (buf, token->buf, token->len); + break; + case ENDUNLESS: + shell_endunless (buf, token->buf, token->len); + break; +#endif + default: + break; + } + token = token->next; + } + + return (token); + +} + +#endif diff --git a/src/h_script.h b/src/h_script.h new file mode 100644 index 0000000..e1098fe --- /dev/null +++ b/src/h_script.h @@ -0,0 +1,52 @@ +/* + * $Id: haserl.h,v 1.14 2005/11/18 14:43:10 nangel Exp $ + */ +#ifndef H_SCRIPT_H +#define H_SCRIPT_H 1 + + +/* Everything we care to know about a script */ +typedef struct { + char *name; /* pointer to name of script */ + int size; /* size of script in bytes */ + uid_t uid; /* user owner */ + gid_t gid; /* group owner */ + char *buf; /* pointer to malloc'ed buffer */ + size_t curpos; /* current position in buffer */ + void *next; /* next script in our chain */ + } script_t; + +/* tag types */ +#ifdef BASHEXTENSIONS +enum tag_t { HTML, RUN, INCLUDE, EVAL, COMMENT, IF, ELIF, ELSE, ENDIF, CASE, WHEN, OTHERWISE, ENDCASE, WHILE, ENDWHILE, UNTIL, ENDUNTIL, FOR, ENDFOR, UNLESS, ELUN, UNELSE, ENDUNLESS, NOOP }; +#else +enum tag_t { HTML, RUN, INCLUDE, EVAL, COMMENT, NOOP }; +#endif + + +/* token structure */ +typedef struct { + script_t *script; /* the parent script */ + enum tag_t tag; /* the token type */ + size_t len; /* length of token */ + char *buf; /* pointer to start of token */ + void *next; /* the next token in the chain */ + } token_t; + + + +/* h_script.c */ +script_t *load_script(char *filename, script_t *scriptlist); +void free_script_list(script_t *script); +token_t *push_token_on_list(token_t *tokenlist, script_t *scriptbuf, char *start, size_t len); +void free_token_list(token_t *tokenlist); + +#ifndef JUST_LUACSHELL + +token_t *build_token_list(script_t *scriptbuf, token_t *tokenlist); +void preprocess_token_list(token_t *tokenlist); +token_t *process_token_list(buffer_t *buf, token_t *tokenlist); + +#endif + +#endif /* !H_SCRIPT_H */ diff --git a/src/haserl.c b/src/haserl.c new file mode 100644 index 0000000..da7c755 --- /dev/null +++ b/src/haserl.c @@ -0,0 +1,917 @@ +/* -------------------------------------------------------------------------- + * core of haserl.cgi - a poor-man's php for embedded/lightweight environments + * Copyright (c) 2003-2007 Nathan Angelacos (nangel@users.sourceforge.net) + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 2, as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * ----- + * The x2c() and unescape_url() routines were taken from + * http://www.jmarshall.com/easy/cgi/getcgi.c.txt + * + * The comments in that text file state: + * + *** Written in 1996 by James Marshall, james@jmarshall.com, except + *** that the x2c() and unescape_url() routines were lifted directly + *** from NCSA's sample program util.c, packaged with their HTTPD. + *** For the latest, see http://www.jmarshall.com/easy/cgi/ + * ----- + * + ------------------------------------------------------------------------- */ + +#if HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <unistd.h> +#include <time.h> +#include <getopt.h> +#include <sys/mman.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <sys/fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <grp.h> + +#if HAVE_SIGNAL_H +#include <signal.h> +#endif + +#include "common.h" +#include "h_error.h" +#include "h_script.h" +#include "sliding_buffer.h" +#include "rfc2388.h" +#ifdef INCLUDE_BASHSHELL +#include "h_bash.h" +#endif + +#ifdef USE_LUA +#include <lua.h> +#include <lauxlib.h> +#include <lualib.h> +#include "h_lua_common.h" +#endif +#ifdef INCLUDE_LUASHELL +#include "h_lua.h" +#endif +#ifdef INCLUDE_LUACSHELL +#include "h_luac.h" +#endif + +#include "haserl.h" + +#ifndef TEMPDIR +#define TEMPDIR "/tmp" +#endif + +#ifndef MAX_UPLOAD_KB +#define MAX_UPLOAD_KB 2048 +#endif + +/* Refuse to disable the subshell */ +#ifndef SUBSHELL_CMD +#define SUBSHELL_CMD "/bin/sh" +#endif + +haserl_t global; + + +/* declare the shell_ function pointers here */ +void (*shell_exec) (buffer_t * buf, char *str); +void (*shell_echo) (buffer_t * buf, char *str, size_t len); +void (*shell_eval) (buffer_t * buf, char *str, size_t len); +void (*shell_setup) (char *, list_t *); +void (*shell_doscript) (buffer_t *, char *); +void (*shell_destroy) (void); + +#ifdef BASHEXTENSIONS +void (*shell_if) (buffer_t * buf, char *str, size_t len); +void (*shell_elif) (buffer_t * buf, char *str, size_t len); +void (*shell_else) (buffer_t * buf, char *str, size_t len); +void (*shell_endif) (buffer_t * buf, char *str, size_t len); +void (*shell_case) (buffer_t * buf, char *str, size_t len); +void (*shell_when) (buffer_t * buf, char *str, size_t len); +void (*shell_otherwise) (buffer_t * buf, char *str, size_t len); +void (*shell_endcase) (buffer_t * buf, char *str, size_t len); +void (*shell_while) (buffer_t * buf, char *str, size_t len); +void (*shell_endwhile) (buffer_t * buf, char *str, size_t len); +void (*shell_until) (buffer_t * buf, char *str, size_t len); +void (*shell_enduntil) (buffer_t * buf, char *str, size_t len); +void (*shell_for) (buffer_t * buf, char *str, size_t len); +void (*shell_endfor) (buffer_t * buf, char *str, size_t len); +void (*shell_unless) (buffer_t * buf, char *str, size_t len); +void (*shell_elun) (buffer_t * buf, char *str, size_t len); +void (*shell_unelse) (buffer_t * buf, char *str, size_t len); +void (*shell_endunless) (buffer_t * buf, char *str, size_t len); +#endif + +/* global shell execution function pointers. These point to the actual functions + that do the job, based on the language */ + +/* + * Command line / Config file directives When adding a long option, make sure + * to update the short_options as well + */ + +struct option ga_long_options[] = { + {"version", no_argument, 0, 'v'}, + {"help", no_argument, 0, 'h'}, + {"debug", no_argument, 0, 'd'}, + {"upload-limit", required_argument, 0, 'u'}, + {"upload-dir", required_argument, 0, 'U'}, + {"upload-handler", required_argument, 0, 'H'}, + {"accept-all", no_argument, 0, 'a'}, + {"accept-none", no_argument, 0, 'n'}, + {"shell", required_argument, 0, 's'}, + {"silent", no_argument, 0, 'S'}, + {0, 0, 0, 0} +}; + +const char *gs_short_options = "+vhdu:U:H:ans:S"; + +/* + * Convert 2 char hex string into char it represents + * (from http://www.jmarshall.com/easy/cgi) + */ +char +x2c (char *what) +{ + char digit; + + digit = (what[0] >= 'A' ? ((what[0] & 0xdf) - 'A') + 10 : (what[0] - '0')); + digit *= 16; + digit += (what[1] >= 'A' ? ((what[1] & 0xdf) - 'A') + 10 : (what[1] - '0')); + return (digit); +} + +/* + * unsescape %xx to the characters they represent + */ +/* Modified by Juris Feb 2007 */ +void +unescape_url (char *url) +{ + int i, j; + for (i = 0, j = 0; url[j]; ++i, ++j) + { + if ((url[i] = url[j]) != '%') + continue; + if (!url[j + 1] || !url[j + 2]) + break; + url[i] = x2c (&url[j + 1]); + j += 2; + } + url[i] = '\0'; +} + + + + +/* + * allocate memory or die, busybox style. + */ +void * +xmalloc (size_t size) +{ + void *buf; + if ((buf = malloc (size)) == NULL) + { + die_with_message (NULL, NULL, g_err_msg[E_MALLOC_FAIL]); + } + memset (buf, 0, size); + return buf; +} + + +/* + * realloc memory, or die xmalloc style. + */ +void * +xrealloc (void *buf, size_t size) +{ + if ((buf = realloc (buf, size)) == NULL) + { + die_with_message (NULL, NULL, g_err_msg[E_MALLOC_FAIL]); + } + return buf; +} + + +/* + * adds or replaces the "key=value" value in the env_list chain + * prefix is appended to the key (e.g. FORM_key=value) + */ +list_t * +myputenv (list_t * cur, char *str, char *prefix) +{ + list_t *prev = NULL; + size_t keylen; + char *entry = NULL; + char *temp = NULL; + int array = 0; + int len; + + temp = memchr (str, '=', strlen (str)); + /* if we don't have an equal sign, exit early */ + if (temp == 0) + { + return (cur); + } + + keylen = (size_t) (temp - str); + + /* is this an array */ + if (memcmp (str + keylen - 2, "[]", 2) == 0) + { + keylen = keylen - 2; + array = 1; + } + + entry = xmalloc (strlen (str) + strlen (prefix) + 1); + entry[0] = '\0'; + if (strlen (prefix)) + { + strncat (entry, prefix, strlen (prefix)); + } + + if (array == 1) + { + strncat (entry, str, keylen); + strcat (entry, str + keylen + 2); + } + else + { + strcat (entry, str); + } + + /* does the value already exist? */ + len = keylen + strlen (prefix) + 1; + while (cur != NULL) + { + if (memcmp (cur->buf, entry, len) == 0) + { + if (array == 1) + { + /* if an array, create a new string with this + * value added to the end of the old value(s) + */ + temp = xmalloc (strlen (cur->buf) + len + 1); + memmove (temp, cur->buf, strlen (cur->buf) + 1); + strcat (temp, "\n"); + strcat (temp, str + keylen + 3); + free (entry); + entry = temp; + } + /* delete the old entry */ + free (cur->buf); + if (prev != NULL) + prev->next = cur->next; + free (cur); + cur = prev; + } /* end if found a matching key */ + prev = cur; + if (cur) + { + cur = (list_t *) cur->next; + } + } /* end if matching key */ + + /* add the value to the end of the chain */ + cur = xmalloc (sizeof (list_t)); + cur->buf = entry; + if (prev != NULL) + prev->next = cur; + + return (cur); +} + +/* free list_t chain */ +void +free_list_chain (list_t * list) +{ + list_t *next; + + while (list) + { + next = list->next; + free (list->buf); + free (list); + list = next; + } +} + + + +/* readenv + * reads the current environment and popluates our environment chain + */ + +void +readenv (list_t * env) +{ + extern char **environ; + int count = 0; + + while (environ[count] != NULL) + { + myputenv (env, environ[count], global.nul_prefix); + count++; + } +} + + +/* CookieVars () + * if HTTP_COOKIE is passed as an environment variable, + * attempt to parse its values into environment variables + */ +void +CookieVars (list_t * env) +{ + char *qs; + char *token; + + if (getenv ("HTTP_COOKIE") != NULL) + { + qs = strdup (getenv ("HTTP_COOKIE")); + } + else + { + return; + } + + /** split on; to extract name value pairs */ + token = strtok (qs, ";"); + while (token) + { + // skip leading spaces + while (token[0] == ' ') + { + token++; + } + myputenv (env, token, global.var_prefix); + myputenv (env, token, global.cookie_prefix); + token = strtok (NULL, ";"); + } + free (qs); +} + + +/* SessionID + * Makes a uniqe SESSIONID environment variable for this script + */ + +void +sessionid (list_t * env) +{ + char session[29]; + + sprintf (session, "SESSIONID=%x%x", getpid (), (int) time (NULL)); + myputenv (env, session, global.nul_prefix); +} + +list_t * +wcversion (list_t * env) +{ + char version[200]; + sprintf (version, "HASERLVER=%s", PACKAGE_VERSION); + return (myputenv (env, version, global.nul_prefix)); +} + + +void +haserlflags (list_t * env) +{ + char buf[200]; + + snprintf (buf, 200, "HASERL_UPLOAD_DIR=%s", global.uploaddir); + myputenv (env, buf, global.nul_prefix); + + snprintf (buf, 200, "HASERL_UPLOAD_LIMIT=%lu", global.uploadkb); + myputenv (env, buf, global.nul_prefix); + + snprintf (buf, 200, "HASERL_ACCEPT_ALL=%d", global.acceptall); + myputenv (env, buf, global.nul_prefix); + + snprintf (buf, 200, "HASERL_SHELL=%s", global.shell); + myputenv (env, buf, global.nul_prefix); + +} + +/* + * Read cgi variables from query string, and put in environment + */ + +int +ReadCGIQueryString (list_t * env) +{ + char *qs; + char *token; + int i; + + if (getenv ("QUERY_STRING") != NULL) + { + qs = strdup (getenv ("QUERY_STRING")); + } + else + { + return (0); + } + + /* change plusses into spaces */ + for (i = 0; qs[i]; i++) + { + if (qs[i] == '+') + { + qs[i] = ' '; + } + }; + + /** split on & and ; to extract name value pairs */ + + token = strtok (qs, "&;"); + while (token) + { + unescape_url (token); + myputenv (env, token, global.var_prefix); + myputenv (env, token, global.get_prefix); + token = strtok (NULL, "&;"); + } + free (qs); + return (0); +} + + +/* + * Read cgi variables from stdin (for POST queries) + */ + +int +ReadCGIPOSTValues (list_t * env) +{ + size_t content_length = 0; + size_t max_len; + size_t i, j, x; + sliding_buffer_t sbuf; + buffer_t token; + unsigned char *data; + const char *CONTENT_LENGTH = "CONTENT_LENGTH"; + + if (getenv (CONTENT_LENGTH) == NULL) + return (0); + + if (getenv ("CONTENT_TYPE")) + { + if (strncasecmp (getenv ("CONTENT_TYPE"), "multipart/form-data", 19) + == 0) + { + /* This is a mime request, we need to go to the mime handler */ + i = rfc2388_handler (env); + return (i); + } + } + + s_buffer_init (&sbuf, 32768); + sbuf.fh = STDIN; + if ((getenv (CONTENT_LENGTH)) || + (strtoul (getenv(CONTENT_LENGTH), NULL, 10) == 0 )) + { + sbuf.maxread = strtoul (getenv (CONTENT_LENGTH), NULL, 10); + } + buffer_init (&token); + + + /* Allow 2MB content, unless they have a global upload set */ + max_len = ((global.uploadkb == 0) ? 2048 : global.uploadkb) *1024; + + do + { + /* x is true if this token ends with a matchstr or is at the end of stream */ + x = s_buffer_read (&sbuf, "&"); + content_length += sbuf.len; + if (content_length > max_len) + { + die_with_message (NULL, NULL, + "Attempted to send content larger than allowed limits."); + } + + if ((x == 0) || (token.data)) + { + buffer_add (&token, (char *) sbuf.segment, sbuf.len); + } + + if (x) + { + data = sbuf.segment; + sbuf.segment[sbuf.len] = '\0'; + if (token.data) + { + /* add the ASCIIZ */ + buffer_add (&token, sbuf.segment + sbuf.len, 1); + data = token.data; + } + + /* change plusses into spaces */ + j = strlen ((char *) data); + for (i = 0; i <= j; i++) + { + if (data[i] == '+') + { + data[i] = ' '; + } + } + unescape_url ((char *) data); + myputenv (env, (char *) data, global.var_prefix); + myputenv (env, (char *) data, global.post_prefix); + if (token.data) + { + buffer_reset (&token); + } + } + } + while (!sbuf.eof); + s_buffer_destroy (&sbuf); + buffer_destroy (&token); + return (0); +} + + +int +parseCommandLine (int argc, char *argv[]) +{ + int c; + int option_index = 0; + + /* set optopt and optind to 0 to reset getopt_long - + * we may call it multiple times + */ + optopt = 0; + optind = 0; + + while ((c = getopt_long (argc, argv, gs_short_options, + ga_long_options, &option_index)) != -1) + { + switch (c) + { + case 'd': + global.debug = TRUE; + break; + case 's': + global.shell = optarg; + break; + case 'S': + global.silent = TRUE; + break; + case 'u': + if (optarg) + { + global.uploadkb = atoi (optarg); + } + else + { + global.uploadkb = MAX_UPLOAD_KB; + } + break; + case 'a': + global.acceptall = TRUE; + break; + case 'n': + global.acceptall = NONE; + break; + case 'U': + global.uploaddir = optarg; + break; + case 'H': + global.uploadhandler = optarg; + break; + case 'v': + case 'h': + printf ("This is " PACKAGE_NAME " version " PACKAGE_VERSION "" + " (http://haserl.sourceforge.net)\n"); + exit (0); + break; + } + } + return (optind); +} + +int +BecomeUser (uid_t uid, gid_t gid) +{ + /* This silently fails if it doesn't work */ + /* Following is from Timo Teras */ + if (getuid () == 0) + setgroups (1, &gid); + + setgid (gid); + setgid (getgid ()); + + setuid (uid); + setuid (getuid ()); + + return (0); +} + +/* + * Assign default values to the global structure + */ + +void +assignGlobalStartupValues () +{ + global.uploadkb = 0; /* how big an upload do we allow (0 for none) */ + global.shell = SUBSHELL_CMD; /* The shell we use */ + global.silent = FALSE; /* We do print errors if we find them */ + global.uploaddir = TEMPDIR; /* where to upload to */ + global.uploadhandler = NULL; /* the upload handler */ + global.debug = FALSE; /* Not in debug mode. */ + global.acceptall = FALSE; /* don't allow POST data for GET method */ + global.uploadlist = NULL; /* we don't have any uploaded files */ + global.var_prefix = "FORM_"; + global.get_prefix = "GET_"; + global.post_prefix = "POST_"; + global.cookie_prefix = "COOKIE_"; + global.nul_prefix = ""; + +} + + +void +unlink_uploadlist () +{ + token_t *me; + me = global.uploadlist; + + while (me) + { + unlink (me->buf); + free (me->buf); + me = me->next; + } + +} + + + +/*------------------------------------------------------------------------- + * + * Main + * + *------------------------------------------------------------------------*/ + +int +main (int argc, char *argv[]) +{ +#ifndef JUST_LUACSHELL + token_t *tokenchain = NULL; + buffer_t script_text; +#endif + script_t *scriptchain; + + int retval = 0; + char *filename = NULL; + + argv_t *av = NULL; + char **av2 = argv; + int av2c = argc; + + int command; + int count; + + list_t *env = NULL; + + assignGlobalStartupValues (); +#ifndef JUST_LUACSHELL + buffer_init (&script_text); +#endif + + /* if more than argv[1] and argv[1] is not a file */ + switch (argc) + { + case 1: + /* we were run, instead of called as a shell script */ + puts ("This is " PACKAGE_NAME " version " PACKAGE_VERSION "\n" + "This program runs as a cgi interpeter, not interactively\n" + "Please see: http://haserl.sourceforge.net\n" +#ifdef USE_LUA + "This version includes Lua (precompiled" +#ifdef INCLUDE_LUASHELL + " and interpreted" +#endif + ")\n" +#endif +#ifdef BASHEXTENSIONS + "Unsupported bash extensions supplied by simnux enabled\n" +#endif + ); + return (0); + break; + default: /* more than one */ + /* split combined #! args - linux bundles them as one */ + command = argc_argv (argv[1], &av, ""); + + if (command > 1) + { + /* rebuild argv into new av2 */ + av2c = argc - 1 + command; + av2 = xmalloc (sizeof (char *) * av2c); + av2[0] = argv[0]; + for (count = 1; count <= command; count++) + { + av2[count] = av[count-1].string; + } + for (; count < av2c; count++) { + av2[count] = argv[count - command + 1]; + } + } + + parseCommandLine (av2c, av2); + free (av); + if (av2 != argv) free (av2); + + if (optind < av2c) + { + filename = av2[optind]; + } + else + { + die_with_message (NULL, NULL, "No script file specified"); + } + + break; + } + + + scriptchain = load_script (filename, NULL); +/* drop permissions */ + BecomeUser (scriptchain->uid, scriptchain->gid); + + /* populate the function pointers based on the shell selected */ + if (strcmp (global.shell, "lua") && strcmp (global.shell, "luac")) + /* default to "bash" */ + { +#ifdef INCLUDE_BASHSHELL + shell_exec = &bash_exec; + shell_echo = &bash_echo; + shell_eval = &bash_eval; + shell_setup = &bash_setup; + shell_doscript = &bash_doscript; + shell_destroy = &bash_destroy; + +#ifdef BASHEXTENSIONS + shell_if = &bash_if; + shell_elif = &bash_elif; + shell_else = &bash_else; + shell_endif = &bash_endif; + shell_case = &bash_case; + shell_when = &bash_when; + shell_otherwise = &bash_otherwise; + shell_endcase = &bash_endcase; + shell_while = &bash_while; + shell_endwhile = &bash_endwhile; + shell_until = &bash_until; + shell_enduntil = &bash_enduntil; + shell_for = &bash_for; + shell_endfor = &bash_endfor; + shell_unless = &bash_unless; + shell_elun = &bash_elun; + shell_unelse = &bash_unelse; + shell_endunless = &bash_endunless; +#endif + +#else + die_with_message (NULL, NULL, "Bash shell is not enabled."); +#endif + } + else + { +#ifdef USE_LUA + shell_setup = &lua_common_setup; + shell_destroy = &lua_common_destroy; + global.var_prefix = "FORM."; + global.nul_prefix = "ENV."; + + if (global.shell[3] == 'c') /* luac only */ +#ifdef INCLUDE_LUACSHELL + shell_doscript = &luac_doscript; +#else + die_with_message (NULL, NULL, "Compiled Lua shell is not enabled."); +#endif + else + { +#ifdef INCLUDE_LUASHELL + shell_exec = &lua_exec; + shell_echo = &lua_echo; + shell_eval = &lua_eval; + shell_doscript = &lua_doscript; +#else + die_with_message (NULL, NULL, "Standard Lua shell is not enabled."); +#endif + } +#else + die_with_message (NULL, NULL, "Lua shells are not enabled."); +#endif + } + +/* Read the current environment into our chain */ + env = wcversion (env); + readenv (env); + sessionid (env); + haserlflags (env); + +#ifndef JUST_LUACSHELL + if (strcmp (global.shell, "luac")) + { + tokenchain = build_token_list (scriptchain, NULL); + preprocess_token_list (tokenchain); + } +#endif + +/* Read the request data */ + if (global.acceptall != NONE) + { + /* If we have a request method, and we were run as a #! style script */ + CookieVars (env); + if (getenv ("REQUEST_METHOD")) + { + if (strcasecmp (getenv ("REQUEST_METHOD"), "GET") == 0) + { + if (global.acceptall == TRUE) + ReadCGIPOSTValues (env); + ReadCGIQueryString (env); + } + + if (strcasecmp (getenv ("REQUEST_METHOD"), "POST") == 0) + { + if (global.acceptall == TRUE) + retval = ReadCGIQueryString (env); + retval = ReadCGIPOSTValues (env); + } + } + } + +/* build a copy of the script to send to the shell */ +#ifndef JUST_LUACSHELL + if (strcmp (global.shell, "luac")) + { + process_token_list (&script_text, tokenchain); + } +#endif + + /* run the script */ + if (global.debug == TRUE) + { +#ifndef JUST_LUACSHELL + if (getenv ("REQUEST_METHOD")) + { + write (1, "Content-Type: text/plain\n\n", 26); + } + write (1, script_text.data, script_text.ptr - script_text.data); +#else + die_with_message (NULL, NULL, + "Debugging output doesn't work with the compiled Lua shell."); +#endif + } + else + { + shell_setup (global.shell, env); +#ifdef JUST_LUACSHELL + shell_doscript (NULL, scriptchain->name); +#else + shell_doscript (&script_text, scriptchain->name); +#endif + shell_destroy (); + } + + + if (global.uploadlist) + { + unlink_uploadlist (); + free_token_list (global.uploadlist); + } + +#ifndef JUST_LUACSHELL + /* destroy the script */ + buffer_destroy (&script_text); + free_token_list (tokenchain); +#endif + + free_list_chain (env); + free_script_list (scriptchain); + + return (0); + +} diff --git a/src/haserl.h b/src/haserl.h new file mode 100644 index 0000000..e7b0e1f --- /dev/null +++ b/src/haserl.h @@ -0,0 +1,77 @@ +#ifndef _HASERL_H +#define _HASERL_H 1 + + +/* Just a silly construct to contain global variables */ +typedef struct +{ + unsigned long uploadkb; /* how big an upload do we allow (0 for none)*/ + char *shell; /* The shell we use */ + char *uploaddir; /* where we upload to */ + char *uploadhandler; /* a handler for uploads */ + char *var_prefix; /* what name we give to FORM variables */ + char *get_prefix; /* what name we give to POST variables */ + char *post_prefix; /* what name we give to POST variables */ + char *cookie_prefix; /* what name we give to COOKIE variables */ + char *nul_prefix; /* what name we give to environment variables*/ + token_t *uploadlist; /* a linked list of pathspecs */ + int debug; /* true if in "debug" mode */ + int acceptall; /* true if we'll accept POST data on + GETs and vice versa */ + int silent; /* true if we never print errors */ +} haserl_t; + +extern haserl_t global; + +char x2c(char *what); +void unescape_url(char *url); +void *xmalloc (size_t size); +void *xrealloc (void *buf, size_t size); +list_t *myputenv(list_t *cur, char *str, char *prefix); +void free_list_chain ( list_t *); +void readenv(list_t *env); +void CookieVars(list_t *env); +void sessionid(list_t *env); +list_t *wcversion(list_t *env); +void haserlflags(list_t *env); +int ReadCGIQueryString(list_t *env); +int ReadCGIPOSTValues(list_t *env); +int LineToStr(char *string, size_t max); +int ReadMimeEncodedInput(list_t *env); +void PrintParseError(char *error, int linenum); +int parseCommandLine(int argc, char *argv[]); +int BecomeUser(uid_t uid, gid_t gid); +void assignGlobalStartupValues(void); +void unlink_uploadlist (void); +int main(int argc, char *argv[]); + + +extern void (*shell_exec)(buffer_t *buf, char *str); +extern void (*shell_echo)(buffer_t *buf, char *str, size_t len); +extern void (*shell_eval)(buffer_t *buf, char *str, size_t len); +extern void (*shell_setup)( char *, list_t *); +extern void (*shell_doscript)( buffer_t *, char *); +extern void (*shell_destroy) (void); + +#ifdef BASHEXTENSIONS +extern void (*shell_if)(buffer_t *buf, char *str, size_t len); +extern void (*shell_elif)(buffer_t *buf, char *str, size_t len); +extern void (*shell_else)(buffer_t *buf, char *str, size_t len); +extern void (*shell_endif)(buffer_t *buf, char *str, size_t len); +extern void (*shell_case)(buffer_t *buf, char *str, size_t len); +extern void (*shell_when)(buffer_t *buf, char *str, size_t len); +extern void (*shell_otherwise)(buffer_t *buf, char *str, size_t len); +extern void (*shell_endcase)(buffer_t *buf, char *str, size_t len); +extern void (*shell_while)(buffer_t *buf, char *str, size_t len); +extern void (*shell_endwhile)(buffer_t *buf, char *str, size_t len); +extern void (*shell_until)(buffer_t *buf, char *str, size_t len); +extern void (*shell_enduntil)(buffer_t *buf, char *str, size_t len); +extern void (*shell_for)(buffer_t *buf, char *str, size_t len); +extern void (*shell_endfor)(buffer_t *buf, char *str, size_t len); +extern void (*shell_unless)(buffer_t *buf, char *str, size_t len); +extern void (*shell_elun)(buffer_t *buf, char *str, size_t len); +extern void (*shell_unelse)(buffer_t *buf, char *str, size_t len); +extern void (*shell_endunless)(buffer_t *buf, char *str, size_t len); +#endif +#endif /* !_HASERL_H */ + diff --git a/src/haserl_lualib.inc b/src/haserl_lualib.inc new file mode 100644 index 0000000..e533384 --- /dev/null +++ b/src/haserl_lualib.inc @@ -0,0 +1,90 @@ +/* This file was automatically generated from haserl_lualib.lua. DO NOT EDIT */ + +static const unsigned char haserl_lualib[] = { + 27, 76,117, 97, 81, 0, 1, 4, 4, 4, 8, 0, 19, 0, 0, 0, + 64,104, 97,115,101,114,108, 95,108,117, 97,108,105, 98, 46,108, + 117, 97, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 16, + 0, 0, 0, 10, 0, 0, 0, 74, 0, 0, 0,138, 0, 0, 0,135, + 128, 0, 0, 71, 64, 0, 0, 7, 0, 0, 0, 5, 0, 0, 0,100, + 0, 0, 0, 9, 64,128,129, 5, 0, 0, 0,100, 64, 0, 0, 9, + 64, 0,130, 5, 0, 0, 0,100,128, 0, 0, 9, 64,128,130, 30, + 0,128, 0, 6, 0, 0, 0, 4, 7, 0, 0, 0,104, 97,115,101, + 114,108, 0, 4, 5, 0, 0, 0, 70, 79, 82, 77, 0, 4, 4, 0, + 0, 0, 69, 78, 86, 0, 4, 9, 0, 0, 0,115,101,116,102,105, + 101,108,100, 0, 4, 9, 0, 0, 0,103,101,116,102,105,101,108, + 100, 0, 4, 9, 0, 0, 0,109,121,112,117,116,101,110,118, 0, + 3, 0, 0, 0, 0, 0, 0, 0, 23, 0, 0, 0, 37, 0, 0, 0, + 0, 2, 0, 10, 29, 0, 0, 0,133, 0, 0, 0,197, 64, 0, 0, + 198,128,192, 1, 0, 1, 0, 0, 65,193, 0, 0,220, 0,129, 1, + 22,128, 4,128, 5, 2, 1, 0, 64, 2, 0, 3, 28,130, 0, 1, + 26, 2, 0, 0, 22,192, 0,128, 5, 2, 1, 0, 64, 2, 0, 3, + 28,130, 0, 1,128, 1, 0, 4, 23, 64,193, 3, 22,128, 1,128, + 6,130, 1, 1, 26, 66, 0, 0, 22, 0, 0,128, 10, 2, 0, 0, + 137, 0, 2, 3,134,128, 1, 1, 22, 0, 0,128,137, 64, 0, 3, + 225,128, 0, 0, 22,128,250,127, 30, 0,128, 0, 6, 0, 0, 0, + 4, 3, 0, 0, 0, 95, 71, 0, 4, 7, 0, 0, 0,115,116,114, + 105,110,103, 0, 4, 6, 0, 0, 0,103,102,105,110,100, 0, 4, + 15, 0, 0, 0, 40, 91, 37,119, 95, 37, 45, 93, 43, 41, 40, 46, + 63, 41, 0, 4, 9, 0, 0, 0,116,111,110,117,109, 98,101,114, + 0, 4, 2, 0, 0, 0, 46, 0, 0, 0, 0, 0, 29, 0, 0, 0, + 25, 0, 0, 0, 26, 0, 0, 0, 26, 0, 0, 0, 26, 0, 0, 0, + 26, 0, 0, 0, 26, 0, 0, 0, 26, 0, 0, 0, 27, 0, 0, 0, + 27, 0, 0, 0, 27, 0, 0, 0, 27, 0, 0, 0, 27, 0, 0, 0, + 28, 0, 0, 0, 28, 0, 0, 0, 28, 0, 0, 0, 28, 0, 0, 0, + 30, 0, 0, 0, 30, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, + 31, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 32, 0, 0, 0, + 32, 0, 0, 0, 34, 0, 0, 0, 26, 0, 0, 0, 35, 0, 0, 0, + 37, 0, 0, 0, 8, 0, 0, 0, 2, 0, 0, 0,102, 0, 0, 0, + 0, 0, 28, 0, 0, 0, 2, 0, 0, 0,118, 0, 0, 0, 0, 0, + 28, 0, 0, 0, 2, 0, 0, 0,116, 0, 1, 0, 0, 0, 28, 0, + 0, 0, 16, 0, 0, 0, 40,102,111,114, 32,103,101,110,101,114, + 97,116,111,114, 41, 0, 6, 0, 0, 0, 28, 0, 0, 0, 12, 0, + 0, 0, 40,102,111,114, 32,115,116, 97,116,101, 41, 0, 6, 0, + 0, 0, 28, 0, 0, 0, 14, 0, 0, 0, 40,102,111,114, 32, 99, + 111,110,116,114,111,108, 41, 0, 6, 0, 0, 0, 28, 0, 0, 0, + 2, 0, 0, 0,119, 0, 7, 0, 0, 0, 26, 0, 0, 0, 2, 0, + 0, 0,100, 0, 7, 0, 0, 0, 26, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 39, 0, 0, 0, 45, 0, 0, 0, 0, 1, 0, 8, + 12, 0, 0, 0, 69, 0, 0, 0,133, 64, 0, 0,134,128, 64, 1, + 192, 0, 0, 0, 1,193, 0, 0,156, 0,129, 1, 22, 0, 0,128, + 70, 64,129, 0,161, 64, 0, 0, 22, 0,255,127, 94, 0, 0, 1, + 30, 0,128, 0, 4, 0, 0, 0, 4, 3, 0, 0, 0, 95, 71, 0, + 4, 7, 0, 0, 0,115,116,114,105,110,103, 0, 4, 6, 0, 0, + 0,103,102,105,110,100, 0, 4, 7, 0, 0, 0, 91, 37,119, 95, + 93, 43, 0, 0, 0, 0, 0, 12, 0, 0, 0, 40, 0, 0, 0, 41, + 0, 0, 0, 41, 0, 0, 0, 41, 0, 0, 0, 41, 0, 0, 0, 41, + 0, 0, 0, 41, 0, 0, 0, 42, 0, 0, 0, 41, 0, 0, 0, 42, + 0, 0, 0, 44, 0, 0, 0, 45, 0, 0, 0, 6, 0, 0, 0, 2, + 0, 0, 0,102, 0, 0, 0, 0, 0, 11, 0, 0, 0, 2, 0, 0, + 0,118, 0, 1, 0, 0, 0, 11, 0, 0, 0, 16, 0, 0, 0, 40, + 102,111,114, 32,103,101,110,101,114, 97,116,111,114, 41, 0, 6, + 0, 0, 0, 10, 0, 0, 0, 12, 0, 0, 0, 40,102,111,114, 32, + 115,116, 97,116,101, 41, 0, 6, 0, 0, 0, 10, 0, 0, 0, 14, + 0, 0, 0, 40,102,111,114, 32, 99,111,110,116,114,111,108, 41, + 0, 6, 0, 0, 0, 10, 0, 0, 0, 2, 0, 0, 0,119, 0, 7, + 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 47, + 0, 0, 0, 53, 0, 0, 0, 0, 2, 0, 6, 20, 0, 0, 0,133, + 0, 0, 0,134, 64, 64, 1,192, 0, 0, 0, 1,129, 0, 0, 65, + 193, 0, 0,156,128, 0, 2, 0, 0, 0, 1,133, 0, 0, 0,134, + 64, 64, 1,192, 0, 0, 0, 1, 1, 1, 0, 65,193, 0, 0,156, + 128, 0, 2, 0, 0, 0, 1,133, 64, 1, 0,134,128, 65, 1,192, + 0, 0, 0, 0, 1,128, 0,156, 64,128, 1, 30, 0,128, 0, 7, + 0, 0, 0, 4, 7, 0, 0, 0,115,116,114,105,110,103, 0, 4, + 5, 0, 0, 0,103,115,117, 98, 0, 4, 7, 0, 0, 0, 91, 92, + 93, 92, 91, 93, 0, 4, 2, 0, 0, 0, 46, 0, 4, 6, 0, 0, + 0, 91, 92, 46, 93, 43, 0, 4, 7, 0, 0, 0,104, 97,115,101, + 114,108, 0, 4, 9, 0, 0, 0,115,101,116,102,105,101,108,100, + 0, 0, 0, 0, 0, 20, 0, 0, 0, 49, 0, 0, 0, 49, 0, 0, + 0, 49, 0, 0, 0, 49, 0, 0, 0, 49, 0, 0, 0, 49, 0, 0, + 0, 49, 0, 0, 0, 50, 0, 0, 0, 50, 0, 0, 0, 50, 0, 0, + 0, 50, 0, 0, 0, 50, 0, 0, 0, 50, 0, 0, 0, 50, 0, 0, + 0, 52, 0, 0, 0, 52, 0, 0, 0, 52, 0, 0, 0, 52, 0, 0, + 0, 52, 0, 0, 0, 53, 0, 0, 0, 2, 0, 0, 0, 4, 0, 0, + 0,107,101,121, 0, 0, 0, 0, 0, 19, 0, 0, 0, 6, 0, 0, + 0,118, 97,108,117,101, 0, 0, 0, 0, 0, 19, 0, 0, 0, 0, + 0, 0, 0, 16, 0, 0, 0, 21, 0, 0, 0, 21, 0, 0, 0, 21, + 0, 0, 0, 21, 0, 0, 0, 21, 0, 0, 0, 21, 0, 0, 0, 23, + 0, 0, 0, 37, 0, 0, 0, 23, 0, 0, 0, 39, 0, 0, 0, 45, + 0, 0, 0, 39, 0, 0, 0, 47, 0, 0, 0, 53, 0, 0, 0, 47, + 0, 0, 0, 53, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; diff --git a/src/haserl_lualib.lua b/src/haserl_lualib.lua new file mode 100644 index 0000000..94e0e43 --- /dev/null +++ b/src/haserl_lualib.lua @@ -0,0 +1,54 @@ +-- -------------------------------------------------------------------------- +-- haserl luascript library +-- $Id: haserl.c,v 1.32 2005/11/22 15:56:42 nangel Exp $ +-- Copyright (c) 2003-2007 Nathan Angelacos (nangel@users.sourceforge.net) +-- +-- This program is free software; you can redistribute it and/or modify +-- it under the terms of the GNU General Public License, version 2, +-- as published by the Free Software Foundation. +-- +-- This program is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +-- General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program; if not, write to the Free Software +-- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +-- +-- -------------------------------------------------------------------------- + +haserl, FORM, ENV = {}, {}, {} + +function haserl.setfield (f, v) + -- From programming in Lua 1st Ed. + local t = _G -- start with the table of globals + for w, d in string.gfind(f, '([%w_%-]+)(.?)') do + if (tonumber(w)) then + w = tonumber(w) + end + if d == '.' then -- not last field? + t[w] = t[w] or {} -- create table if absent + t = t[w] -- get the table + else -- last field + t[w] = v -- do the assignment + end + end +end + +function haserl.getfield (f) + local v = _G -- start with the table of globals + for w in string.gfind(f, '[%w_]+') do + v = v[w] + end + return v +end + +function haserl.myputenv(key, value) + -- convert key to dotted form + key = string.gsub(key, '[\\]\\[]', '.' ) + key = string.gsub(key, '[\\.]+', '.' ) + -- and create a table if necessary + haserl.setfield (key, value) +end + diff --git a/src/lua2c.c b/src/lua2c.c new file mode 100644 index 0000000..f1f7abe --- /dev/null +++ b/src/lua2c.c @@ -0,0 +1,97 @@ +/* -------------------------------------------------------------------------- + * a simple lua 2 c function converter - a simple luac + bin2c + * Copyright (c) 2007 Nathan Angelacos (nangel@users.sourceforge.net) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * ------------------------------------------------------------------------- */ + + +/* This program loads the source file, and then uses lua_dump to output it + * to a c const char array. Because lua_dump is used instead of the internal + * luaU_dump function, debugging info is in the output array. If you want + * to compile the array without debugging information, first use luac on the + * .lua source, and then run lua2c on that output: + * luac -s -o foo haserl_lualib.lua + * lua2c haserl_lualib foo >haserl_lualib.inc + */ + +#if HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include <lua.h> +#include <lauxlib.h> +#include <lualib.h> + +lua_State *lua_vm = NULL; + + +static void +loadit (char *filename) +{ + +lua_vm = luaL_newstate(); +luaL_openlibs (lua_vm); + +if (luaL_loadfile(lua_vm, filename)) { + puts (lua_tostring(lua_vm, -1)); + exit (-1); + } + +} + +static int writer (lua_State* L, const void* p, size_t size, void* u) +{ + static int count = 0; + int i; + for (i=0; i < size; i++ ) { + if ((count) && (count % 16) == 0 ) printf ("\n "); + printf ("%3d,", *((unsigned char *) (p+i))); + count++; + } + + return (0); +} + + +static void +dumpit() +{ +lua_dump (lua_vm, writer, NULL); +} + + +int +main (int argc, char *argv[]) { + if (argc != 3) { + printf("usage: %s varname luasource >output\n", argv[0]); + return (-1); + } + + loadit (argv[2]); + + + printf ("/* This file was automatically generated from %s. DO NOT EDIT */\n\n", argv[2]); + printf ("static const unsigned char %s[] = { \n ", argv[1]); + dumpit(); + printf ("\n};\n"); + + return (0); +} diff --git a/src/rfc2388.c b/src/rfc2388.c new file mode 100644 index 0000000..8aa5d19 --- /dev/null +++ b/src/rfc2388.c @@ -0,0 +1,508 @@ +/* -------------------------------------------------------------------------- + * multipart/form-data handler functions (obviously, see rfc2388 for info) + * Copyright (c) 2007 Nathan Angelacos (nangel@users.sourceforge.net) + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 2, as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + ------------------------------------------------------------------------- */ + +#if HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <unistd.h> +#include <time.h> +#include <sys/mman.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <sys/fcntl.h> +#include <stdlib.h> +#include <string.h> + +#if HAVE_SIGNAL_H +#include <signal.h> +#endif + +#include "common.h" +#include "h_error.h" +#include "h_script.h" +#include "h_bash.h" +#include "sliding_buffer.h" +#include "rfc2388.h" + +#include "haserl.h" + +void +empty_stdin (void) +{ + char c[2000]; + while (read (STDIN_FILENO, &c, 2000)) + { + }; +} + + +void +mime_var_init (mime_var_t * obj) +{ + obj->name = NULL; + obj->filename = NULL; + obj->type = NULL; + obj->tempname = NULL; + buffer_init (&(obj->value)); + obj->fh = 0; +} + +void +mime_var_destroy (mime_var_t * obj) +{ + int status; + + if (obj->name) + { + free (obj->name); + obj->name = NULL; + } + if (obj->filename) + { + free (obj->filename); + obj->filename = NULL; + } + if (obj->type) + { + free (obj->type); + obj->type = NULL; + } + if (obj->tempname) + { + free (obj->tempname); + obj->tempname = NULL; + } + buffer_destroy (&(obj->value)); + if (obj->fh) + { + close (obj->fh); + if (global.uploadhandler) + { + wait (&status); + } + obj->fh = 0; + } +} + +char * +mime_substr (char *start, int len) +{ + char *ptr; + + if (!start) + return NULL; + if (len < 0) + return NULL; + ptr = xmalloc (len + 2); + memcpy (ptr, start, len); + return ptr; + +} + +void +mime_tag_add (mime_var_t * obj, char *str) +{ + char *a = NULL; + char *b = NULL; + static char *tag[] = { "name=\"", "filename=\"", "Content-Type: " }; + + a = strcasestr (str, tag[0]); + if (a) + { + a += strlen (tag[0]); + b = strchr (a, '"'); + if (!obj->name) + obj->name = mime_substr (a, b - a); + } + + a = strcasestr (str, tag[1]); + if (a) + { + a += strlen (tag[1]); + b = strchr (a, '"'); + if (!obj->filename) + obj->filename = mime_substr (a, b - a); + } + + a = strcasestr (str, tag[2]); + if (a) + { + a += strlen (tag[2]); + b = a + strlen (a); + if (!obj->type) + obj->type = mime_substr (a, b - a); + } +} + +void +mime_var_putenv (list_t * env, mime_var_t * obj) +{ + buffer_t buf; + buffer_init (&buf); + if (obj->name) + { + buffer_add (&(obj->value), "", 1); + buffer_add (&buf, obj->name, strlen (obj->name)); + buffer_add (&buf, "=", 1); + buffer_add (&buf, (char *) obj->value.data, + strlen ((char *) obj->value.data) + 1); + myputenv (env, (char *) buf.data, global.var_prefix); + myputenv (env, (char *) buf.data, global.post_prefix); + buffer_reset (&buf); + } + if (obj->filename) + { + buffer_add (&buf, obj->name, strlen (obj->name)); + buffer_add (&buf, "_name=", 6); + buffer_add (&buf, obj->filename, strlen (obj->filename) + 1); + myputenv (env, (char *) buf.data, global.var_prefix); + myputenv (env, (char *) buf.data, global.post_prefix); + buffer_reset (&buf); + } + buffer_destroy (&buf); +} + +void +mime_exec (mime_var_t * obj, char *fifo) +{ + + int pid; + char *av[4]; + char *type, *filename, *name; + char *c; + int fh; + + pid = fork (); + if (pid == -1) + { + empty_stdin (); + die_with_message (NULL, NULL, g_err_msg[E_SUBSHELL_FAIL]); + } + + if (pid == 0) + { + + /* store the content type, filename, and form name */ + /* we do not use global.var_prefix because it could be lua or shell + * or something else, and we are only shell here */ + if (obj->type) + { + + type = xmalloc (13 + strlen (obj->type) + 1); + sprintf (type, "CONTENT_TYPE=%s", obj->type); + putenv (type); + } + if (obj->filename) + { + filename = xmalloc (9 + strlen (obj->filename) + 1); + sprintf (filename, "FILENAME=%s", obj->filename); + putenv (filename); + } + + if (obj->name) + { + name = xmalloc (5 + strlen (obj->name) + 1); + sprintf (name, "NAME=%s", obj->name); + putenv (name); + } + + av[0] = global.uploadhandler; + av[1] = fifo; + av[2] = NULL; + execv (av[0], av); + /* if we get here, we had a failure. Not much we can do. + * We are the child, so we can't even warn the parent */ + fh = open (fifo, O_RDONLY); + while (read (fh, &c, 1)) + { + } + exit (-1); + } + else + { + /* I'm parent */ + } + + /* control should get to this point only in the parent. + */ +} /* end mime_exec */ + +void +mime_var_open_target (mime_var_t * obj) +{ + char *tmpname; + token_t *curtoken; + curtoken = global.uploadlist; + int ok; + + /* if upload_limit is zero, we die right here */ + if (global.uploadkb == 0) + { + empty_stdin (); + die_with_message (NULL, NULL, "File uploads are not allowed."); + } + + + ok = -1; + tmpname = xmalloc (strlen (global.uploaddir) + 8); + strcpy (tmpname, global.uploaddir); + strcat (tmpname, "/XXXXXX"); + obj->fh = mkstemp (tmpname); + + if (obj->fh == -1) + { + ok = 0; + } + + /* reuse the name as a fifo if we have a handler. We do this + * because tempnam uses TEMPDIR if defined, among other bugs + */ + if ((ok) && global.uploadhandler) + { + + /* I have a handler */ + close (obj->fh); + unlink (tmpname); + if (mkfifo (tmpname, 0600)) + ok = 0; + /* you must open the fifo for reading before writing + * on non linux systems + */ + if (ok) + { + mime_exec (obj, tmpname); + obj->fh = open (tmpname, O_WRONLY); + } + if (obj->fh == -1) + ok = 0; + } + else + { + buffer_add (&(obj->value), tmpname, strlen (tmpname)); + } + + if (!ok) + { + empty_stdin (); + die_with_message (NULL, NULL, g_err_msg[E_FILE_OPEN_FAIL], tmpname); + } + + curtoken = + push_token_on_list (curtoken, NULL, tmpname, strlen (tmpname) + 1); + if (global.uploadlist == NULL) + { + global.uploadlist = curtoken; + } + +} + + + + +void +mime_var_writer (mime_var_t * obj, char *str, int len) +{ + /* if not a file upload, then just a normal variable */ + if (!obj->filename) + { + buffer_add (&(obj->value), str, len); + } + + /* if a file upload, but don't have an open filehandle, open one */ + if ((!obj->fh) && (obj->filename)) + mime_var_open_target (obj); + + /* if we have an open file, write the chunk */ + if (obj->fh) + { + write (obj->fh, str, len); + } +} + +/* + * Read multipart/form-data input (RFC2388), typically used when + * uploading a file. + */ + +int +rfc2388_handler (list_t * env) +{ + enum mime_state_t + { DISCARD, BOUNDARY, HEADER, CONTENT }; + + + int state; + int i, x; + unsigned long max_len, content_length; + sliding_buffer_t sbuf; + char *crlf = "\r\n"; + char *boundary; + char *str; + buffer_t buf; + mime_var_t var; + + /* get the boundary info */ + str = getenv ("CONTENT_TYPE"); + i = strlen (str) - 9; + while ((i >= 0) && (memcmp ("boundary=", str + i, 9))) + { + i--; + } + if (i == -1) + { + empty_stdin (); + die_with_message (NULL, NULL, "No Mime Boundary Information Found"); + } + + i = i + 9; + if (str[i] == '"') + i++; + + boundary = xmalloc (strlen (str + i) + 5); /* \r\n-- + NULL */ + memcpy (boundary, crlf, 2); + memcpy (boundary + 2, "--", 2); + memcpy (boundary + 4, str + i, strlen (str + i) + 1); + if ((i > 0) && (str[i - 1] == '"')) + { + while ((boundary[i]) && (boundary[i] != '"')) + i++; + boundary[i] = '\0'; + } + + /* Allow 2MB content, unless they have a global upload set */ + max_len = ((global.uploadkb == 0) ? 2048 : global.uploadkb) *1024; + content_length = 0; + + /* initialize a 128K sliding buffer */ + s_buffer_init (&sbuf, 1024 * 128); + sbuf.fh = STDIN; + if (getenv ("CONTENT_LENGTH")) + { + sbuf.maxread = strtoul (getenv ("CONTENT_LENGTH"), NULL, 10); + } + + /* initialize the buffer, and make sure it doesn't point to null */ + buffer_init (&buf); + buffer_add (&buf, "", 1); + buffer_reset (&buf); + + state = DISCARD; + str = boundary + 2; /* skip the leading crlf */ + do + { + /* x is true if this token ends with a matchstr or is at the end of stream */ + x = s_buffer_read (&sbuf, str); + content_length += sbuf.len; + if (content_length >= max_len) + { + empty_stdin (); + free (boundary); + s_buffer_destroy (&sbuf); + buffer_destroy (&buf); + if (var.name) + { + mime_var_destroy (&var); + } + die_with_message (NULL, NULL, + "Attempted to send content larger than allowed limits."); + } + + switch (state) + { + + case DISCARD: + /* discard any text - used for first mime boundary */ + if (x) + { + state = BOUNDARY; + str = crlf; + buffer_reset (&buf); /* reinitializes the buffer */ + } + break; + + + case BOUNDARY: + if (!x) + { + buffer_add (&buf, sbuf.segment, sbuf.len); + } + if (x) + { + buffer_add (&buf, sbuf.segment, sbuf.len); + if (!memcmp (buf.data, boundary + 2, 2)) + { /* "--" */ + /* all done... what does that mean? */ + str = boundary + 2; + state = DISCARD; + } + else + { + buffer_reset (&buf); + mime_var_init (&var); + state = HEADER; + str = crlf; + } + } + break; + + case HEADER: + buffer_add (&buf, sbuf.segment, sbuf.len); + if (x) + { + if (sbuf.len == 0) + { /* blank line */ + buffer_reset (&buf); + state = CONTENT; + str = boundary; + } + else + { + buffer_add (&buf, "", 1); + mime_tag_add (&var, (char *) buf.data); + buffer_reset (&buf); + } + } + break; + + case CONTENT: + /* write to writer process, regardless */ + mime_var_writer (&var, (char *) sbuf.segment, sbuf.len); + if (x) + { + buffer_reset (&buf); + mime_var_putenv (env, &var); + mime_var_destroy (&var); + state = BOUNDARY; + str = crlf; + } + + break; + + } /* end switch */ + + } + while (!sbuf.eof); + free (boundary); + s_buffer_destroy (&sbuf); + buffer_destroy (&buf); + return (0); +} diff --git a/src/rfc2388.h b/src/rfc2388.h new file mode 100644 index 0000000..acad1ac --- /dev/null +++ b/src/rfc2388.h @@ -0,0 +1,28 @@ +#ifndef _RFC2388_H +#define _RFC2388_H 1 + +typedef struct +{ + char *name; /* the variable name */ + char *filename; /* the client-specified filename */ + char *type; /* the mime-type */ + char *tempname; /* the tempfilename */ + buffer_t value; /* the value of the variable */ + int fh; /* the output file handle */ + } mime_var_t; + + +/* rfc2388.c */ +void empty_stdin(void); +void mime_var_init(mime_var_t *obj); +void mime_var_destroy(mime_var_t *obj); +char *mime_substr(char *start, int len); +void mime_tag_add(mime_var_t *obj, char *str); +void mime_var_putenv(list_t *env, mime_var_t *obj); +void mime_exec(mime_var_t *obj, char *fifo); +void mime_var_open_target(mime_var_t *obj); +void mime_var_writer(mime_var_t *obj, char *str, int len); +int rfc2388_handler(list_t *env); + + +#endif /* _RFC2388_H */ diff --git a/src/sliding_buffer.c b/src/sliding_buffer.c new file mode 100644 index 0000000..2b5ef33 --- /dev/null +++ b/src/sliding_buffer.c @@ -0,0 +1,180 @@ +/* + * -------------------------------------------------------------------------- + * Sliding Buffer functions for haserl Copyright (c) 2007 Nathan Angelacos + * (nangel@users.sourceforge.net) This program is free software; you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License, version 2, as published by the Free Software Foundation. This + * program is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * ------------------------------------------------------------------------- + */ + +#if HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> + +#include "sliding_buffer.h" + +/* + * initialize a sliding buffer structure + */ +int +s_buffer_init (sliding_buffer_t * sbuf, int size) +{ + sbuf->maxsize = size; + sbuf->buf = malloc (sbuf->maxsize); + /* reduce maxsize by one, so that you can add a NULL to the end of any + returned token and not have a memory overwrite */ + sbuf->maxsize -= 1; + sbuf->fh = 0; /* use stdin by default */ + sbuf->eof = 0; + sbuf->len = 0; + sbuf->ptr = sbuf->buf; + sbuf->bufsize = 0; + sbuf->maxread = 0; + sbuf->nrread = 0; + return (sbuf->buf != NULL); /* return true if the alloc succeeded */ +} + +/* + * destroy a sliding buffer structure + */ +void +s_buffer_destroy (sliding_buffer_t * sbuf) +{ + free (sbuf->buf); +} + + +/* + * read the next segment from a sliding buffer. returns !=0 if the + * segment ends at a matchstr token, or if we are at the end of the string + * returns 0 if the segment does not end + */ +int +s_buffer_read (sliding_buffer_t * sbuf, char *matchstr) +{ + int len, pos; + int r; + + /* + * if eof and ptr ran off the buffer, then we are done + */ + if ((sbuf->eof) && (sbuf->ptr > sbuf->buf)) + { + return 0; + } + + /* + * if need to fill the buffer, do so + */ + if ((sbuf->bufsize == 0) || + (sbuf->ptr >= (sbuf->buf + sbuf->bufsize - strlen (matchstr)))) + { + len = sbuf->bufsize - (sbuf->ptr - sbuf->buf); + if (len) + { + memmove (sbuf->buf, sbuf->ptr, len); + } + sbuf->ptr = sbuf->buf; + sbuf->bufsize = len; + /* if the filedescriptor is invalid, we are obviously + * at an end of file condition. + */ + if (fcntl (sbuf->fh, F_GETFL) == -1) + { + r = 0; + } + else + { + size_t n = sbuf->maxsize - len; + if ( sbuf->maxread && sbuf->maxread < sbuf->nrread + n) + n = sbuf->maxread - sbuf->nrread; + r = read (sbuf->fh, sbuf->buf + len, n); + } + /* + * only report eof when we've done a read of 0. + */ + if (r == 0 || (r < 0 && errno != EINTR)) + { + sbuf->eof = -1; + } + else + { + sbuf->bufsize += (r > 0) ? r : 0; + sbuf->nrread += (r > 0) ? r : 0; + } + } + + /* + * look for the matchstr + */ + pos = 0; + len = sbuf->bufsize - (int) (sbuf->ptr - sbuf->buf) - strlen (matchstr); + while (memcmp (matchstr, sbuf->ptr + pos, strlen (matchstr)) && (pos < len)) + { + pos++; + } + + /* + * if we found it + */ + if (pos < len) + { + sbuf->len = pos; + sbuf->segment = sbuf->ptr; + sbuf->ptr = sbuf->segment + pos + strlen (matchstr); + return -1; + } + + if (sbuf->eof) + { + len += strlen (matchstr); + } + + /* + * ran off the end, didn't find the matchstr + */ + sbuf->segment = sbuf->ptr; + sbuf->len = len; + sbuf->ptr += sbuf->len; + return (sbuf->eof) ? (-1) : (0); +} + + + +#ifdef TEST_FRAMEWORK + +main () +{ + int x; + sliding_buffer_t sb; + char foo[200]; + + s_buffer_init (&sb, 32); + + do + { + x = s_buffer_read (&sb, "&"); + sprintf (foo, "%03d- %03d - %03d", x, sb.eof, sb.len); + write (1, foo, strlen (foo)); + write (1, sb.segment, sb.len); + write (1, "\n", 1); + } + while ((!sb.eof)); + s_buffer_destroy (&sb); +} + +#endif diff --git a/src/sliding_buffer.h b/src/sliding_buffer.h new file mode 100644 index 0000000..167e461 --- /dev/null +++ b/src/sliding_buffer.h @@ -0,0 +1,27 @@ +#ifndef _SLIDING_BUF_H +#define _SLIDING_BUF_H 1 + + +/* sliding buffer structure */ +typedef struct { + int fh; /* the input filehandle for the buffer */ + unsigned char *buf; /* pointer to the buffer */ + unsigned char *ptr; /* start positon (used internally) */ + unsigned char *segment; /* the start position of this segment */ + size_t len; /* length of this segment */ + size_t maxsize; /* max size of buffer */ + size_t bufsize; /* current size of buffer */ + size_t maxread; /* maximum number of bytes to read from fh, ignored if 0 */ + size_t nrread; /* number of bytes read from fh */ + int eof; /* true if there is no more to read */ + } sliding_buffer_t; + + + +/* sliding_buffer.c */ +int s_buffer_init(sliding_buffer_t *sbuf, int size); +void s_buffer_destroy(sliding_buffer_t *sbuf); +int s_buffer_read(sliding_buffer_t *sbuf, char *matchstr); + + +#endif /* !_SLIDING_BUF_H */ diff --git a/src/stamp-h.in b/src/stamp-h.in new file mode 100644 index 0000000..9788f70 --- /dev/null +++ b/src/stamp-h.in @@ -0,0 +1 @@ +timestamp diff --git a/tests/Makefile.am b/tests/Makefile.am new file mode 100644 index 0000000..358ac94 --- /dev/null +++ b/tests/Makefile.am @@ -0,0 +1,14 @@ +PACKAGE=@PACKAGE_TARNAME@ +EXTRA_DIST=minunit.h +datarootdir=@datarootdir@ + + +#--------- Unit Tests ---------------------- +TESTS = utest_common + +check_PROGRAMS = utest_common + +utest_common: ../src/common.c utest_common.c + +#--------- End Unit Tests ------------------- + diff --git a/tests/minunit.h b/tests/minunit.h new file mode 100644 index 0000000..749c746 --- /dev/null +++ b/tests/minunit.h @@ -0,0 +1,6 @@ +/* file: minunit.h */ + +#define mu_assert(message, test) do { if (!(test)) return message; } while (0) +#define mu_run_test(test) do { char *message = test(); tests_run++; \ + if (message) return message; } while (0) +extern int tests_run; diff --git a/tests/utest_common.c b/tests/utest_common.c new file mode 100644 index 0000000..0085cb4 --- /dev/null +++ b/tests/utest_common.c @@ -0,0 +1,46 @@ +/* file minunit_example.c */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "../src/common.h" +#include "minunit.h" + +int tests_run = 0; + + +void * xmalloc=malloc; +void * xrealloc=realloc; + + +char *test_lowercase() { + char source[] = "This is a Test!"; + int result; + + lowercase(source); + result=memcmp("this is a test!", source, strlen(source)); + + mu_assert("lowercase failed",result == 0); + return 0; + } + + +char *all_tests() { + mu_run_test(test_lowercase); + return 0; + } + +int main(int argc, char **argv) { + char *result = all_tests(); + printf ("%d\n", (int) result); + if (result != 0) { + printf("%s\n", result); + } + else { + printf("ALL TESTS PASSED\n"); + } + printf("Tests run: %d\n", tests_run); + + return result != 0; + } + |