diff options
author | jcid <devnull@localhost> | 2007-10-07 00:36:34 +0200 |
---|---|---|
committer | jcid <devnull@localhost> | 2007-10-07 00:36:34 +0200 |
commit | 93715c46a99c96d6c866968312691ec9ab0f6a03 (patch) | |
tree | 573f19ec6aa740844f53a7c0eb7114f04096bf64 |
Initial revision
-rw-r--r-- | AUTHORS | 68 | ||||
-rw-r--r-- | COPYING | 674 | ||||
-rw-r--r-- | ChangeLog | 1416 | ||||
-rw-r--r-- | INSTALL | 176 | ||||
-rw-r--r-- | Makefile.am | 5 | ||||
-rw-r--r-- | NEWS | 33 | ||||
-rw-r--r-- | README | 76 | ||||
-rw-r--r-- | README-port | 145 | ||||
-rw-r--r-- | config.h.in | 106 | ||||
-rw-r--r-- | configure.in | 475 | ||||
-rw-r--r-- | dillorc2 | 194 | ||||
-rw-r--r-- | dlib/Makefile.am | 6 | ||||
-rw-r--r-- | dlib/dlib.c | 750 | ||||
-rw-r--r-- | dlib/dlib.h | 158 | ||||
-rw-r--r-- | doc/Cache.txt | 185 | ||||
-rw-r--r-- | doc/Cookies.txt | 85 | ||||
-rw-r--r-- | doc/Dillo.txt | 103 | ||||
-rw-r--r-- | doc/Dpid.txt | 454 | ||||
-rw-r--r-- | doc/Dw.txt | 383 | ||||
-rw-r--r-- | doc/DwImage.txt | 201 | ||||
-rw-r--r-- | doc/DwPage.txt | 152 | ||||
-rw-r--r-- | doc/DwRender.txt | 620 | ||||
-rw-r--r-- | doc/DwStyle.txt | 310 | ||||
-rw-r--r-- | doc/DwTable.txt | 205 | ||||
-rw-r--r-- | doc/DwWidget.txt | 339 | ||||
-rw-r--r-- | doc/HtmlParser.txt | 116 | ||||
-rw-r--r-- | doc/IO.txt | 468 | ||||
-rw-r--r-- | doc/Images.txt | 114 | ||||
-rw-r--r-- | doc/Imgbuf.txt | 177 | ||||
-rw-r--r-- | doc/Makefile.am | 19 | ||||
-rw-r--r-- | doc/NC_design.txt | 80 | ||||
-rw-r--r-- | doc/README | 53 | ||||
-rw-r--r-- | doc/Selection.txt | 149 | ||||
-rw-r--r-- | dpi/Makefile.am | 37 | ||||
-rw-r--r-- | dpi/bookmarks.c | 1716 | ||||
-rw-r--r-- | dpi/cookies.c | 1466 | ||||
-rw-r--r-- | dpi/datauri.c | 323 | ||||
-rw-r--r-- | dpi/downloads.cc | 1133 | ||||
-rw-r--r-- | dpi/dpiutil.c | 270 | ||||
-rw-r--r-- | dpi/dpiutil.h | 97 | ||||
-rw-r--r-- | dpi/file.c | 975 | ||||
-rw-r--r-- | dpi/ftp.c | 301 | ||||
-rw-r--r-- | dpi/hello.c | 161 | ||||
-rw-r--r-- | dpi/https.c | 713 | ||||
-rw-r--r-- | dpid/Makefile.am | 26 | ||||
-rw-r--r-- | dpid/TODO | 32 | ||||
-rw-r--r-- | dpid/dpi.c | 97 | ||||
-rw-r--r-- | dpid/dpi.h | 54 | ||||
-rw-r--r-- | dpid/dpi_service.c | 114 | ||||
-rw-r--r-- | dpid/dpi_service.h | 20 | ||||
-rw-r--r-- | dpid/dpi_socket_dir.c | 128 | ||||
-rw-r--r-- | dpid/dpi_socket_dir.h | 17 | ||||
-rw-r--r-- | dpid/dpid.c | 734 | ||||
-rw-r--r-- | dpid/dpid.h | 103 | ||||
-rw-r--r-- | dpid/dpid_common.c | 43 | ||||
-rw-r--r-- | dpid/dpid_common.h | 61 | ||||
-rw-r--r-- | dpid/dpidc | 31 | ||||
-rw-r--r-- | dpid/main.c | 398 | ||||
-rw-r--r-- | dpid/misc_new.c | 172 | ||||
-rw-r--r-- | dpid/misc_new.h | 12 | ||||
-rw-r--r-- | dpip/Makefile.am | 5 | ||||
-rw-r--r-- | dpip/dpip.c | 168 | ||||
-rw-r--r-- | dpip/dpip.h | 34 | ||||
-rwxr-xr-x | install-dpi-local | 39 | ||||
-rw-r--r-- | src/IO/IO.c | 412 | ||||
-rw-r--r-- | src/IO/IO.h | 45 | ||||
-rw-r--r-- | src/IO/Makefile.am | 14 | ||||
-rw-r--r-- | src/IO/Url.h | 40 | ||||
-rw-r--r-- | src/IO/about.c | 344 | ||||
-rw-r--r-- | src/IO/dpi.c | 779 | ||||
-rw-r--r-- | src/IO/http.c | 494 | ||||
-rw-r--r-- | src/IO/iowatch.cc | 35 | ||||
-rw-r--r-- | src/IO/iowatch.hh | 25 | ||||
-rw-r--r-- | src/IO/mime.c | 152 | ||||
-rw-r--r-- | src/IO/mime.h | 58 | ||||
-rw-r--r-- | src/IO/proto.c | 13 | ||||
-rw-r--r-- | src/Makefile.am | 87 | ||||
-rw-r--r-- | src/binaryconst.h | 38 | ||||
-rw-r--r-- | src/bitvec.c | 59 | ||||
-rw-r--r-- | src/bitvec.h | 36 | ||||
-rw-r--r-- | src/bookmark.c | 89 | ||||
-rw-r--r-- | src/bookmark.h | 19 | ||||
-rw-r--r-- | src/bw.c | 248 | ||||
-rw-r--r-- | src/bw.h | 96 | ||||
-rw-r--r-- | src/cache.c | 932 | ||||
-rw-r--r-- | src/cache.h | 75 | ||||
-rw-r--r-- | src/capi.c | 587 | ||||
-rw-r--r-- | src/capi.h | 29 | ||||
-rw-r--r-- | src/chain.c | 128 | ||||
-rw-r--r-- | src/chain.h | 69 | ||||
-rwxr-xr-x | src/chg | 28 | ||||
-rw-r--r-- | src/colors.c | 366 | ||||
-rw-r--r-- | src/colors.h | 15 | ||||
-rw-r--r-- | src/cookies.c | 332 | ||||
-rw-r--r-- | src/cookies.h | 24 | ||||
-rw-r--r-- | src/debug.h | 149 | ||||
-rw-r--r-- | src/dialog.cc | 116 | ||||
-rw-r--r-- | src/dialog.hh | 22 | ||||
-rw-r--r-- | src/dicache.c | 451 | ||||
-rw-r--r-- | src/dicache.h | 70 | ||||
-rw-r--r-- | src/dillo.cc | 108 | ||||
-rw-r--r-- | src/dir.c | 48 | ||||
-rw-r--r-- | src/dir.h | 19 | ||||
-rw-r--r-- | src/dns.c | 535 | ||||
-rw-r--r-- | src/dns.h | 31 | ||||
-rw-r--r-- | src/dpiapi.c | 82 | ||||
-rw-r--r-- | src/dpiapi.h | 3 | ||||
-rw-r--r-- | src/form.cc | 98 | ||||
-rw-r--r-- | src/form.hh | 87 | ||||
-rw-r--r-- | src/gif.c | 1054 | ||||
-rw-r--r-- | src/history.c | 125 | ||||
-rw-r--r-- | src/history.h | 24 | ||||
-rw-r--r-- | src/html.cc | 5123 | ||||
-rw-r--r-- | src/html.hh | 279 | ||||
-rw-r--r-- | src/image.cc | 226 | ||||
-rw-r--r-- | src/image.hh | 79 | ||||
-rw-r--r-- | src/jpeg.c | 334 | ||||
-rw-r--r-- | src/klist.c | 118 | ||||
-rw-r--r-- | src/klist.h | 40 | ||||
-rw-r--r-- | src/list.h | 49 | ||||
-rw-r--r-- | src/menu.cc | 358 | ||||
-rw-r--r-- | src/menu.hh | 34 | ||||
-rw-r--r-- | src/misc.c | 271 | ||||
-rw-r--r-- | src/misc.h | 25 | ||||
-rw-r--r-- | src/msg.h | 42 | ||||
-rw-r--r-- | src/nav.c | 427 | ||||
-rw-r--r-- | src/nav.h | 40 | ||||
-rw-r--r-- | src/pixmaps.h | 1652 | ||||
-rw-r--r-- | src/plain.cc | 233 | ||||
-rw-r--r-- | src/png.c | 472 | ||||
-rw-r--r-- | src/prefs.c | 434 | ||||
-rw-r--r-- | src/prefs.h | 130 | ||||
-rwxr-xr-x | src/srch | 33 | ||||
-rw-r--r-- | src/timeout.cc | 46 | ||||
-rw-r--r-- | src/timeout.hh | 20 | ||||
-rw-r--r-- | src/ui.cc | 912 | ||||
-rw-r--r-- | src/ui.hh | 109 | ||||
-rw-r--r-- | src/uicmd.cc | 644 | ||||
-rw-r--r-- | src/uicmd.hh | 62 | ||||
-rw-r--r-- | src/url.c | 632 | ||||
-rw-r--r-- | src/url.h | 144 | ||||
-rw-r--r-- | src/web.cc | 175 | ||||
-rw-r--r-- | src/web.hh | 45 |
143 files changed, 39853 insertions, 0 deletions
diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 00000000..69a1a375 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,68 @@ + + _________________________________________________________________ + + Dillo project's team + _________________________________________________________________ + + Project maintainer: + * Jorge Arellano Cid + + Core Developers: + * Jorge Arellano Cid + * Sebastian Geerken + + Steady developers: + * Livio Baldini + * Eric Gaudet + * Jörgen Viksell + + Contributors: + * Lars Clausen + * Geoff Lane + * Sammy Mannaert + * James McCollough + + Patches: + * Philip Blundell + * Francis Daly + * Sam Dennis + * Melvin Hadasht + * Madis Janson + * Andrew McPherson + * Sean 'Shaleh' Perry + * Marcos Ramírez + * Adam Sampson + * Andreas Schweitzer + * Dominic Wong + _________________________________________________________________ + + Web site logo: + * Eric Gaudet + * Jarrod Henry + + Gzilla author: + * Raph Levien + _________________________________________________________________ + + + + +------------------- +Up to gzilla-0.1.7: +------------------- + +Raph Levien <raph@acm.org> is the author of gzilla. + +Christoph Thompson <obituary@freshmeat.net> and Tomas O"gren <stric@ing.umu.se> + added pixmaps for the buttons and the code encessary to make them work. + They also reorganized the source tree and gave general advice and tips. + (Tomas will be pleased to find that my personal bookmarks file is no longer + hard-coded into Gzilla. :)) + +Ian Main <slow@intergate.bc.ca> did the bookmarks. + +Thanks to Otto Hammersmith, David Mosberger-Tang, and Peter Mattis for +patches. + +Contributions are always welcome! + diff --git a/COPYING b/COPYING new file mode 100644 index 00000000..94a9ed02 --- /dev/null +++ b/COPYING @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. 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 +them 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 prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. 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. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey 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; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If 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 convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU 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 that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + 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. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +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. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + 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 +state 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 3 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, see <http://www.gnu.org/licenses/>. + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + <program> Copyright (C) <year> <name of author> + This program 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, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +<http://www.gnu.org/licenses/>. + + The GNU 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 Lesser General +Public License instead of this License. But first, please read +<http://www.gnu.org/philosophy/why-not-lgpl.html>. diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 00000000..8a1e3105 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,1416 @@ +============================================================================= +Dillo project +============================================================================= + +dillo-fltk2 + + - Ported Dillo from GTK1 to FLTK2. + - Ported a susbstantial part of the code from C to C++ (FLTK2 is in C++). + - Wrote a new library: Dlib. With "Dlib" Dillo doesn't need glib anymore. + - Ported all the code to Dlib. + - Fixed Http_must_use_proxy() to be case insensitive. + - Fixed some leaks and bugs in the cookies dpi. + - Made Dillo's UI Control Panel resizable on-the-fly. + - Implemented a new, simpler, dillorc parser. + - Added handling of "localhost" in file URIs. + - Fix: recognize "http://foo" and "http://foo/" as the same URL (BUG#497). + - Reimplemented the Concomitant Callback chains into a uniform scheme! + (two query branches and a single answer branch). It simplifies a lot the + former CCC paths and allows for easier error control. + - Added a new method for internally-generated urls: a_Cache_entry_inject(). + - Switched the cache to use Dlib's Dstr for its data storage. + - Removed threads from IO. Now it only uses select-based watches. + - Reimplemented IO.c and dpi.c to use Dlib's Dstr as its main buffer. + - Turned Klist into a sorted list. + - Removed one data-copy stage in Html_write_raw(). + - Switched gcc's "fmt..." syntax to ISO C __VA_ARGS__. + - Fixed Dillo and its dpis to work from "/tmp" (for easy device unmount). + - Simplified http.c by reusing the new non-blocking writes in IO. + - Reworked the capi API so cache is only accessable from capi. + - Rewrote the CCC's OpAbort handling. + - Rewrote the DNS API and the Dpid start code inside Dillo. + - Implemented Stop button to not only stop rendering but also networking. + Patches: Jorge Arellano + + + TODO: + + - test no_proxy (set a list in dillorc). + +----------------------------------------------------------------------------- + + +0.8.5-pre-dw-redesign-1 [internal] +- Prototype + +dillo-0.8.3-pre-dw-redesign-3 [Aug 30, 2004] + - * Fixed bug GtkDwViewport, which caused some redraws to be ignored. + * Added GdkDwPreview. + Patches: Sebastian Geerken + + +dillo-0.8.3-pre-dw-redesign-2 [Aug 28, 2004] + - * Added images to the current state of the redesign. + - New module Imgbuf, see doc/Imgbuf.txt for details. + Patch: Sebastian Geerken + + +dillo-0.8.3-pre-dw-redesign-1 [Aug 25, 2004] + - * Introduced an abstraction layer between Dw and Gtk+. See README-port and + doc/DwRender.txt for more details. + Patch: Sebastian Geerken + + +============================================================================= +Dillo project +============================================================================= + + +dillo-0.8.6 [Apr 26, 2006] + + - * Designed and implemented a dpi protocol library (libDpip.a in /dpip). + * Added a couple of new dpip commands. + * Fixed and uniformed the escaping of values inside dpip tags. + * Ported the bookmarks, download, file, https, ftp and hello plugins, + plus the dpid daemon and the rest of the source tree to use it. + * Improved the dpi buffer reception to handle split buffers (This was + required for handling arbitrary data streams with dpip). + * Fixed a bug in Cache_entry_remove_raw. + * Added a couple of "const" and C++ wrappers to dpiutil's API. + * Fixed a serious bug with the FTP plugin that led to two downloads of the + same file when left-clicking a non-viewable file. + * Added MIME/type detection to the FTP plugin, and removed popen(). + * Set the dpi daemon (dpid) not to exit when the downloads dpi is running. + * Improved the accuracy of the illegal-character error reporting for URLs. + * Now save dialog replaces %20 and ' ' with '_' in the Filename suggestion. + * Made URL ADT automatically count and strip illegal characters from URLs. + * Added dpi/downloads.cc (Default FLTK2-based GUI for downloads dpi). + * Added "./configure --disable-dlgui" to build without FLTK2-GUI downloads. + * Fixed dpip's tag syntax and its parsing to accept any value string. + * Added DOCTYPE parsing (for better bug-meter error messages). + * Added a DOCTYPE type declaration tag to dpi-generated HTML. + * Fixed bookmarks dpi to escape ' in URLs and &<>"' in titles (BUG#655). + * Added a check for malicious image sizes in IMG tags. + * Made the parser aware of buggy pages with multiple BODY and HTML elements. + * Fixed a bug in MIME content/type detection. + * Check HTTP Content-Type against real data (a security procedure). + Patches: Jorge Arellano + - * Added a datauri dpi to handle "data:" URIs (RFC-2397). + Patch: Jorge Arellano, Ben Wiley Sittler + - * Moved the cookies management into a dpi server: cookies.dpi. + * Removed the restriction of only one dillo with cookies enabled! + * Fixed a bug with cookies for sites with self-signed certificate. + * Updated the cookies documentation. + * Made the downloads plugin dillo-cookie aware. + * Ported the cookies dpi to libDpip.a. + * Merged the new dpip code into the source tree. + Patches: Diego Sáenz, Jorge Arellano + - * Added "./configure --disable-threaded-dns" (for some non reentrant BSDs). + Patch: Jorge Arellano, Francis Daly + - * Fixed a bug with roman literals divisible by 10 (BUG#700). + * Fixed a bug with long alphabetically ordered lists (BUG#704). + Patch: Glyn Kennington + - * Fixed a file descriptor leak in the dpi protocol library. + * Fixed a subtle segfault bug with malformed URLs in cookies.c. + Patch: Francis Daly + + +dillo-0.8.5 [Jun 15, 2005] + + - * Set "file:" to work as URI for current directory. + Patch: Diego Sáenz + - * Added a "small" dillorc option for panel size (medium without labels). + Patch: Eugeniy, Jorge Arellano + - * Fixed the shell escaping code in the ftp plugin. + * Added some checks for sane values in html.c. + * Added URL filtering to the ftp and downloads dpis to avoid SMTP hacks. + * Fixed the file dpi to react to the DpiBye command. + Patches: Jorge Arellano + + +dillo-0.8.4 [Jan 11, 2005] + + - * Fixed a possible attack (program abortion) by malicious web pages, which + contain huge values for <table> attributes "colspan" and "rowspan". + * Changed anchors, they are now tested to be unique, and removed properly, + when a widget tree is changed (e.g. another page is visited). Also added + HTML warnings. + Patches: Sebastian Geerken + - * Fixed two minor memory leaks (IO's Buf1Start & html's SPCBuf). + * Fixed handling of XML's "/>" tag-closing (e.g. <script ... />). BUG#514 + * Removed obsolete code from IO/file.c. + * Added a few missing EINTR handlers in dpi.c. + * Orthogonalized the generic parser: + - Fixes memory leaks and widget state when recovering from bad HTML. + - Improves error detection and validation (needed by XHTML). + - Makes DOC tree generation possible (needed by CSS). + - Cleaner design of handling routines for bad HTML. + - Orthodox treatment of double optional elements (HTML, HEAD, BODY). + - Lots of minor code cleanups. + * Switched the dpi file server's design to pthreads (fixes a critical race). + * Avoided a crash when indexed GIF images lack a color map (BUG#647). + * Fixed a bug when the remote HTTP server sends no Content-Type and + the TCP packetizing splits the header from data (BUG#650). + * Returned the parser to the old whitespace "collapsing" mode + (this can be changed with the SGML_SPCDEL define in html.c). + * Fixed a memory leak for DwStyle (there was one leak per page). + Patches: Jorge Arellano + - * Fixed a large memory leak of thread specific resources. --Very important + Patch: Jorge Arellano, Livio Baldini + - * Removed warnings for pointer arithmetic and strict prototypes all + around the code (now it works under LP64 architectures). + * Made miscelaneous cleanups for LP64 architectures. + Patches: Jorge Arellano, Dieter Baron + - * Changed dpid's umask to 0077. + Patch: Jorge Arellano, Richard Zidlicky + - * Switched to g_vsnprintf (instead of vsnprintf). + Patch: Frank Wille + - * Updated a bit the README file. + Patch: Dieter Baron + - * Made a grammatical and typographical review of the whole documentation + in doc/. Also added some clarifications. + * Fixed a libpng detection problem (e.g., on CYGWIN). BUG#651 + Patches: Roberto Sanchez + - * Fixed "id" and "name" attributes parsing logic. + * Improved the parsing algorithm for character entities. BUG#605 + Patches: Matthias Franz + - * Fixed a security bug with uncertain data and a_Interface_msg(). + CAN-2005-0012. + Patch: Tavis Ormandy + + +dillo-0.8.3 [Oct 27, 2004] + + - * Added a missing error handler for unreachable host in http.c. + Patch: Dennis Schneider, Jorge Arellano + - * Added fragment (aka anchor) decoding before it is set by Dw. + Patch: Matthias Franz, Jorge Arellano + - * Fixed dpid to work even when a dpi directory is empty. + Patch: George Georgiev, Jorge Arellano + - * Made the search dialog's URL go encoded in the query. + Patch: Matthias Franz + - * Fixed the width of sized text entry widgets within FORMS. + Patch: Thorben Thuermer + -(*)Made a library-based https dpi prototype, with certificate authentication! + * Separated the code in dpi/ so the common base now lies in dpiutil.c. + Patches: Garrett Kajmowicz + - * Added SSL library detection code to configure.in. + Patch: Garrett Kajmowicz, Thomas-Martin Seck + - * Fixed the wrong image-URL after cancelling a link-image popup (BUG#580). + * Improved the transfer speed for the bookmarks dpi when using 2.6.x linux. + * Fixed the downloads dpi to work when there're "'" characters in the URL. + * Fixed " and ' characters stuffing in capi and interface for dpip commands. + (*)Added a "dialog" command to the dpi protocol (dpip). It allows any dpi to + make GUI choice-questions trough Dillo by using simple dpi tags. + * Merged some dialog-generating code in interface.c and fixed a bug with + the FORM repost dialog. + * Designed and implemented a unified API for handling data streams inside + dillo plugins. Servers and filters can use it. + * Converted the bookmarks, ftp, file, hello and the https prototype dpis + to the new dpiutil API. + * Replaced the old 'force_visited_color' dillorc option with the new + 'contrast_visited_color' (using a renewed color-choosing algorithm). + * Added some 'undefined ASCII' to latin1 character conversions. + * Added the "w3c_plus_heuristics" option to dillorc. + * Removed a segfault bug on oversized images (rare case). + * Removed a CPU-hog on 302 redirections with cookies. + * Made HTTP's 302 redirections non-cacheable (incomplete). + * Implemented a new scheme for detecting redirection loops. + * Fixed cookies to accept four legacy old-date formats for "Expires". + Patches: Jorge Arellano + - * Introduced a light-weight heuristic algorithm over the W3C parsing + scheme (allows for slightly better rendering: w3c_plus_heuristics=YES). + Patch: Rubén Fernández + - * Moved the internal support for "file:" URIs into a dpi (server plugin). + * Added TABLE-based rendering of directory listings to the new file dpi. + Patches: Jorge Arellano, Jörgen Viksell + - * Removed DwWidget::realize and DwWidget::unrealize. + * Made all signals expect events to abstract methods. + * Renamed a_Dw_widget_{size_request|get_extremes|size_allocate|set_*} to + p_*, they should not be used outside of Dw. + Patches: Sebastian Geerken + - * Fixed the meta refresh warning to not switch from IN_HEAD to IN_BODY. + Patch: Björn Brill + + +dillo-0.8.2 [Jul 06, 2004] + + - * Made PgUp/PgDn scroll by a full page, instead of a half (BUG#418). + * Added new Gtk+ widgets GtkExtButton, GtkExtMenu, and GtkExtMenuItem. + - Used GtkExtButton to enhance the button 3 menu of the forward button, + backward button and bug meter buutton. + - GtkExtMenu and GtkExtMenuItem are used to make handling button 2 + in the history menus cleaner. + * Made bug meter button react on high-level "clicked" signal, instead of + "button-press-event". + * New widget GtkMenuTitle, used for nicer titles in menus. + Patches: Sebastian Geerken + - * Added a small handler for javascript links (BUG#546). + * Made the parser ignore white space immediately after an open tag, and + immediately before a close tag. + * Fixed handling of redirection answers with unviewable MIME type (BUG#563). + * Fixed the history-stack handling after redirection chains. + * Fixed the character escaping logic in directory listings (BUG#564). + * Added a small hack to view UTF-8 encoded quotation marks. + * Added a "start_page" option to dillorc (to override the splash screen). + * Tuned the buffering scheme for local directory listings (more speed). + * Set some initial socket-buffering for dpis (in dpid). + * Disallowed the "about:" method on non-root URLs (BUG#573). + * Made the rendered area keep its focus after a form submition. + * Fixed some include files in src/IO/. + Patches: Jorge Arellano + - * Added hints (icons and tooltip text) to buttons featuring a right-click + context menu. + * Now you can copy & paste an URL into the "Clear URL" button. + Patch: Jorge Arellano, Sebastian Geerken + - * Made the save and open file dialogs remember the last directory (BUG#211). + Patch: Diego Sáenz + + +dillo-0.8.1 [May 14, 2004] + + - * Fixed dirent.h includes inside dpid. + Patch: Joseph Myers + - * Fixed a slippery bug with certain interlaced gif images (BUG#500). + Patch: Andreas Mueller + - * Fixed libpng-1.2.4 detection in configure.in. + Patch: Rubén Helguera + - * Added proxy authentication support through the "http_proxyuser" option + in dillorc (the password is asked at run time). + Patch: Ivan Daniluk, Jorge Arellano, Francis Daly + - * Moved tooltips to DwStyle, tooltip event handling to DwPage, and applied + this also to the TITLE attribute of <a> and <abbr>. + Patch: Jörgen Viksell, Sebastian Geerken + - * Fixed a bug related to spaces after anchors and breaks. + Patch: Sebastian Geerken + - * Removed two "type punning" gcc warnings (dw_gtk_viewport.c). + * Added some missing "static" qualifiers. + * Improved a_Strbuf_chars() so no list reversion is required. + * Removed an unused data list (dns.c), and redundant code (selection.c). + * Switch one realloc() call to g_realloc(), to match g_free() (dpi.c). + * Removed unnecessary NULL-checks and NULL initializations. + * Added Html_get_attr_wdef(), it lets providing a default return value. + Patches: Jörgen Viksell + - * Fixed configure.in so pthreads are only linked where needed. + Patch: Jörgen Viksell, Jorge Arellano + - * Modified a_Misc_stuff_chars for simplicity and removed a memory leak. + * Made the dpi framework send the HTTP query to the https dpi + (this allows for an SSL-lib dpi and for easier session caching). + * Cleaned up the int2void and void2int casts in CCC parameters. + * Added container|inline model information to the HTML element table, and + made the bug-meter and the parser aware of it. This both improves bug + detection and rendering. + * Fixed newly detected HTML bugs in bookmarks dpi and file.c. + * Fixed opening files with a ':' character in its name (again). + * Added binaryconst.h (allows for binary constants in C). + * Fixed The ladder effect with lists (BUG#534). + * Made the bug-meter detect tags lacking a closing '>' (BUG#532). + * Made the bug-meter detect excluded inline elements from <PRE>. + * Eliminated a segfault source with tricky <input> tags (BUG#552). + * Fixed <address> to render as a block element (BUG#527). + * Added a content test for "name" and "id" attribute values (BUG#536). + * Fixed the URL resolver handling of the "//" sequence in <path> (BUG#535). + * Added "show_extra_warnings" and removed "use_old_parser" (dillorc). + * Added minor support for the deprecated <MENU> element. + * Eliminated a race condition that produced segfaults when a dpi transfer + was cancelled before the contents were sent (a very rare case). + * Added a test for socklen_t in configure.in. + * Fixed the downloads dpi to handle both new savenames and target directory. + * GdkRgb: fixed handling of not usable system default visual and colormap. + * Made dillo recognize unhandled MIME types, and offer a download dialog! + Patches: Jorge Arellano + + +dillo-0.8.0 [Feb 08, 2004] + + - * Added a right-mouse-button popup for images! + Patch: Frank de Lange, Eric Gaudet, Jorge Arellano + - * Made main document window grab focus on startup, fullwindow, + and after open url (BUG#330) + * Set Ctrl-U to focus the location entry, + Ctrl-R to reload, and Ctrl-H to hide controls. + Patches: Johan Hovold, Jorge Arellano, Stephan Goetter + - * Added a missing handler for broken-connection condition. + Patch: Jorge Arellano, Phil Pennock + - * Introduced a new way of handling dillo plugins! Now the + communications and managing is done by a daemon: dpid. + This comes with a lot of advantages described in Dpid.txt. + Patch: Programming: Ferdi Franceschini; Design: Jorge Arellano + - * Wrote documentation for dpid (Dpid.txt). + * Removed a memory leak in Get_line(). + Patches: Jorge Arellano, Ferdi Franceschini + - * Developed a plugin for downloads. It uses wget and can handle several + connections at the same time. + * Developed stress tests for both dpid and the downloads dpi. + Patches: Ferdi Franceschini + - * Adapted dpi.c to manage plugins through dpid. + * Improved the incoming dpi-stream processing to accept images from a dpi. + * Added/updated lots of dpi-related comments. + * Updated the dpi1 spec. + * Removed the forced end-to-end reload that was set upon dpis. Now, + dpi-generated pages can be cached. + * Made dillo able to handle multiple plugins (still lacks a dynamic API) + * Wrote bare bones plugins for handling: FTP and HTTPS. + * Wrote an example plugin: HELLO --kind of "Hello world". + * Made all the bindings to make it work (fully commented). + * Added code for automatical and non-blocking dpid start! + * Added an extensible secondary URL convenience for popup menus. + * Attached the image popup to the link menu (when the link is an image). + * Removed a memory leak in the selection code (commands.c). + * Cleaned up memory handling when reusing the GioChannel for IPv6. + * Removed a race-condition-polling-CPU-hog bug in IO! (hairy... ;) + * Adapted the generic parser to make HTML error detection, providing + the line number and a hint (expected tag) in the error message! + * Added a bug-meter button that shows the count of detected HTML errors + (left click shows the errors, right click offers a validation menu). + * Added information about optional, required and forbidden end tags. + * Modified the parser's handling of closing tags to account for elements + with an optional close tag, and for more accurate diagnosis messages. + * Added 'use_old_parser' option to dillorc (boolean). + * Fixed the handling of HEAD and BODY elements to account for their + double optional condition (both open and close tags are optional). + * Added the MSG() macro to msg.h and replaced g_print() with it. + * Added the "show_msg" dillorc option to disable MSG(). + * Increased the number of warning messages reported by gcc. + * Solved a lot of signed/unsigned problems. + * Made the necessary cleanups/bug-removals for the new warning level. + * Connected the dpi stream to the cache using the CCC! + * Fixed the cache API by introducing the new call a_Capi_get_buf(). + * Fixed a race condition and a multiple request problem. + * Cleaned up the code for the progressbar widgets. + * Standarized unix domain sockets with AF_LOCAL for portability. + * Minor cleanups for a smooth compile on older systems (libc5). + * Fixed the handling of P element for the HTML nesting checks. + * Set Ctrl-B for bookmarks shortcut (instead of Alt-B). + Patches: Jorge Arellano + - * Enhanced the speed of the actual selection of text. + * Added command line option --debug-rendering. + * Added "button_sensitive" attribute to DwWidget, which is needed to + make <BUTTON>'s accessable at all. (They were inaccessable since the + introduction of text selection!) + * Changed behaviour of DwButton, see NOTE at beginning of dw_button.c. + * Added "collapsing margins" to DwPage. + * Added CSS "list-style-type" and "display" equivalents to DwStyle, changed + definition of "font", replaced "nowrap" by "white-space", and renamed + "link" to "x_link". + * DwBullet now uses DwStyle for the bullet type, made necessary changes + in HTML parser. + * Changed DwStyleLength, now only pixel values and percentages are + supported. (For CSS, anything else will be done elsewhere.) + * Added word backgrounds to DwPage (not yet used.) + * Added the possibility to clip widget drawings (new function + p_Dw_widget_will_clip). + * Made images showing the ALT text as long as no image data has been + retrieved. + * Cleaned up event handling and related code: "link_*" signals now return + gboolean, and DwWidget events are signals. + * Moved DwRectangle and related to dw.c. + * Rewrote idle drawing, fixed BUG#202. + * Removed p_Dw_widget_queue_clear*. + * Added --enable-rtfl option to configure. + * Fixed a bug in findtext (wrong highlighting). + * Many changes in scrolling: added x coordinate (except for anchors), and + DW_[VH]POS_INTO_VIEW position. Added x coordinate also to DilloUrl. + Patches: Sebastian Geerken + - * Fixed bug in DwImage::link_clicked signal. + Patch: Stephan Goetter, Frank de Lange (simultaneously and independent :-) + - * Fixed memory leak in Html_tag_open_isindex. + * Added numerical keypad cursor keys navigation. + * Changed return values of Dw event methods from gint to gboolean. + * Cleaned up debug message generation by using glib wrappers. + * Replaced DwStyle::SubSup by new DwStyleVAlignType values, and + DwStyle::uline and DwPage::strike by new DwStyle::text_decorations. + * Added new convenience macros DW_WIDGET_HEIGHT, DW_WIDGET_CONTENT_HEIGHT, + and DW_WIDGET_CONTENT_WIDTH. + * Added configure options to disable either: png, jpeg or gif. + * Fixed configure.in for proper library linking for dpis and dpid. + * Improved libpng detection. + Patches: Jörgen Viksell + - * Fixed wrong handling of coordinates in ISMAP and USEMAP images. + * Added a hand-shaped cursor to input controls of type image. + * Fixed a off-by-one memory leak in Dw(Ext)Iterator. + * Fixed NULL result handling of p_Dw_widget_text_iterator() in DwBullet, + DwHRuler and DwImage. + * Made dpid/Makefile.am aware of $(DESTDIR). + * Fixed wrong return value of a_Findtext_search for widget == NULL. + Patches: Frank de Lange + - * Fixed a bug in Dw cursor code. + Patch: Frank de Lange, Sebastian Geerken + - * Corrected marshal functions for DwWidget signals. + Patch: Anders Gavare, Sebastian Geerken + - * Added support for anchors using the "id" attribute (BUG#495). + * Defined dillo's version-string in one place only: configure.in. + Patch: Francis Daly + - * Removed a segfault source with corrupted MIME types in HTTP (BUG#501). + * Made SPAM-safe URLs aware of image buttons (BUG#508). + Patch: Francis Daly, Jorge Arellano + - * Added a web search dialog (with toolbar icon, shortcut: Ctrl-S). + The search engine can be set in dillorc (defaults to google). + Patch: Johan Hovold, Jorge Arellano + - * Fixed a problem with libpng options detection (configure.in). + Patch: Rubén Fernández + - * Added "pthreads" (with an "s") detection to configure.in. + Patch: Andreas Schweitzer + - * Added the "-geometry" switch to the CLI. + Patch: Jorge Arellano, Jan Dittmer + + +dillo-0.7.3 [Aug 03, 2003] + + - * Some more selection goodies: + - Redesign of the selection state model, now the selection is preserved + as long as possible. + - Highlighted text is now drawn inverse (new DwWidget::bg_color). + - Selection of images, list bullets and hrulers (as text), with a common + text iterator for the respective widgets. + * Borders may now be drawn inverse (needed for selection). + * Improved the speed when selecting large areas. (BUG#450) + * Fixed a bug in DwPage extremes. + * Fixed a wrong implementation of incremental resizing for DwPage. + (Affected functions: Dw_page_rewrap and a_Dw_page_add_widget) + * Fixed a bug in a_Dw_widget_size_allocate. + * Made jumping to anchors faster (removes CPU hog). + * Fixed a bug in Dw_page_get_extremes(). + * Made (invalid) <li>'s without <ol> or <ul> defined, and independent of + each other. + * Fixed rendering of <frameset>. + Patches: Sebastian Geerken + - * Made a new set of toolbar icons! + Patch: John Grantham (http://www.grantham.de/) + - * Added support for the hspace and vspace attributes of the IMG tag. + * Made only left button activate the image input type (BUG#367,#451). + * Fixed SELECT with "multiple" but without "size" (BUG#469). + Patches: Jörgen Viksell + - * Added titles to bookmark server's html pages. + Patch: Kelson Vibber + - * Made IFRAME to be handled like FRAME (shows link). + Patch: Nikita Borodikhin, Jorge Arellano + - * Fixed a bug in 'a_Misc_stristr' that permeated findtext. (BUG#447) + Patch: Jorge Arellano, "squirrelblue" + - * Finished handling of single and double quotes inside dpi tags. + * Fixed a bug for named-entities' character codes greater than 255. + * Introduced a small UCS to Latin1 converter to help rendering. + * Added a check for Unix98's "socklen_t" (BUG#466). + * Added the missing EINTR handlers in IO.c and file.c. + * Fixed the problem of adding garbage anchors. + Patches: Jorge Arellano + + +dillo-0.7.2 [Apr 30, 2003] + + - * Implemented text selection! (aka: Copy&Paste) (BUG#59) + Patch: Sebastian Geerken, Eric Gaudet + - * Fixed IPv6 support when the unthreaded server is used. + Patch: Damien Couderc, Jorge Arellano + - * Fixed the IPv6 socket connection code for *BSD. + Patch: Daniel Hartmeier, Jorge Arellano + - * Made the URL_SpamSafe flag be inherited by the BASE element. + Patch: Melvin Hadasht + - * Switched configure.in to use AC_CANONICAL_SYSTEM instead of 'uname'. + Patch: Patrice Mandin + - * Added "image/x-png" to MIME types (obsolete, but should be recognized). + Patch: Paolo P. + - * Fixed the code that handled the installation of "dillorc". + Patch: Andreas Schweitzer + - * Fixed a lot of glitches in configure.in: notably libpng and libjpeg + detection, enabling and disabling. (BUG#: 386, 407, 392, 349)! + Patches: Andreas Schweitzer + - * Fixed two leaks in Dw(Ext)Iterator. + Patches: Jörgen Viksell + - * Repaired some minor misbehaviours in the cookie-strings parser. + Patches: Jörgen Viksell, Jorge Arellano + - * Enabled entities parsing in HTML-given hidden and password values. + Patch: Jorge Arellano, Francis Daly + - * Implemented character stuffing in dpi (Fix bookmarks with quotes) BUG#434. + * Added a HTML warning message for META outside HEAD. + * Removed a segfault source when the server doesn't send content/type info. + * Added file type detection for filenames without extension. + * Removed the warnings detected with gcc 3.2.2. + * Fixed the VERBATIM parsing mode and replaced the SCRIPT mode with it. + * Fixed the problem with CR handling in TEXTAREA (BUG#318). + * Fixed initial value parsing within TEXTAREA tags (BUG#400). + * Fixed loading files with spaces in the name (command line) BUG#437. + Patches: Jorge Arellano + + +dillo-0.7.1.2 [Mar 11, 2003] + + - * Fixed a bug in the bugfix that used uninitialized memory contents + causing all kind of undesirable side effects. + Patch: Andreas Schweitzer + + +dillo-0.7.1 [Mar 10, 2003] -- bugfix release + + - * Fixed the setting of the FD_CLOEXEC flag. + Patch: Raphael Barabas + - * Added an automatic file-locking alternative for systems lacking flock(). + Patch: Yang Guilong + - * Fixed a memory leak with pixmaps. + Patch: Keith Packard + - * Fixed the link color switch with scroll wheel mouses (BUG#348) + Patch: Stephen Lewis + - * Made the bookmarks server keep a backup file: bm.txt.bak. + * Fixed not loading the bookmarks file (and erasing the bookmarks). + * Added some missing EINTR handlers. + * Added a handler for unresponsive dpi sockets! + * Restricted dpi-requests to dpi-generated pages only. + * Used -1 instead of WAIT_ANY (some systems don't have it). (BUG#413) + * Fixed a source bug when G_DNS_THREADED is not defined. (BUG#421) + * Switched sprintf to g_snprintf which is safer. + Patches: Jorge Arellano + + +dillo-0.7.0 [Feb 17, 2003] + + - * Added IPv6 support! [./configure --enable-ipv6] (BUG#351) + Patch: Philip Blundell + - * Fixing char escaping/encoding problems with file URIs (BUG#321) + * Fixing buffer overflow sources in file.c. + * Switched the image tooltip from "alt" to "title" attribute. + Patch: Francis Daly, Jorge Arellano + - * Added code so that tooltips stay within the screen. + Patch: Pekka Lampila, Sebastian Geerken + - * Fixed a problem occurring when scrolling with the "b" key. + Patch: Livio Baldini + - * Fixed a memory leak in DwAlignedPage. + Patch: Jörgen Viksell, Sebastian Geerken + - * Moved stuff into remove_cookie() and add_cookie() functions. + * Made cookies sort once in add_cookie(). + * Removed some unneeded casts and calls in cookies. + * Repairing some minor misbehaviours in Cookies_parse_string(). + Patches: Jörgen Viksell, Jorge Arellano, Madis Janson + - * Fixed a bug in Dw_widget_mouse_event. + Patch: Jörgen Viksell + - * Fixed a bug in DwPage ("height" argument). + Patch: Pekka Lampila + - * Removed a segfault source in http.c + Patch: Madis Janson + - * Removed space around tables. + * Implemented the <button> tag! (BUG#276) + * Added iterators (DwItetator, DwExtItetator, DwWordItetator). + - Rewrote findtext, added highlighting and "case sensitive" option. + - Improved findtext dialog placement too! + * Implemented "ALIGN = {LEFT|RIGHT|CENTER}" for <table>, and + "ALIGN = {LEFT|RIGHT|CENTER|JUSTIFY}" for <tr>. + * Implemented character alignment, applied it on ALIGN=CHAR and CHAR for + <tr>, <td> and <th>. + - New widget DwTableCell. + - Some smaller changes in DwAlignedPage and DwPage (virtual word_wrap, + ignore_line1_offset_sometimes). + * Implemented vertical alignment of table cells. + - Changed behavior of Dw_page_size_allocate. + - Applied it on "VALIGN={TOP|BOTTOM|MIDDLE|BASELINE}" for <tr>, <td> and + <th>. + - Fixed splash screen. + * Set the height of <BR>'s in non-empty lines to zero. + * Moved some code from html.c to a_Dw_page_change_link_color. + * Made bullets size depending on the font size. + * Fixed too wide widgets in lists (e.g. nested lists). + Patches: Sebastian Geerken + - * Added support for <input type=image...> (BUG#313) + Patch: Madis Janson, Sebastian Geerken, Jorge Arellano + - * Made a better EAGAIN handler, and enabled FreeIOVec operation in IOWrite. + Patch: Jorge Arellano, Livio Baldini + - * Fixed include directives for config.h + Patch: Jorge Arellano, Claude Marinier + - * Made lots of minor cleanups. + Patches: Lex Hider, Jorge Arellano, Rudmer van Dijk + - * Added a simple command line interface, and enabled some options (BUG#372). + * Added full-window option in command line and dillorc. + * Added an option to set offline URLs from CLI. + * Made dillo embeddable into other GTK applications. + Patches: Jorge Arellano, Melvin Hadasht + - * Made drafts for dillo plugins protocol (dpi1) + Work: Jorge Arellano, Eric Gaudet + - * Avoided a file lock when cookiesrc disables cookies (BUG#358). + * Fixed scroll-jumping between widgets when pressing Up&Dn arrows. + * Added a tiny warning/handler for meta refresh. + * Concomitant Control Chain (CCC): + - Extended the theory to allow bidirectional message passing. + - Renewed the API. + - Improved the debugging code. + - Redesigned the old CCCs, and made a new one for plugins (dpi). + - Reimplemented dillo's core with the new chains. + * Input/Output engine (IO): + - Extended the functionallity with a threaded operation that + allows buffered writes of small chunks on the same FD. + - Created a new IO API, and adapted dillo to it. + * Used the new CCC and IO to implement dillo plugins! (dpi). + * Implemented the internal support for a bookmarks dpi. + * Wrote a dpi-program for bookmarks. + * Created capi.c, a meta module for cache.c. + * Restructured Html_write so custom HTML can be inserted. + * Set BackSpace and Shift+BackSpace to work as Back/Forward buttons. + * Set the escape key as a dialog closing shortcut. + * Removed a segfault in find text with a string of spaces (BUG#393) + * Added wrappers/whitespace filtering for pasted/typed/CLI URLs. (RFC-2396) + * Added an HTML warning message for illegal characters inside URLs. + * Made dpi communication go through unix domain sockets. + * Enabled dillo to launch the bookmarks plugin! + * Made some cleanups in IO/. + Patches: Jorge Arellano + + +dillo-0.6.6 [May 30, 2002] + + - * Added a few canonical casts to fix some obvious 64bit issues. + Patch: pvalchev + - * Fixed a bug with cookies path parsing. + * Fixed persistent-cookies obliteration (BUG#312, BUG#314) + * Set max 20 persistent cookies for each domain. + Patches: Jörgen Viksell + - * Switched flock to lockf. + Patch: Andreas Schweitzer + - * Made a little bugfix in doc/Makefile.am. + Patch: Grigory Bakunov + - * Removed the < 256 hostname length restraint from http queries. + * Made a date-parser that copes with three HTTP date-syntaxes (BUG#335) + * Made the HTML parser a bit more robust with bad HTML (BUG#325, BUG#326) + Patches: Jorge Arellano + + +dillo-0.6.5 [Apr 26, 2002] + + - * Improved a bit table rendering speed. + Patch: Mark Schreiber + - * Extended Dw crossing events. + Patch: Sebastian Geerken + - * Added code to autoresize the "View source" window (BUG#198). + Patch: Andreas Schweitzer + - * Improved *BSD detection code at './configure' time. + Patch: Andreas Schweitzer, Jorge Arellano + - * Added a (pthread_t) cast in dns.c + * Fixed a problem with #fragment hash-lookup (in anchors_table). + * Added code to install/test usr/local/etc/dillorc (BUG#287) + * Added control-character filtering for pasted/typed URLs. + * Replaced the old cache list with a hash table! + Patches: Livio Baldini + - * Fixed a momentous memory leak in png decoding. + * Fixed a segfault source in GIF colormap handling. + Patch: Livio Baldini, Jorge Arellano + - * Added fontname selection to dillorc. + Patch: Arvind Narayanan + - * Removed a segfault source under G_IO_ERR conditions in IO.c. + Patch: Madis Janson + - * Removed a wild deallocation chance in klist.c + Patch: Pekka Lampila + - * Fixed saving of pages that result from POST. + Patch: Nikita Borodikhin + - * Fixed a tiny bug with dillorc parsing on certain locales (BUG#301) + Patch: Lars Clausen, Jorge Arellano + - * Added support for cookies! RFC-2965 (BUG#82) + Patch: Jörgen Viksell, Lars Clausen, Jorge Arellano + - * Added code to detect redirect-loops (BUG#260) + Patch: Jorge Arellano, Chet Murthy + - * Added support for missing Content-Type in HTTP headers (BUG#216) + * Added support for a bare '>' inside attribute values (BUG#306) + Patch: Jorge Arellano, Andreas Schweitzer + - * Allowed enter to submit forms when there's a single text entry. + * Added 'generate_submit' and 'enterpress_forces_submit' to dillorc. + Patch: Jorge Arellano, Mark Schreiber. + - * Added support for rendering adjacent <BR>, Tabs in <PRE>, and linebreak + handling (BUG#244, BUG#179, BUG#291). + Patch: Jorge Arellano, Mark Schreiber, Sebastian Geerken. + - * Switched a_List_* methods to three parameters (and wiped BUG#286) + * Fixed two little bugs within url.c (BUG#294) + * Created an API for nav_stack usage (a handy cleanup). + * Set the attribute parser to trim leading and trailing white space. + * Fixed a problem with NULL requests to the dns (BUG#296). + * Added Tru64(tm) detection code at './configure' time. + * Fixed the parser to skip <style> and <script> contents (BUG#316). + * Bound the space key to PgDn, and 'b' | 'B' to PgUp. + * Allowed 'query' and 'fragment' in POST submitions (BUG#284). + * Changed the url module API (the URL_* macros), and updated the calling + modules, removing several potential bugs at the same time --toilsome. + Patches: Jorge Arellano + + +dillo-0.6.4 [Jan 29, 2002] + + - * Implemented remembering of page-scrolling-position! (BUG#219) + Patch: Jorge Arellano, Livio Baldini + - * Moved jpeg's include directory from CFLAGS to CPPFLAGS in configure.in + Patch: John L. Utz, Lionel Ulmer + - * Made a standarization cleanup to every *.h + * Cleaned some casts to use the GPOINTER_TO_INT and GINT_TO_POINTER macros. + * Added the 'static' qualifier to some module-internal variables. + * Added the 'static' qualifier to module-internal functions! + Patches: Jörgen Viksell + - * New widget DwAlignedPage for alignment of vertical arrays. + - New widget DwListItem for nicer list items (based on some extensions + of DwPage) BUG#271. + * Implemented text alignments (except CHAR). + - Extension of DwStyle and DwPage. + - Applied it on "ALIGN = {LEFT|RIGHT|CENTER}" for <hr>, and + "ALIGN = {LEFT|RIGHT|CENTER|JUSTIFY}" for <p>, <hN>, <div>, <td> and + <th>. Implemented <center> --BUGs #215, #189. + * Small change in DwPageWord (space_style), fixes problems with spaces and + underlining (BUG#278). + Patches: Sebastian Geerken + - * Added 'force_visited_colors' to dillorc. It asserts a different color + on visited links, regardless of the author's setting. + Patch: Jorge Arellano, Sebastian Geerken + - * Updated and improved several #include directives inside *.c + * Added history.c for linear history and scroll-position tracking. + Now the navigation-stack references linear history and nav-expect + holds a DilloUrl (history.c provides an API). + * Fixed a rare data-integrity race-condition with popups (BUG#225) + * Made small icons a bit narrower. + * Fixed a problem with image-maps handling code (BUG#277) + * Added support for several domains in dillorc's 'no_proxy' variable. + * Fixed a small boundary-bug in named-colors parsing. + * Implemented IOs validity-test with klist (avoids a rare segfault source). + Patches: Jorge Arellano + + +dillo-0.6.3 [Dec 23, 2001] + + - * Removed a_Dw_widget_set_usize. + * Removed *_indent in DwStyle, this is now done by nested widgets. + * List items are now single widgets, this fixes bug #78. + * Extended queue_resize and related code, removed fast resizing. + - Applied these changes on DwPage (many changes!). + * Changes in requisition of DwPage. + * Added a nice indenter to the pagemarks! ("Jump to..." menu). + Patches: Sebastian Geerken + - * Reworked the dicache to use a hash table and use image versions. + * Wiped some dicache glitches, and added a dillorc option turn it off! + (reducing memory usage significatively). + Patches: Livio Baldini + - * Added support for OSes that use a slightly different 'struct sockaddr'. + Patch: Johan Danielsson + - * Removed a cache leak when reloading (BUG#257). + Patch: Livio Baldini, Jorge Arellano + - * Added full-screen mode! (left double-click toggles it). + Patch: Jorge Arellano, Sebastian Geerken + - * Extended interface customization options in dillorc (a must for iPAQ). + Patch: Jorge Arellano, Sam Engström + - * Rewrote the whole tag-parsing code with a new scheme (single pass FSM!) + (BUG#190, BUG#197, BUG#207, BUG#228, BUG#239) --Big work here. + Patch: Jorge Arellano, Jörgen Viksell + - * Set form encoding to escape everything but alphanumeric and -_.* (BUG#236) + * Rewrote Html_tag_open_input. + * Extended BACK and FWD key shortcuts to: {ALT | MOD*} + {, | .} :-) + * Fixed URI fragment parsing (BUG#247). + * Centered FindText and OpenUrl dialog windows. + * Structured dillorc (now it's more readable! ;) + * Added a dillorc option to force transient_dialogs. + * Fixed a subtle bug with HTTP end-to-end reload (BUG#234). + * Fixed form submition when action has <query> or <fragment> (BUG#255) + * Added fast URL resolving methods! (96% rfc2396 compliant by now) BUG#256 + * Switched form-urlencoded CR to be sent as CR LF pair (BUG#266). + * Fixed leaving open FDs when the socket connection fails (BUG#268). + Patches: Jorge Arellano + + +dillo-0.6.2 [Oct 17, 2001] + + - * Added code to parse away <?...> tags (BUG#203). + Patch: Sebastian Geerken + - * Made an explicit ISO8859-1 requirement in font loading (BUG#193). + Patch: Karsten M. Self + - * Added a temporary handler for frames! (lynx/w3m like). + Patch: Livio Baldini + - * Added gtk_set_locale to dillo's init sequence (BUG#173). + Patch: Eric Gaudet, Martynas Jocius + - * Added support for <big> and <small> tags (BUG#221). + Patch: Livio Baldini, Jorge Arellano + - * Added back and forward history popup menus! (BUG#227) + Patch: Jorge Arellano, Eric Gaudet, Olaf Dietsche + - * Removed anchors from to-proxy queries (also added some checks, BUG#210). + * Removed a leak in url.c + * Fixed a bug with command-line HTML files that reference images (BUG#217). + * Improved status-bar messages a bit, modified toolbar pixmaps and + reduced the number of a_Url_dup calls. + * Set Ctrl-Q to close window and Alt-Q to quit. + * Devised an abstract model for parsing, wrote it into HtmlParser.txt and + made dillo compliant with it! + * Fixed CR/LF entities parsing inside <PRE> (BUG#188) + * Added an error message for unsupported protocols (BUG#226) + * Removed some warnings detected with different gcc versions. + Patches: Jorge Arellano + + +dillo-0.6.1 [Sep 13, 2001] + + - * Changed calculation of shaded colors. + * Eliminated redundant code when drawing background colors. + * Fixed a bug in DwStyle drawing functions. + * Fixed a bug in Dw_page_calc_widget_size. + * Some changes for <hr> (also BUG#168). + * Added <tr> backgrounds. + Patches: Sebastian Geerken + - * Added support for hexadecimal character references, as ¡ (BUG#183) + Patch: Liam Quinn + - * Replaced atoi(3) calls with strtol(3). + * Made path comparison case sensitive in a_Url_cmp. + Patches: Livio Baldini + - * Added a tiny handler for <DIV> + Patch: Robert J. Thomson + - * Fixed a segfault source in color parsing, and extended it a bit. + Patch: Scott Cooper, Jorge Arellano + - * Removed a leak with the DilloImage structure (when image is not found). + * Fixed (and made faster) Url_str_resolve_relative (BUG#194) + Patch: Jorge Arellano, Livio Baldini + - * Added parsing support for %HexHex escape sequences in file URIs + Patch: Jorge Arellano, Livio Baldini, Agustín Ferrín :) + - * Implemented Ctrl-W (close window) (BUG#87) + Patch: Jorge Arellano, Martynas Jocius + - * Fixed a segfault when dillo cannot access ~/.dillo for some reason. + Patch: Jorge Arellano, Amit Vainsencher + - * Fixed the segfault from untrue Content-Length in HTTP header (BUG#187) + * Fixed closing an active browser window from the window manager (bug#91) + * Eliminated anchors from HTTP queries (BUG#195) + * Fixed the repeated reload segfault (BUG#69) + * Updated some docs in doc/ dir. + * Added a keyed-list ADT (klist.[ch]) + * Removed a segfault source in dns.c. + * Massive changes in Cache module: redesigned the external and internal API, + implemented new methods, changed several algorithms, removed transitory + and obsoleted code, removed a segfault source and improved CCC operations. + * Changes in Http module: extended error handling, improved abort sequences, + and added code that's aware of race conditions (based on klist ADT). + * Uniformed CCC start operation in IO, http and cache modules. + Patches: Jorge Arellano + + +dillo-0.6.0 [July 31, 2001] + + - * Fixed a bunch of memory leaks! + * Fixed links on pages with only one line, tuned text-entries size and + fixed the HTTP header problem (BUG#180) + Patches: Jörgen Viksell + - * Improved dialogs handling: find_text, view_source, open_url, open_file, + save_link and save_page (also removed a leak here). + Patches: Jorge Arellano, Jörgen Viksell + - * Modified GtkDwScrolledWindow and GtkDwViewport, now scrollbars work much + better. This also fixes of the wrong viewport length (BUG#137). + * Implemented tables! (incomplete) + - Changes in Dw: DwTable and DwWidget::get_extremes. + - html.c: extended DilloHtmlState, added code for table parsing, moved + some attributes from DwPage into the HTML linkblock. + * Restructured code for image maps (works now with tables). + * Removed "alt" attribute from <a> tag (no standard). + * Fixed a bug in a_Url_dup. + * Extended Dw events: leave_notify_event is now called for more widgets. + * Extended DwPage and DwImage signal "link_entered". + * Extended DwStyle by CSS-style boxes, background colors and border_spacing: + - Implemented borders around image links (BUG#169). + * Fixed the wrong PNG background? (BUG#172) + * Corrected handling of styles by the html parser. + * Added alternative, "fast" resizing method. + * Added a simple possibility to scroll long option menus (BUG#158) + * Added backing pixmap, this prevents flickering (BUG#174). + * Changes and extensions in handling lenghts, see doc/DwStyle.txt. + * Added option "limit_text_width". + Patches: Sebastian Geerken + - * Added nowrap attribute to DwStyle, and applied it to <pre> (BUG#134), + <td> and <th>. + Patch: Jörgen Viksell, Sebastian Geerken + - * Added a_List_resize to list.h methods. + * Added debug.h to standarize debugging messages. + Patches: Sebastian Geerken, Jorge Arellano + - * Added file selection while saving pages or links. + Patch: Livio Baldini + - * Added a few 'const', a missing header and some strength reductions. + Patch: Aaron Lehmann + - * Made dillo to also check '/etc/dillorc' for options. + Patch: Eduardo Marcel Maçan, Jorge Arellano + - * Made a help page, and linked it to 'about:help' (BUG#72) + Patch: Jorge Arellano, Kristian A. Rink + - * Added an "alt" camp to DilloUrl + * Fixed the linkblock memory-leak (BUG#163) + * Fixed local file loading from the command line (BUG#164) + * Fixed server-side image maps support (BUG#165) + * Added code for accel-keys on toolbar buttons + * Fixed the segfault with unconnected servers (BUG#170) + * Fixed the open HTTP-sockets problem (BUG#171) + * Reimplemented the low-level file descriptor handling with GIOChannels + (and dillo became even faster!) + * Made reload-button to request an end-to-end reload (BUG#64) + * Fixed the multiple-POST problem, and added a confirmation dialog (BUG#175) + * Finished fixing the repeated link-click problem (BUG#74) + * Misc: rewrote the 'about:splash' method, tuned DwPage for minimal + memory usage, improved a_Color_parse and Html_read_coords, cleaned-up + popup-menus and linkblock initialization, eliminated a lock-source in + Html_parse_length. + * Added DEBUG_HTML_MSG macro for invalid HTML messages. + * Fixed the nav-stack (and multiple #anchors) problem (BUG#177) + * Added code to avoid segfaults with unhandled MIME types. + * Fixed dns.c from solving the same address on different channels (BUG#178) + * Improved error handling and extended the CCC scope! (mainly HTTP). + * Fixed a Dw-leak that was affecting: hr, bullets, images, tables (&pages)! + * Made several cleanups and added/fixed comments in various places. + * Reimplemented find-text with a faster algorithm and extended semantics!! + * Fixed some oddities with our autoconf stuff. + Patches: Jorge Arellano + + +dillo-0.5.1 [May 30, 2001] + + - * Designed a new URL handling scheme, and integrated it throughout the code! + Patch: Livio Baldini, Jorge Arellano + - * Removed a significative memory leak in dw_page. + * Added support for EAGAIN in IO.c + Patches: Livio Baldini + - * Removed 6 memory leaks! (of varying significance) + Patches: Jörgen Viksell + - * Fixed a bug in DwPage (BUG#162, crash when clicking on links). + * Removed a_Dw_gtk_viewport_queue_anchor and related code (becomes obsolete + with the new URL handling scheme). + * Speed-optimized key moving in GtkDwScrolledFrame (no more blocking). + * Fixed two memory leaks, in Dw_style_color_remove, and + Dw_style_font_remove. + Patches: Sebastian Geerken + - * Implemented the splash screen with "about:" (No more splash-file saving!) + * Set all pthreads to run in detached state. + * Reworked dillo's interface so now there're six options; available by + changing 'panel_size' and the new 'small_icons' in dillorc. + * Removed a minor leak in dns.c and a wild-deallocation source. + Patches: Jorge Arellano + + +dillo-0.5.0 [May 10, 2001] + + - * Implemented <IMG> ALT as tooltip. + * Fixed bug #135 (incorrect update of statusbar when leaving "ismap" img). + Patches: Livio Baldini, Sebastian Geerken + - * Completed image scaling (BUG#75). + Patch: Sebastian Geerken, Jorge Arellano + - * Fixed proxy support (BUG#150). + Patch: Livio Baldini + - * Fixed two bugs in the Dw event handling. + * Fixed bugs in DwEmbedGtk and GtkDwViewport: idle functions are now + removed properly. + * Fixed bug in DwEmbedGtk (added call of parent_class->destroy). + * Moved DwPageAttr into a new submodule (DwStyle). + - Applied DwStyle to DwBullet (BUG#146). + - Implemented immediate changing of link color provisionally (BUG#152). + * Fixed positioning of headers (half of BUG#118). + * Fixed rendering of <b><i> and <i><b> (BUG#145). + * Fixed unrecognized dillorc text_color setting (BUG#151). + Patches: Sebastian Geerken + - * Changed word/line structure of DwPage + * Improved FORM sending (standar name/value submits) and processing; + added READONLY, SIZE, MAXLENGTH attributes, type=BUTTON and some cleanups + * Fixed VERBATIM parsing mode (BUG#130) + * Fixed a bug in calculating the page width (BUG#136) + Patches: Jörgen Viksell + - * Added a dillorc option to set the location entry within the menu bar. + Patch: Eric Gaudet + - * Integrated some modifications to ease compiling on GNU Darwin. + * Added support for leading whitespaces in HREF (BUG#120) + * Fixed anchor's hash_table and a few more quirks (were warnings on Alpha) + * Fixed entities parsing in URI attributes (BUG#114) + * Fixed stop button's sensitivity on plain files (BUG#142) + * Made filesize more accurate on directory listings (BUG#143) + * Introduced the new Concomitant Control Chain (CCC) design! + - All the way in the quering branch + - Halfway in the answering branch + - Introduced more error handling and status messages + - Started implementing error control using the CCC! + - Fixed too much caching (BUG#84) + - Fixed a CPU hog error condition (BUG#144) + - Eliminated the segfault from outdated dns answers (BUG#140) + - Fixed repeated Back (faster than rendering) segfault. + * Cleaned the header include files + * Incremented the valid-charset for dillorc identifiers (BUG#149) + * Added support for unterminated quotation of attribute values (BUG#155) + Patches: Jorge Arellano + + +dillo-0.4.0 [March 3, 2001] + + - * Rewrote most of the Dw module from scratch: + - Page widget: ported, added support for relative sizes of widgets, and + changed behaviour for pressing button 2 on a link. + - Removed the now unnecessary event boxes for check and radio buttons. + - Modified the code outside to use new Dw. + * Started to implement relative sizes for images (in html.c) + * Implemented attributes WIDTH, SIZE and NOSHADE of the <hr> tag. + * Added focus adjustment for selection lists (<SELECT>) + * Implemented TAB, Shift+TAB navigation in FORMS (BUG#86) + Patches: Sebastian Geerken + - * Included a scaling font_factor into dillorc! + Patch: Bruno Widmann + - * Fixed bugs #125 and #129 (menu item focus and radio reset in forms) + Patch: Jörgen Viksell + - * Added code to ignore anything inside STYLE tags. + Patch: Mark McLoughlin + - * Implemented image rendering based on GdkRGB and DwImage! + * Fixed 4 bit color planes support, cleaned the image code, + removed a few leaks and added documentation (Images.txt). + * Ported every patch from 0.3.2 to 0.4.0 + Patches: Jorge Arellano + + +dillo-0.3.2 [February 22, 2001] + + - * Added the option to use oblique font instead of italic (dillorc) + Patch: Eric Gaudet, Sebastian Geerken, Jorge Arellano + - * Changed Dw_page_find_line_index to use a binary search + Patch: Eric Gaudet, Jorge Arellano + - * Added a visual hint for visited links (BUG#117) + * Repaired the dillorc parser to skip unknown symbols (BUG#119) + Patch: Eric Gaudet + - * Fixed the segfault for controls outside FORM and SELECT elements (BUG#121) + Patch: Eric Gaudet, Jörgen Viksell + - * Added support for SUB and SUP tags (BUG#115) + Patch: Jörgen Viksell + - * Added a geometry directive to dillorc (sets initial browser size) + Patch: Livio Baldini, Jorge Arellano + - * Fixed bookmarks loading in new browser windows (BUG#110) + * Included a workaround for BUG#71 + Patch: Livio Baldini + - * Fixed a CPU hog when clicking ftp URLs (BUG#123) + * Set a 64 bytes threshold on pagemark headers + Patch: Jorge Arellano + - * Added check for negative image sizes. + Patch: Livio Baldini, Sebastian Geerken + + +dillo-0.3.1 [December 22, 2000] + + - * Implemented basic Find-text functionality + Patch: Sam Dennis, Sebastian Geerken, Allan Clark and Jorge Arellano :-) + - * Implemented "Pagemarks" (Kind of a headings-based page index!) + Patch: Sebastian Geerken and Eric Gaudet + - * Fixed nested-lists numbering, and added support for "type" (BUG#76) + * Added support for image maps, both usemap and ismap! (BUG#27) + * Set "on" as default value for check boxes + Patch: Eric Gaudet, Jorge Arellano + - * Added "Copy link location" to the link menu + Patch: Eric Gaudet + - * Removed redundant functions from misc.c + * Added support for BASE, CODE, DFN, KBD, SAMP and VAR tags (BUG#106) + * Added support for TAB characters in plain text files (BUG#112) + Patches: Jörgen Viksell, Jorge Arellano + - * Fixed a_Url_squeeze (BUG#100) + Patch: Livio Baldini, Jorge Arellano + - * Added gamma support and basic transparency for PNG images (BUG#60) + * Moved menu_popup into the 'bw' structure (BUG#96) + * Fixed the gif decoder to get image size from the right place (BUG#98) + * Made the new browser window size the same as the parent (BUG#55) + Patch: Livio Baldini + - * Added support for ISINDEX method (BUG#15) + Patch: Sam Dennis + - * Added support for bare '<' character parsing + * Removed every sign-conflict warnings given by gcc with '-W -Wall' + * Fixed several identation problems (rendering) + * Implemented "Save link as" (link menu) + * Removed the subtle bug that used to segfault when deleting and processing + queue clients at the same time (BUG#111). + * + Some comments, cleanups, size reductions, minor optimizations etc. + Patches: Jorge Arellano Cid + + +dillo-0.3.0 [November 13, 2000] +(Lots of patches are pending!) + + - * Added support for <strike>, <s>, <del> and <u> tags. + Patch: Jörgen Viksell + - * Fixed a bug in #anchors code + Patch: Sebastian Geerken + - * Parsed text between script tags, out of the rendering part. + * Added support for decimal entities that start with 0. + * Added some comments to html.c + Patches: Sean 'Shaleh' Perry + - * Added support for corrupted png images (avoids segfaults!) + Patch: Eric Gaudet, Jorge Arellano + - * Fixed empty title bookmarking (BUG#85 and #88) + Patch: Livio + - * Fixed view-source to take its URL from the right place. + Patch: Sam Dennis + - * Added font support for the compaq iPaq linux distribution. + Patch: Eric Christianson + - * Fixed spaced attribute parsing (BUG#79). + * Fixed concurrent save and downloading! + * Added alpha support for external (simple) plugins. + ? * Added a workaround (maybe a bug fix) for BUG#77 (No segfault). + * Introduced a new design layer between the IO and Dw: + - The imgsink stuff was completely removed. + - The dicache was rewritten from scratch and integrated + into the normal cache. + - A single client queue is being used for both caches. + - The file descriptors were replaced by cache keys that serve + as connection handlers. + - The image data structure and related sources got changed. + - Every decoder (png, gif, jpeg) was adapted to the new scheme. + * Fixed the file-images caching problem and the associated memory-leaks. + * Improved progress bar accuracy for images. + * Added progress bar functionality for plain text (+comments +cleanups) + * Fixed the right-click-over-plain-text segfault (BUG#80). + * Started improving the right-mouse-button menus. + Patches: Jorge Arellano Cid + + +dillo-0.2.4 [August 21, 2000] + + - * Fixed the white square bug with PNG images (BUG #4) + Patch: Luca Rota + - * Added support for #anchors! (BUG #10) + * Added support for resolving relative #anchors (BUG #65) + Patches: Sebastian Geerken + - * Fixed a segfault-source that produced BUG #61. + * Made several cleanups and standarizations in html.c + * Extended entity-parsing scope, and the list of supported entities. + * Rearranged TagInfo data into a new structure. + * Added the base for refresh support in META tags. + Patches: Sean 'Shaleh' Perry + - * Added support for TEXTAREA tags! + Patch: Jörgen Viksell + - * Improved and fixed Html_parse_entities. + * Reimplemented the Stash buffer with a GString. + * Fixed a bug with \r\n-terminated HTML lines. + * Added redirection support for relative URLs (BUG #73). + * Added some comments and minor fixes to patches. + Patches: Jorge Arellano Cid + - * Linked "open link in new window" to mouse button #2 (#3 also works) + Patch: Eric Gaudet + + +dillo-0.2.3 [August 4, 2000] + + - * Removed "search.h" include in http.c (freeBSD compatibility). + Patch: Kurt J. Lidl + - * Removed several memory leaks that were sprinkled through the code. + Patches: Jörgen Viksell + - * Fixed a segfault crash when hitting PgDn in the URL box (BUG #54). + * Removed a segfault source in commands.c + * Made some minor fixes to Dw and added more comments to the code. + * Made changes in dw_gtk_view.c, and fixed the rendering problem that + arise when changing from a scrolled page into another (BUG #58). + * Changes in hruler dynamic resize --not finished though. + * Removed a floating point exception bug in image handling code (image.c) + * Dramatically improved rendering speed!!! Most notably long HTML pages + with lots of links; Improvement ranges from 2 to 5 times faster! (aprox.) + * Fixed misplaced rendering of small pages (BUG #35) + * Fixed the bookmark bug with empty title strings (BUG #57, #67) + * Completed support for "\r", "\n" and "\r\n" in PRE tags. + * Fixed text rendering between multiple selection boxes (BUG #56) + * Added several minor enhancements (comments, formatting, speed, etc) + * Added extensive documentation! (IO.txt, DilloWidget.txt and Dw.txt) + Patches: Jorge Arellano Cid + + +dillo-0.2.2 [July 9, 2000] + + - * Added a gtk_window_set_wmclass to all windows to prevent dialogs + from having the same size as the main window. (Ex: with Sawfish) + * Made some width and height changes to the SELECT-stuff + * Added "submit" to submit buttons without a value. + Patches: Jörgen Viksell + - * Fixed a segfault when calling "about:" method + Patch: Dominic Wong + - * Added an option to force dillorc-defined colors (Try it with slashdot!) + * Fixed display of encoded-URL-links on the status bar + Patches: Adam Sampson + - * Removed several compiler dependencies + (detected with lcc on a 64 bit machine) + * Modified mime.c and Url.c to use list.h, and eliminated hdlr.c + * Standarized unsigned types to glib all around the code + * Added some includes for libc5 compatibility + * Modified IO_callback to avoid a CPU-hog (it happened in some systems). + * Fixed a bug that added a trailing ampersand to GET and POST queries. + * FIxed attribute parsing. It had nasty side effects; as providing + wrong attribute values to POST and GET methods. + * Joined Url.c and url.c into a single module. + * Reimplemented URL resolving functions. + * Implemented a new parser for "file:" URLs (Try "file:" & "file:."). + * Removed child_linkblock and changed the HTML stack handling + (both changes result in a simpler, easier to understand code). + * Modified and removed a segfault source in Html_lb_new. + * Modified forms handling to be more tolerant with messy HTML. + * Linked "image/pjpeg" in MIME types (progressive jpeg) + * Fixed form submittion when there's no submit button (bug #49) + Now dillo can search on freshmeat, altavista, etc! + Patches: Jorge Arellano Cid + + +dillo-0.2.1 [June 17, 2000] + + - * Modified the pixmaps for better interface perception ;) + * Modified Dw_gtk_view_adjustment_value_changed to update the visible + rectangle even though the widget is not realized; it seems to work! + * Implemented the horizontal ruler as a Dw --dw_hruler.[ch] + Fixed its expose problems (Bug #13). (todo: resizing). + * Changed Dw_gtk_progressbar module to "progressbar" --naming stuff + * Added Content-length in file headers (avoids reallocations) + * Modified form submittion and encoding to use dynamic memory allocation + * Eliminated a dns.c hack that passed an int as a void* ;) + * Updated the documentation with an extensive IO description. + Patches: Jorge Arellano Cid + - * Added some functionality to reload button (not complete yet) + Patch: Luca Rota , Jorge Arellano Cid + - * Fixed hash handling within URL parsing. (Bug #9) + Patch: Marcos Ramírez , Jorge Arellano Cid + + +dillo-0.2.0 [June 2, 2000] +*** THIS IS A HALF-NEW BROWSER *** + + - * Finally reimplemented the whole networking process (***BIG changes***) + Rewrote from scratch: IO, cache, web, http, socket, ... + Modified: gif, png, jpeg, html, nav, plain, ... (Every client) + All the querying, retrieving, caching and delivering is NEW!!! + * Eliminated CPU-hogging while waiting for a DNS query to complete + * Eliminated CPU-hogging when facing redirections + * Implemented basic redirection functionality + * Eliminated several segmentation fault bugs + * Modified autoconf stuff + * Modified source-code tree and libraries + * Reduced binary size + * Eliminated a memory leak in socket connections + * Created a new socket connection scheme + * Implemented Cache as the main networking abstraction layer + * Joined almost every URL-retrieving module into libDio + * Set the basis for save-link-as functionality (see save function) + * Modified the navigation stack to a cleaner design + * Improved status bar messages when connecting + * Changed some function names + * Created new pixmaps for the toolbar! + * Added a "new" button near the URL to clear the entry! + * Added a_List_remove to list.h + * Updated documentation in doc/ + (README, Cache.txt, store.txt, Dillo.txt, Images.txt and IO.txt) + Patches: Jorge Arellano Cid + - * Added a workaround patch for BUG #35 (page expose problems) + Patch: Andrew McPherson + + +dillo-0.1.0 [Mar 30, 2000] + + - * Fixed a bug that used to lock hostname queries. + ('DNS can't resolve name' mesg.) + * Fixed the wrong parent link when browsing directory contents + * Changed the file/directory HTML-output-layout + * Finally rewrote the whole file.c module :-) + * Made Http_query buffer overflow-safe + * Commented and cleaned web.c + * Changed the licence to GPL. (Raph agreed on that) + * Fixed a tag-search bug in html.c; it produced rendering problems. + * Fixed a parsing problem with tags that were split on different lines + * Fixed the after-tables parsing problem + * Added a startup page + Patches: Jorge Arellano Cid + - * Fixed a bug with http queries that sometimes produced infinite loops + Patch: Marcos Ramírez + + +dillo-0.0.6 [Mar 9, 2000] + + - * Readded an old, wiped-by-mistake, bug fix. + * Added preferences settings using a readable config (dillorc) + * Added a page-title trimmer facility (39 chars) to bookmarks saving. + Patch: Luca Rota + - * Fixed three memory leaks in bookmarks reading + * Added 'Open link in a new window' within the right button pop-up-menu + Patch: Sammy Mannaert + - * Fixed a bug that used to put two slashes on directory file anchors + * Actualized plugin.txt to current code base (and a bit of fix) + * Changed "fprintf(stderr..." to "g_print(..." + * Improved list.h + * Fixed image URLs both for HTTP and local files! + * Fixed tag attribute parsing (The trimmed-text-inside-buttons bug) + * Wrote several documentation files (placed them in doc/) + * Fixed transparent image rendering + * Implemented a binary search for HTML tags (just a bit faster) + * Small leak fixes and some corrections to http.c + * Made style fixes, added function comments and things like that. + Patches: Jorge Arellano Cid + + +dillo-0.0.5 [Feb 3, 2000] + + - * Added progress bars (to be improved) + Patch: James McCollough, Jorge Arellano Cid + - * Rearranged, changed and commented the decompressed image cache code + * Fixed autoconf stuff to include zlib + * Added memory deallocating functions to cache, dicache, socket, http & dns + * Fixed memory leaks in jpeg.c, png.c and gif.c + * Made several changes in dw_page.c and dw_image.c + * Introduced 'list.h' source, and standarized the whole code to use it + * Fixed image rendering (Fixed algorithms and data structures) BIG CHANGES + * Removed some false comments and added true ones (I hope ;) + * Made several "standarizing" changes all over the code and + * some other changes (not worth listing) + Patches: Jorge Arellano Cid + - * Added support for 'text' and 'link' colors inside <BODY> tags + * Standarized all memory management to glib functions + * Fixed the plugin API to work again (forked) + * Removed a warning (remove not implemented for Dw_view and Dw_scroller) + * Solved the page-without-title bug in bookmarks. + Patches: Luca Rota + + +dillo-0.0.4 [Jan 4, 2000] + + - * Removed the test widget + * Added a jpeg image decoder error-handler + Patches: Sammy Mannaert + - * Changed some ADTs to glib to be compatible with newer glibc2 systems + * Added background color alternative when bg. is white (or not specified) + * Improved connecting time status messages + Patches: Jorge Arellano Cid + - * Added background color support. + Patch: Luca Rota, James McCollough + - * Added support for <OL></OL> tags + * Added view-source and view-bookmarks functionality + * Improved PgUP/PgDown and Up/Down response. (No need to grab focus!) + * Fixed openfile gtk run-time warning + * Fixed the focus problem with text camps + * Fixed the title-linger bug with pages that don't specify title. + * Added a preliminary right button menu + * Added POST method support + Patches: Luca Rota + - * Added PNG image support. + Source Code: Geoff Lane, Patch: Jorge Arellano + + +dillo-0.0.3.tar.gz [Dec 18, 1999] + + - * Finished the whole Naming&Coding effort!!! + Stage 2 worked by: Luca Rota and Jorge Arellano + - * Removed all compile time warnings (at least with gcc 2.7.2.3) + * Added more documentation inside the code + * Removed the '~/.dillo' directory creation bug. + * Integrated a patch for menu module + * Renamed menus.c to menu.c + * And some other minor things... + Patches: Jorge Arellano Cid + + +dillo-0.0.2.tar.gz [Dec, 1999 --Does anyone remember the day?] + + - * Finished stage one of the naming&coding design (Hey, it's 1.3 Mb of code!) + Worked by: Jorge Arellano, Sammy Mannaert, James McCollough and Luca Rota + - * Removed some bugs and renamed the source files. + * Heavily rearranged URL/ an IO/ files for better understanding & debugging + * Added more documentation within the sources + * Recoded automake stuff + * Integrated some queued patches + * (And several things that I have no time to write now! :-) + Patches: Jorge Arellano Cid + + +dillo-0.0.1.tar.gz [Dec, 1999] + + - * Halfway release, amidst stage one of the naming&coding effort. + Worked by: Jorge Arellano, Sammy Mannaert, James McCollough and Luca Rota + + +dillo-0.0.0.tar.gz [Dec, 1999] + + - * Applied a cleanning patch to menus.[ch] + Patch: Sammy Mannaert + - * Made a threaded DNS scheme (several improvements: now it works with gdb) + * Bug fix on TMP_FAILURE_RETRY + * Bug fix on links not been followed (Url parsing) + * Changed the default pixmaps + * Maked automake, autoconf, autoheader, changes + * Changed binary name + Patches: Jorge Arellano Cid + + + diff --git a/INSTALL b/INSTALL new file mode 100644 index 00000000..3b50ea95 --- /dev/null +++ b/INSTALL @@ -0,0 +1,176 @@ +Basic Installation +================== + + These are generic installation instructions. + + The `configure' shell script attempts to guess correct values for +various system-dependent variables used during compilation. It uses +those values to create a `Makefile' in each directory of the package. +It may also create one or more `.h' files containing system-dependent +definitions. Finally, it creates a shell script `config.status' that +you can run in the future to recreate the current configuration, a file +`config.cache' that saves the results of its tests to speed up +reconfiguring, and a file `config.log' containing compiler output +(useful mainly for debugging `configure'). + + If you need to do unusual things to compile the package, please try +to figure out how `configure' could check whether to do them, and mail +diffs or instructions to the address given in the `README' so they can +be considered for the next release. If at some point `config.cache' +contains results you don't want to keep, you may remove or edit it. + + The file `configure.in' is used to create `configure' by a program +called `autoconf'. You only need `configure.in' if you want to change +it or regenerate `configure' using a newer version of `autoconf'. + +The simplest way to compile this package is: + + 1. `cd' to the directory containing the package's source code and type + `./configure' to configure the package for your system. If you're + using `csh' on an old version of System V, you might need to type + `sh ./configure' instead to prevent `csh' from trying to execute + `configure' itself. + + Running `configure' takes a while. While running, it prints some + messages telling which features it is checking for. + + 2. Type `make' to compile the package. + + 3. Optionally, type `make check' to run any self-tests that come with + the package. + + 4. Type `make install' to install the programs and any data files and + documentation. + + 5. You can remove the program binaries and object files from the + source code directory by typing `make clean'. To also remove the + files that `configure' created (so you can compile the package for + a different kind of computer), type `make distclean'. There is + also a `make maintainer-clean' target, but that is intended mainly + for the package's developers. If you use it, you may have to get + all sorts of other programs in order to regenerate files that came + with the distribution. + +Compilers and Options +===================== + + Some systems require unusual options for compilation or linking that +the `configure' script does not know about. You can give `configure' +initial values for variables by setting them in the environment. Using +a Bourne-compatible shell, you can do that on the command line like +this: + CC=c89 CFLAGS=-O2 LIBS=-lposix ./configure + +Or on systems that have the `env' program, you can do it like this: + env CPPFLAGS=-I/usr/local/include LDFLAGS=-s ./configure + +Compiling For Multiple Architectures +==================================== + + You can compile the package for more than one kind of computer at the +same time, by placing the object files for each architecture in their +own directory. To do this, you must use a version of `make' that +supports the `VPATH' variable, such as GNU `make'. `cd' to the +directory where you want the object files and executables to go and run +the `configure' script. `configure' automatically checks for the +source code in the directory that `configure' is in and in `..'. + + If you have to use a `make' that does not supports the `VPATH' +variable, you have to compile the package for one architecture at a time +in the source code directory. After you have installed the package for +one architecture, use `make distclean' before reconfiguring for another +architecture. + +Installation Names +================== + + By default, `make install' will install the package's files in +`/usr/local/bin', `/usr/local/man', etc. You can specify an +installation prefix other than `/usr/local' by giving `configure' the +option `--prefix=PATH'. + + You can specify separate installation prefixes for +architecture-specific files and architecture-independent files. If you +give `configure' the option `--exec-prefix=PATH', the package will use +PATH as the prefix for installing programs and libraries. +Documentation and other data files will still use the regular prefix. + + If the package supports it, you can cause programs to be installed +with an extra prefix or suffix on their names by giving `configure' the +option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. + +Optional Features +================= + + Some packages pay attention to `--enable-FEATURE' options to +`configure', where FEATURE indicates an optional part of the package. +They may also pay attention to `--with-PACKAGE' options, where PACKAGE +is something like `gnu-as' or `x' (for the X Window System). The +`README' should mention any `--enable-' and `--with-' options that the +package recognizes. + + For packages that use the X Window System, `configure' can usually +find the X include and library files automatically, but if it doesn't, +you can use the `configure' options `--x-includes=DIR' and +`--x-libraries=DIR' to specify their locations. + +Specifying the System Type +========================== + + There may be some features `configure' can not figure out +automatically, but needs to determine by the type of host the package +will run on. Usually `configure' can figure that out, but if it prints +a message saying it can not guess the host type, give it the +`--host=TYPE' option. TYPE can either be a short name for the system +type, such as `sun4', or a canonical name with three fields: + CPU-COMPANY-SYSTEM + +See the file `config.sub' for the possible values of each field. If +`config.sub' isn't included in this package, then this package doesn't +need to know the host type. + + If you are building compiler tools for cross-compiling, you can also +use the `--target=TYPE' option to select the type of system they will +produce code for and the `--build=TYPE' option to select the type of +system on which you are compiling the package. + +Sharing Defaults +================ + + If you want to set default values for `configure' scripts to share, +you can create a site shell script called `config.site' that gives +default values for variables like `CC', `cache_file', and `prefix'. +`configure' looks for `PREFIX/share/config.site' if it exists, then +`PREFIX/etc/config.site' if it exists. Or, you can set the +`CONFIG_SITE' environment variable to the location of the site script. +A warning: not all `configure' scripts look for a site script. + +Operation Controls +================== + + `configure' recognizes the following options to control how it +operates. + +`--cache-file=FILE' + Use and save the results of the tests in FILE instead of + `./config.cache'. Set FILE to `/dev/null' to disable caching, for + debugging `configure'. + +`--help' + Print a summary of the options to `configure', and exit. + +`--quiet' +`--silent' +`-q' + Do not print messages saying which checks are being made. + +`--srcdir=DIR' + Look for the package's source code in directory DIR. Usually + `configure' can determine that directory automatically. + +`--version' + Print the version of Autoconf used to generate the `configure' + script, and exit. + +`configure' also accepts some other, not widely useful, options. + diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 00000000..ab2a5455 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,5 @@ +SUBDIRS = doc dlib dpip src dpid dpi + +EXTRA_DIST = ChangeLog.old dillorc2 install-dpi-local README-port + +sysconf_DATA = dillorc2 @@ -0,0 +1,33 @@ +---- +NEWS +---- + +Nov 2000: + + Introduced a new design layer between the IO and the Dw. + +March 2001: + + Finally the new dillo widget is ready! (dillo >= 0.4.0). + 0.4.0 is able to cope with low resolution depths. + +Apr 2002: + + We moved to: http://dillo.cipsga.org.br/ + +Dec 2002 + + We moved to: http://dillo.auriga.wearlab.de/ + +Jun 2003 + + http://www.dillo.org/ (hosted at the wearlab!) + +Sep 2007 + + The new FLTK2-based dillo code is released! (under GPL3) + + Jorge.- + jcid@dillo.org + Project maintainer, core developer, patcher, you name it! :-) + @@ -0,0 +1,76 @@ +======= + Dillo +======= + + This is an alpha release of the next generation of the Dillo +web browser. The code underwent a major rewrite: significant +parts of the codebase were ported to C++, and the rendering +engine now uses the FLTK2 library instead of GTK1. + + With regard to Dillo 0.8.6, dillo-f15f has some advantages (as +antialiasing and utf-8), and some disadvantages. The problems are +simple to solve and only need some man months to complete. When +we're there, dillo-f15f will be easily regarded as better than +the former series. + + This is release should be regarded as alpha. + + + Here's a list of some well known problems: + + * no iteration inside simple lists + * image links haven't been hooked yet + * you may experience crashes from "assert" statements from + unfinished code. + * the scrolling position is not yet updated (when following a + link, the former scroll position is kept). + * viewport scrolling is not yet optimized (takes lots of CPU) + * context menus are not yet completely hooked or activated + + * no FRAMES rendering + * no https -- read the FAQ to enable a protoype. + + +------------ +Dpi programs +------------ + + These are installed by "make install". If you don't have root +access, copy "dillo" and "dpid" to some directory in your path +and install the dpis by running "./install-dpi-local" from the +top directory (they will be installed under ~/.dillo). + + +---- +*BSD +---- + + Dillo may compile on *BSD systems; please report on this. +Please note that you'll need GNU make. + + From OpenBSD >= 3.3, gethost* calls are not thread safe. If +your dillo crashes or locks at times, just use: + + ./configure --disable-threaded-dns + + so dillo uses a single thread for name resolving. + + +------- +Solaris +------- + + Dillo may compile and run OK on Solaris but (please report): + + * use gmake (a symbolic link make -> gmake works OK) + + Solaris is very inconsistent so you may need to add/remove: + + -lrt -lposix4 + + at link time. + + +Jorge.- +(jcid@dillo.org) +Sep 30, 2007 diff --git a/README-port b/README-port new file mode 100644 index 00000000..42ed95b7 --- /dev/null +++ b/README-port @@ -0,0 +1,145 @@ +============================== + Porting Dw to Other Toolkits +============================== + +This is a pre-release, which only contains a small test-program to +test Dw with new new rendering abstraction. This rendering +abstraction, which is described in doc/DwRender.txt, will make Dw +portable at all, by implementing several interfaces, which are defined +in src/dw_render.h. + +Currently, the new rendering abstraction is incomplete, and so not +able to be integrated into dillo. There will be several pre-releases, +with the version number 0.8.3-pre-dw-redesign-<n>. + +Actual Porting +============== + +You will not have to read the whole "DwRender.txt", in order to port +Dw to another platform, instead, here is a description of what to do. + +Gtk Dependencies +---------------- +The current version must still be linked to Gtk+, since the Gtk+ +object model is used for class inheritance (especially the DwWidget +hierarchy). I have been careful to limit the dependencies to a +minimum, there is a script, src/search-gdk-refs, which detects all Gdk +dependencies (which are generally not allowed), as well as those Gtk+ +dependencies, which do not refer to the Gtk+ object model. + +Glib is also used, there are currently no limitations for the usage. + +These dependencies will be removed in the future, so it is recommended +not to use Gtk+ at all for the code, which is necessary for new +platform. + +Test Program +------------ +For the Gtk+ version of Dw, there is a test program dw-test in the src +directory. This should be rewritten by a version, which uses the newly +written platform specific implementations. + +Interfaces +---------- +Since Dw is (currently) written in C, interfaces become a bit +tricky. Generally, there are two structures, one for the object +itself, which implements the interface, and one static structure, +which contains static stuff related to the implementing object, mostly +methods. + +For example, a_Dw_render_layout_new expects two arguments, one for a +newly allcoated implementation the platform interface (as void*), and +another (static) instance of DwRenderPlatformClass. In the test +program, this is Dw_gtk_platform_class, which is defined in +dw_gtk_platform.c. Notice, that the first argument is destroyed, when +the layout is destroyed, while the second one is not touched. + +Methods are simply implemented as function pointers, which first +parameter is void*. When such a method is called, the first argument +is the object, in this case, what has been passed as first argument to +a_Dw_render_layout_new. In some cases, such an interface structure may +contain other members, such as DwRenderViewClass::class_id. + +When implementing an interface in C, you should look at +dw_gtk_platform.[ch] for an example. When using C++, it is probably +the best to put static methods into the *Class structures, which will +delegate to non-static methods. + +What to Implement +----------------- +There are two interfaces, which must at least be implemented, +DwRenderPlatform and DwRenderView. For details, see the comments in +dw_render.h. DwRenderPlatform is relatively simple, it provides some +(nearly) static, platform-specific properties. DwRenderView is rather +abstract, read DwRender.txt for possible implementations. However, +most important is a viewport implementation, this is also the only +current (Gtk+) implementation. + +Important note: Coordinates in the implementation of DwRenderView are +always world coordinates, so it may be to convert them into viewport +coordinates, when the implementation uses a small window for the +viewport (which is recommended for reasons immediately +following). Also, world coordinates are 32 bits, since 16 bits would +not be enough for the dimensions of a typical web page. + +The following functions will have to be called by the view, see the +comments in dw_render.c for details: + + p_Dw_render_layout_expose + p_Dw_render_layout_button_press + p_Dw_render_layout_button_release + p_Dw_render_layout_motion_notify + p_Dw_render_layout_enter_notify + p_Dw_render_layout_leave_notify + p_Dw_render_layout_scroll_pos_changed + p_Dw_render_layout_viewport_size_changed + +The first argument is always the layout, which has been passed to the +view in DwRenderView::set_layout. + + +Dw_style and Dw_tooltip +======================= + +These two modules are still bound to the Gtk+ platform, and so must be +re-implemented, which should, per se, not cause many problems. The +interfaces of these modules are kept in a way, which makes +toolkit-independant usage, e.g. whithin implementations of DwWidget +possible, e.g. by some simple typedef's (GdkGC -> DwGC +etc.). Dw_style_draw itself is independant of the toolkit. + +(This approach is possible on the long term, but will make it +impossible, to use multiple platforms at one time, see DwRender.txt +for details.) + + +UI Widgets +========== + +Embedding UI Widgets (buttons, entries, ...) into the Dw tree is +completly platform specific, see DwRender.txt, section "UI +Widgets". The Gtk+ implementation will reuse Gtk+ widgets, which are +embedded via a special Dw widget. This approach should be preferred in +other implementations. + +At least in Gtk+, embedding Gtk+ widgets is rather tricky, because the +world coordinates are represented by 32 bits. In Gtk+, this is solved +by using the widget GtkLayout as a base for the viewport, which does +some weird stuff like event filtering. An alternative implementation +should deal with this problem in a very early stage of development. + + +Images +====== + +There is another module, which has to be replaced, Imgbuf, which is +described in doc/Imgbuf.txt. Most of its interface should remain, the +only exception is a_Imgbuf_draw, which is only used by GtkDwViewport. + +A view must implement the method draw_image(), which will typically be +delegated to the image buffer (see Dw_gtk_viewport_draw_image() as an +example). The Gtk+ implementation does not use the passed DwGC, but +for portability, this remains in the platform independant part. + +DwImage should remain platform independant, all platform specific code +has been moved into Imgbuf. diff --git a/config.h.in b/config.h.in new file mode 100644 index 00000000..f2a30dc7 --- /dev/null +++ b/config.h.in @@ -0,0 +1,106 @@ +/* config.h.in. Generated from configure.in by autoheader. */ + +/* Enable GIF images */ +#undef ENABLE_GIF + +/* Enable JPEG images */ +#undef ENABLE_JPEG + +/* Enable PNG images */ +#undef ENABLE_PNG + +/* Enable SSL support */ +#undef ENABLE_SSL + +/* Define to 1 if you have the <fcntl.h> header file. */ +#undef HAVE_FCNTL_H + +/* Define to 1 if you have the `gethostbyname' function. */ +#undef HAVE_GETHOSTBYNAME + +/* Define to 1 if you have the <inttypes.h> header file. */ +#undef HAVE_INTTYPES_H + +/* Define to 1 if you have the `nsl' library (-lnsl). */ +#undef HAVE_LIBNSL + +/* Define to 1 if you have the <libpng/png.h> header file. */ +#undef HAVE_LIBPNG_PNG_H + +/* Define to 1 if you have the `socket' library (-lsocket). */ +#undef HAVE_LIBSOCKET + +/* Define to 1 if you have the <memory.h> header file. */ +#undef HAVE_MEMORY_H + +/* Define to 1 if you have the <png.h> header file. */ +#undef HAVE_PNG_H + +/* Define to 1 if you have the `setsockopt' function. */ +#undef HAVE_SETSOCKOPT + +/* 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 <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 <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 <sys/uio.h> header file. */ +#undef HAVE_SYS_UIO_H + +/* Define to 1 if you have the <unistd.h> header file. */ +#undef HAVE_UNISTD_H + +/* 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 + +/* The size of `char', as computed by sizeof. */ +#undef SIZEOF_CHAR + +/* The size of `int', as computed by sizeof. */ +#undef SIZEOF_INT + +/* The size of `long', as computed by sizeof. */ +#undef SIZEOF_LONG + +/* The size of `short', as computed by sizeof. */ +#undef SIZEOF_SHORT + +/* The size of `void *', as computed by sizeof. */ +#undef SIZEOF_VOID_P + +/* Define to 1 if you have the ANSI C header files. */ +#undef STDC_HEADERS + +/* Version number of package */ +#undef VERSION + +/* Define the real type of socklen_t */ +#undef socklen_t diff --git a/configure.in b/configure.in new file mode 100644 index 00000000..dd52bc1a --- /dev/null +++ b/configure.in @@ -0,0 +1,475 @@ +dnl Process this file with aclocal, autoconf and automake. + +AC_INIT(src/dillo.cc) + +dnl Detect the canonical host and target build environment +AC_CANONICAL_SYSTEM + +AM_INIT_AUTOMAKE(dillo, f15) +AM_CONFIG_HEADER(config.h) + +dnl Options + +AC_ARG_WITH(jpeg-lib, [ --with-jpeg-lib=DIR Specify where to find libjpeg], LIBJPEG_LIBDIR=$withval) +AC_ARG_WITH(jpeg-inc, [ --with-jpeg-inc=DIR Specify where to find libjpeg's headers], LIBJPEG_INCDIR=$withval) + +AC_ARG_ENABLE(efence, [ --enable-efence Try to compile and run with Electric Fence], + , enable_efence=no) +AC_ARG_ENABLE(gprof, [ --enable-gprof Try to compile and run with profiling enabled], + , enable_gprof=no) +AC_ARG_ENABLE(insure, [ --enable-insure Try to compile and run with Insure++], + , enable_insure=no) +AC_ARG_ENABLE(ansi, [ --enable-ansi Try to compile and run with ANSI flags], + , enable_ansi=no) +AC_ARG_ENABLE(ipv6, [ --enable-ipv6 Build with support for IPv6], , ) +AC_ARG_ENABLE(rtfl, [ --enable-rtfl Build with rtfl messages], enable_rtfl=yes) +AC_ARG_ENABLE(cookies,[ --disable-cookies Don't compile support for cookies], + , enable_cookies=yes) +AC_ARG_ENABLE(png, [ --disable-png Disable support for PNG images], + enable_png=$enableval, enable_png=yes) +AC_ARG_ENABLE(jpeg, [ --disable-jpeg Disable support for JPEG images], + enable_jpeg=$enableval, enable_jpeg=yes) +AC_ARG_ENABLE(gif, [ --disable-gif Disable support for GIF images], + enable_gif=$enableval, enable_gif=yes) +AC_ARG_ENABLE(ssl, [ --disable-ssl Disable ssl features (eg. https)], + enable_ssl=$enableval, enable_ssl=yes) +AC_ARG_ENABLE(threaded-dns,[ --disable-threaded-dns Disable the advantage of a reentrant resolver library], + enable_threaded_dns=$enableval, enable_threaded_dns=yes) + +AC_PROG_CC +AC_PROG_CXX +AM_PROG_CC_STDC +AC_PROG_RANLIB +AC_PROG_CPP + +dnl ---------------------------- +dnl Check our char and int types +dnl ---------------------------- +dnl +AC_CHECK_SIZEOF(char) +AC_CHECK_SIZEOF(short) +AC_CHECK_SIZEOF(long) +AC_CHECK_SIZEOF(int) +AC_CHECK_SIZEOF(void *) + +case 2 in +$ac_cv_sizeof_short) gint16=short;; +$ac_cv_sizeof_int) gint16=int;; +esac +case 4 in +$ac_cv_sizeof_short) gint32=short;; +$ac_cv_sizeof_int) gint32=int;; +$ac_cv_sizeof_long) gint32=long;; +esac + +cat >d_size.h <<_______EOF +#ifndef __D_SIZE_H__ +#define __D_SIZE_H__ + + +#include "config.h" + +#if HAVE_STDINT_H == 0 +#include <stdint.h> +#else +typedef signed $gint16 int16_t; +typedef unsigned $gint16 uint16_t; +typedef signed $gint32 int32_t; +typedef unsigned $gint32 uint32_t; +#endif +typedef unsigned char uchar_t; +typedef unsigned short ushort_t; +typedef unsigned long ulong_t; +typedef unsigned int uint_t; +typedef int bool_t; + + +#endif /* __D_SIZE_H__ */ +_______EOF + + +dnl -------------------------------------- +dnl Check whether to add /usr/local or not +dnl (this is somewhat a religious problem) +dnl -------------------------------------- +dnl +if test "`$CPP -v < /dev/null 2>&1 | grep '/usr/local/include' 2>&1`" = ""; then + CPPFLAGS="$CPPFLAGS -I/usr/local/include" + LDFLAGS="$LDFLAGS -L/usr/local/lib" +fi + +dnl ------------------------------------ +dnl Check for socket libs (AIX, Solaris) +dnl ------------------------------------ +dnl +AC_CHECK_FUNCS(gethostbyname,, + [AC_CHECK_LIB(nsl,gethostbyname,,[AC_CHECK_LIB(socket,gethostbyname)])]) +AC_CHECK_FUNCS(setsockopt,,[AC_CHECK_LIB(socket,setsockopt)]) + +dnl -------------------- +dnl Checks for socklen_t +dnl -------------------- +dnl +AC_MSG_CHECKING([for socklen_t]) +ac_cv_socklen_t="" +AC_TRY_COMPILE([ +#include <sys/types.h> +#include <sys/socket.h> +],[ +socklen_t a=0; +getsockname(0,(struct sockaddr*)0, &a); +], +ac_cv_socklen_t="socklen_t", +AC_TRY_COMPILE([ +#include <sys/types.h> +#include <sys/socket.h> +],[ +int a=0; +getsockname(0,(struct sockaddr*)0, &a); +], +ac_cv_socklen_t="int", +ac_cv_socklen_t="size_t" +) +) +AC_MSG_RESULT($ac_cv_socklen_t) +if test "$ac_cv_socklen_t" != "socklen_t"; then + AC_DEFINE_UNQUOTED(socklen_t, $ac_cv_socklen_t, + [Define the real type of socklen_t]) +fi + + +dnl ---------------------- +dnl Test for FLTK2 library +dnl ---------------------- +dnl +dnl For debugging and to be user friendly +AC_MSG_CHECKING([FLTK2]) +if sh -c "fltk2-config --version" >/dev/null 2>&1 +then AC_MSG_RESULT(yes) + LIBFLTK_CXXFLAGS=`fltk2-config --cxxflags` + LIBFLTK_LIBS=`fltk2-config --use-images --ldflags` +else if sh -c "fltk-config --version" >/dev/null 2>&1 + then AC_MSG_RESULT(yes) + LIBFLTK_CXXFLAGS=`fltk-config --cxxflags` + LIBFLTK_LIBS=`fltk-config --ldflags` + else AC_MSG_RESULT(no) + AC_ERROR(FLTK2 must be installed!) + fi +fi + + +dnl ---------------- +dnl Test for libjpeg +dnl ---------------- +dnl +if test "x$enable_jpeg" = "xyes"; then + AC_CHECK_HEADER(jpeglib.h, jpeg_ok=yes, jpeg_ok=no) + + if test "x$jpeg_ok" = "xyes"; then + old_libs="$LIBS" + AC_CHECK_LIB(jpeg, jpeg_destroy_decompress, jpeg_ok=yes, jpeg_ok=no) + LIBS="$old_libs" + fi + + if test "x$jpeg_ok" = "xyes"; then + LIBJPEG_LIBS="-ljpeg" + if test -n "$LIBJPEG_LIBDIR"; then + LIBJPEG_LDFLAGS="-L$LIBJPEG_LIBDIR" + fi + if test -n "$LIBJPEG_INCDIR"; then + LIBJPEG_CPPFLAGS="-I$LIBJPEG_INCDIR" + fi + else + AC_MSG_WARN([*** No libjpeg found. Disabling jpeg images.***]) + fi +fi + +if test "x$jpeg_ok" = "xyes"; then + AC_DEFINE([ENABLE_JPEG], [], [Enable JPEG images]) +fi + +dnl ------------------------------ +dnl Test for zlib (libpng uses it) +dnl ------------------------------ +dnl +if test "x$enable_png" = "xyes"; then + AC_CHECK_HEADER(zlib.h, libz_ok=yes, libz_ok=no) + + if test "x$libz_ok" = "xyes"; then + old_libs="$LIBS" + AC_CHECK_LIB(z, zlibVersion, libz_ok=yes, libz_ok=no) + LIBS="$old_libs" + fi + + if test "x$libz_ok" = xyes; then + LIBZ_LIBS="-lz" + else + AC_MSG_WARN([*** No libz found. Disabling PNG images ***]) + fi +fi + +dnl --------------- +dnl Test for libpng +dnl --------------- +dnl +if test "x$enable_png" = "xyes" && test "x$libz_ok" = "xyes"; then + AC_MSG_CHECKING([for libpng-config]) + +dnl Check if the user hasn't set the variable $PNG_CONFIG + if test -z "$PNG_CONFIG"; then + PNG_CONFIG=`which libpng12-config` + if test -z "$PNG_CONFIG"; then + PNG_CONFIG=`which libpng-config` + fi + if test -z "$PNG_CONFIG"; then + PNG_CONFIG=`which libpng10-config` + fi + fi + +dnl Check if the libpng-config script was found and is executable + if test -n "$PNG_CONFIG" && test -x "$PNG_CONFIG"; then + AC_MSG_RESULT([$PNG_CONFIG]) + png_ok="yes" + else + AC_MSG_RESULT([missing]) + png_ok="no" + fi + + if test "x$png_ok" = "xyes"; then +dnl For debugging and to be user friendly + AC_MSG_CHECKING([for libpng version]) + png_version=`$PNG_CONFIG --version` + case $png_version in + 1.2.*) AC_MSG_RESULT([$png_version (newer version)]) ;; + 1.0.*) AC_MSG_RESULT([$png_version (older version)]) ;; + *) AC_MSG_RESULT([ERROR]) ;; + esac + +dnl Try to use options that are supported by all libpng-config versions... + LIBPNG_CFLAGS=`$PNG_CONFIG --cflags` + LIBPNG_LIBS=`$PNG_CONFIG --ldflags` + case $png_version in + 1.2.4*) LIBPNG_LIBS="$LIBPNG_LIBS `$PNG_CONFIG --libs`" ;; + esac + else +dnl Try to find libpng even though libpng-config wasn't found + AC_CHECK_HEADERS(png.h libpng/png.h, png_ok=yes && break, png_ok=no) + + if test "x$png_ok" = "xyes"; then + old_libs="$LIBS" + AC_CHECK_LIB(png, png_check_sig, png_ok=yes, png_ok=no, $LIBZ_LIBS -lm) + LIBS="$old_libs" + + if test "x$png_ok" = "xyes"; then + LIBPNG_LIBS="-lpng -lm" + fi + fi + + if test "x$png_ok" = "xno"; then + AC_MSG_WARN([*** No libpng found. Disabling PNG images ***]) + fi + fi +fi + +if test "x$png_ok" = "xyes"; then + AC_DEFINE([ENABLE_PNG], [], [Enable PNG images]) +fi + +dnl Check if support for GIF images should be compiled in +if test "x$enable_gif" = "xyes"; then + AC_DEFINE([ENABLE_GIF], [], [Enable GIF images]) +fi + +dnl -------------------------- +dnl Test for support for SSL +dnl -------------------------- +dnl +if test "x$enable_ssl" = "xyes"; then + AC_CHECK_HEADER(openssl/ssl.h, ssl_ok=yes, ssl_ok=no) + + if test "x$ssl_ok" = "xyes"; then + old_libs="$LIBS" + AC_CHECK_LIB(ssl, SSL_library_init, ssl_ok=yes, ssl_ok=no, -lcrypto) + LIBS="$old_libs" + fi + + if test "x$ssl_ok" = "xyes"; then + LIBSSL_LIBS="-lcrypto -lssl" + else + AC_MSG_WARN([*** No libssl found. Disabling ssl support.***]) + fi +fi + +if test "x$ssl_ok" = "xyes"; then + AC_DEFINE([ENABLE_SSL], [], [Enable SSL support]) +fi + + +dnl ---------------------- +dnl Test for POSIX threads +dnl ---------------------- +dnl +if test -z "$LIBPTHREAD_LIBS"; then +case $target in + *-*-linux*|*-*-solaris*) + old_libs="$LIBS" + AC_CHECK_LIB(pthread, pthread_create, LIBPTHREAD_LIBS="-lpthread") + LIBS="$old_libs" + ;; + + *-*-osf1*) + AC_MSG_CHECKING(whether pthreads work) + LIBPTHREAD_LIBS="-lpthread -lexc -ldb" + AC_MSG_WARN([*** _Untested pthreads_ try setting LIBPTHREAD_LIBS manually if it doesn't work ***]) + ;; + + *) + AC_MSG_CHECKING(whether threads work with -pthread) + LDSAVEFLAGS=$LDFLAGS + LDFLAGS="$LDFLAGS -pthread" + AC_TRY_LINK_FUNC(pthread_create, pthread_ok=yes, pthread_ok=no) + LDFLAGS=$LDSAVEFLAGS + + if test "x$pthread_ok" = "xyes"; then + AC_MSG_RESULT(yes) + LIBPTHREAD_LDFLAGS="-pthread" + else + AC_MSG_RESULT(no. Now we will try some libraries.) + + AC_SEARCH_LIBS(pthread_create, pthread, + LIBPTHREADS_LIBS="-lpthread", + AC_SEARCH_LIBS(pthread_create, pthreads, + LIBPTHREADS_LIBS="-lpthreads", + AC_SEARCH_LIBS(pthread_create, c_r, + LIBPTHREADS_LIBS="-lc_r", thread_ok=no))) + + if test "x$thread_ok" = "xno"; then + AC_MSG_WARN([*** No pthreads found. ***]) + AC_MSG_ERROR([*** Try setting LIBPTHREAD_LIBS manually to point to your pthreads library. ***]) + exit 1 + else + AC_MSG_WARN([found a way to link threads, but it may not work...]) + fi + fi + ;; + +esac +fi + +dnl ------------------------------------ +dnl Workaround for nanosleep and solaris +dnl ------------------------------------ +dnl +case $target in + *-*-solaris*) + AC_MSG_CHECKING(whether SunOS has -lrt ) + LDSAVEFLAGS="$LDFLAGS" + LDFLAGS="$LDFLAGS -lrt" + AC_TRY_LINK_FUNC(nanosleep, rt_ok=yes, rt_ok=no) + if test "x$rt_ok" = "xyes"; then + AC_MSG_RESULT(yes) + else + AC_MSG_RESULT(no) + AC_MSG_CHECKING(whether SunOS has -lposix4 ) + LDFLAGS="$LDSAVEFLAGS -lposix4" + AC_TRY_LINK_FUNC(nanosleep, posix_ok=yes, posix_ok=no) + if test "x$posix_ok" = "xyes"; then + AC_MSG_RESULT(yes) + else + LDFLAGS=$LDSAVEFLAGS + AC_MSG_RESULT(no) + AC_MSG_WARN([*** Try setting LIBS or LDFLAGS manually to point to the library with nanosleep()***]) + fi + fi + ;; +esac + +dnl -------------------- +dnl Command line options +dnl -------------------- +dnl +if test "x$enable_cookies" = "xno" ; then + CFLAGS="$CFLAGS -DDISABLE_COOKIES" +fi +if test "x$enable_ipv6" = "xyes" ; then + CFLAGS="$CFLAGS -DENABLE_IPV6" +fi +if test "x$enable_efence" = "xyes" ; then + LIBS="-lefence $LIBS" +fi +if test "x$enable_gprof" = "xyes" ; then + CFLAGS="$CFLAGS -pg" +fi +if test "x$enable_insure" = "xyes" ; then + CC="insure -Zoi \"compiler $CC\"" + LIBS="$LIBS -lstdc++-2-libc6.1-1-2.9.0" +fi +if test "x$enable_rtfl" = "xyes" ; then + CFLAGS="$CFLAGS -DDBG_RTFL" +fi +if test "x$enable_threaded_dns" = "xyes" ; then + CFLAGS="$CFLAGS -DD_DNS_THREADED" +fi + +dnl ----------------------- +dnl Checks for header files +dnl ----------------------- +dnl +AC_HEADER_STDC +AC_CHECK_HEADERS(fcntl.h unistd.h sys/uio.h) + +dnl -------------------------- +dnl Check for compiler options +dnl -------------------------- +dnl +if eval "test x$GCC = xyes"; then + if test "`echo $CFLAGS | grep '\-D_REENTRANT' 2> /dev/null`" = ""; then + CFLAGS="$CFLAGS -D_REENTRANT" + fi + if test "`echo $CFLAGS | grep '\-D_THREAD_SAFE' 2> /dev/null`" = ""; then + CFLAGS="$CFLAGS -D_THREAD_SAFE" + fi + if test "`echo $CFLAGS | grep '\-Wall' 2> /dev/null`" = ""; then + CFLAGS="$CFLAGS -Wall" + fi + if test "`echo $CFLAGS | grep '\-W[^a]' 2> /dev/null`" = ""; then + if test "`$CC -v 2>&1 | grep 'version 3'`" != ""; then + CFLAGS="$CFLAGS -W -Wno-unused-parameter" + fi + fi + if test "`echo $CFLAGS | grep '\-Waggregate-return' 2> /dev/null`" = ""; then + CFLAGS="$CFLAGS -Waggregate-return" + fi + + if eval "test x$enable_ansi = xyes"; then + if test "`echo $CFLAGS | grep '\-ansi' 2> /dev/null`" = ""; then + CFLAGS="$CFLAGS -ansi" + fi + + if test "`echo $CFLAGS | grep '\-pedantic' 2> /dev/null`" = ""; then + CFLAGS="$CFLAGS -pedantic" + fi + fi +fi +dnl ----------- +dnl CXX options +dnl ----------- +dnl +CXXFLAGS="$CXXFLAGS -Wall -W -Wno-unused-parameter" + +AC_SUBST(LIBJPEG_LIBS) +AC_SUBST(LIBJPEG_LDFLAGS) +AC_SUBST(LIBJPEG_CPPFLAGS) +AC_SUBST(LIBPNG_LIBS) +AC_SUBST(LIBPNG_CFLAGS) +AC_SUBST(LIBZ_LIBS) +AC_SUBST(LIBSSL_LIBS) +AC_SUBST(LIBPTHREAD_LIBS) +AC_SUBST(LIBPTHREAD_LDFLAGS) +AC_SUBST(LIBFLTK_CXXFLAGS) +AC_SUBST(LIBFLTK_LIBS) +AC_SUBST(datadir) +AC_SUBST(src doc) + +AC_OUTPUT(Makefile dlib/Makefile dpip/Makefile dpid/Makefile dpi/Makefile doc/Makefile src/Makefile src/IO/Makefile) + diff --git a/dillorc2 b/dillorc2 new file mode 100644 index 00000000..23886dbd --- /dev/null +++ b/dillorc2 @@ -0,0 +1,194 @@ +# dillorc +# Sample dillo initialization file. +# Lines that start with a '#' are comments. + + +#------------------------------------------------------------------------- +# FIRST SECTION :) +#------------------------------------------------------------------------- + +# Set the desired initial browser size +#geometry=820x650 +#geometry=650x545 +geometry=725x545 + +# Dicache is where the Decompressed Images are cached (not the original ones). +# If you have a lot of memory and a slow CPU, use YES, otherwise use NO +use_dicache=NO + + +#------------------------------------------------------------------------- +# RENDERING SECTION +#------------------------------------------------------------------------- + +# Fontname for variable width rendering (most of the text). +# - some fonts may slow down rendering, some others not! +# - try to tune a fontname/font_factor combination. +# Ex. {helvetica, lucida, times, "new century schoolbook", utopia, ...} +#vw_fontname="new century schoolbook" +#vw_fontname="helvetica" +#vw_fontname="times" +#vw_fontname="Bitstream vera Serif" +#vw_fontname="arial" +vw_fontname="DejaVu Sans" + +# Fontname for fixed width rendering (mainly text quoted with <pre>) +#fw_fontname=courier +#fw_fontname="Bitstream Vera Sans Mono" +#fw_fontname="Andale Mono" +fw_fontname="DejaVu Sans Mono" + +# All fontsizes are scaled by this value (default is 1.0) +#font_factor=1.2 +font_factor=1.5 + +# If you prefer oblique over italic fonts, uncoment next line +#use_oblique=YES + +# Show tooltip popup for images? +# Note: We use the "title" attribute and not "alt". +# More info at: http://bugzilla.mozilla.org/show_bug.cgi?id=25537 +show_tooltip=YES + +# Set this to YES, if you want to limit the word wrap width to the vieport +# width (may be useful for iPAQ) +limit_text_width=NO + + +#------------------------------------------------------------------------- +# PARSING SECTION +#------------------------------------------------------------------------- + +# If you prefer more accurate HTML bug diagnose, over better rendering +# (page authors and webmasters) set the following to "NO". +# +w3c_plus_heuristics=YES + + +#------------------------------------------------------------------------- +# NETWORK SECTION +#------------------------------------------------------------------------- + +# Set the start page. +# Uncomment if you want to override the default start page. +#start_page="file:/home/jcid/Thinkpad385XD.html" + +# Set the home location +#home="http://dillo.cipsga.org.br/" +home="file:/home/jcid/HomePage/Home.html" + +# Set search url to use with "s <keywords>". +# %s is replaced with keywords separated by '+'. +#search_url="http://search.lycos.com/default.asp?query=%s" +#search_url="http://www.alltheweb.com/search?cat=web&query=%s" +search_url="http://www.google.com/search?q=%s" + +# Set the proxy information for http +#http_proxy=http://localhost:8080/ + +# if you need to provide a user/password pair for the proxy, +# set the proxy user name here and Dillo will ask for the password later. +#http_proxyuser="joe" + +# Set the domains to access without proxy +#no_proxy = ".hola.com .mynet.cl .hi.de" + + +#------------------------------------------------------------------------- +# COLORS SECTION +#------------------------------------------------------------------------- + +# Here we can use the HTML color names or C syntax. + +# Set the background color +# bg_color=gray +# bg_color=0xd6d6c0 +bg_color=0xdcd1ba + +# Set the text color +text_color=black + +# Set the link color +link_color=blue + +# If your eyes don't suffer with white backgrounds, or you need +# high contrast to see sharply, uncomment next line. +allow_white_bg=NO + +# Use the same colors with all documents? +#force_my_colors=YES + +# When set to YES, visited links always have a characteristic color, +# independent of the author's setting. +contrast_visited_color=YES + + +#------------------------------------------------------------------------- +# USER INTERFACE SECTION +#------------------------------------------------------------------------- + +# Size of dillo panel (used to enlarge the browsing area) +# tiny : recommended for iPAQ (with small_icons) +# small : very nice! (it's "medium" without icon titles) +# medium : nice! +# large : Traditional +#panel_size=tiny +panel_size=small +#panel_size=medium +#panel_size=large +small_icons=NO + +# Here you can choose to hide some widgets of the dillo panel... +#show_back=NO +#show_forw=NO +#show_home=NO +#show_reload=NO +#show_save=NO +#show_stop=NO +#show_bookmarks=NO +show_menubar=YES +#show_clear_url=NO +#show_url=NO +#show_search=NO +#show_progress_box=NO + +# Start dillo windows with a hidden panel? +fullwindow_start=NO + +# Enabling this will restrain OpenUrl and FindText, but may be required +# for the ION window manager. +transient_dialogs=NO + +# When filling forms, our default behaviour is to submit on enterpress, +# but only when there's a single text entry (to avoid incomplete submits). +# OTOH, if you have to fill the same form lots of times, you may find +# useful to keep away from the mouse by forcing enter to submit. +enterpress_forces_submit=NO + +# Some forms lack a submit button, and dillo can generate a custom one +# internally. Unfortunately there's no guarantee for it to work. :( +# (my experience is that forms that lack a submit rely on Javascript) +generate_submit=NO + +#------------------------------------------------------------------------- +# DEBUG MESSAGES SECTION +#------------------------------------------------------------------------- + +# Soon we should add the "show_debug_messages=NO" option... + +# Generic messsages (mainly for debugging specific parts) +# Uncomment the following line to disable them. +#show_msg=NO + + +#------------------------------------------------------------------------- +# HTML BUG MESSAGES SECTION +#------------------------------------------------------------------------- + +# Accepted by the W3C validator but "strongly discouraged" by the SPEC. +# (As "TAB character inside <PRE>"). +#show_extra_warnings=YES + + +# ----------------------------------------------------------------------- +# dillorc ends here. diff --git a/dlib/Makefile.am b/dlib/Makefile.am new file mode 100644 index 00000000..378cd785 --- /dev/null +++ b/dlib/Makefile.am @@ -0,0 +1,6 @@ +noinst_LIBRARIES = libDlib.a + +libDlib_a_SOURCES = \ + dlib.h \ + dlib.c + diff --git a/dlib/dlib.c b/dlib/dlib.c new file mode 100644 index 00000000..bba1cc5e --- /dev/null +++ b/dlib/dlib.c @@ -0,0 +1,750 @@ +/* + * File: dlib.c + * + * Copyright (C) 2006 Jorge Arellano Cid <jcid@dillo.org> + * + * 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 3 of the License, or + * (at your option) any later version. + */ + +/* Memory allocation, Simple dynamic strings, Lists (simple and sorted), + * and a few utility functions + */ + +/* + * TODO: vsnprintf() is in C99, maybe a simple replacement if necessary. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <ctype.h> + +#include "dlib.h" + +/* + *- Memory -------------------------------------------------------------------- + */ + +void *dMalloc (size_t size) +{ + void *value = malloc (size); + if (value == 0) + exit(1); + return value; +} + +void *dRealloc (void *mem, size_t size) +{ + void *value = realloc (mem, size); + if (value == 0) + exit(1); + return value; +} + +void *dMalloc0 (size_t size) +{ + void *value = dMalloc (size); + memset (value, 0, size); + return value; +} + +void dFree (void *mem) +{ + if (mem) + free(mem); +} + +/* + *- strings (char *) ---------------------------------------------------------- + */ + +char *dStrdup(const char *s) +{ + if (s) { + int len = strlen(s)+1; + char *ns = dNew(char, len); + memcpy(ns, s, len); + return ns; + } + return NULL; +} + +char *dStrndup(const char *s, size_t sz) +{ + if (s) { + char *ns = dNew(char, sz+1); + memcpy(ns, s, sz); + ns[sz] = 0; + return ns; + } + return NULL; +} + +/* + * Concatenate a NULL-terminated list of strings + */ +char *dStrconcat(const char *s1, ...) +{ + va_list args; + char *s, *ns = NULL; + + if (s1) { + Dstr *dstr = dStr_sized_new(64); + va_start(args, s1); + for (s = (char*)s1; s; s = va_arg(args, char*)) + dStr_append(dstr, s); + va_end(args); + ns = dstr->str; + dStr_free(dstr, 0); + } + return ns; +} + +/* + * Remove leading and trailing whitespace + */ +char *dStrstrip(char *s) +{ + char *p; + int len; + + if (s && *s) { + for (p = s; isspace(*p); ++p); + for (len = strlen(p); len && isspace(p[len-1]); --len); + if (p > s) + memmove(s, p, len); + s[len] = 0; + } + return s; +} + +/* + * Return a new string of length 'len' filled with 'c' characters + */ +char *dStrnfill(size_t len, char c) +{ + char *ret = dNew(char, len+1); + for (ret[len] = 0; len > 0; ret[--len] = c); + return ret; +} + +/* + * strsep() implementation + */ +char *dStrsep(char **orig, const char *delim) +{ + char *str, *p; + + if (!(str = *orig)) + return NULL; + + p = strpbrk(str, delim); + if (p) { + *p++ = 0; + *orig = p; + } else { + *orig = NULL; + } + return str; +} + +/* + * Case insensitive strstr + */ +char *dStristr(const char *haystack, const char *needle) +{ + int i, j; + + for (i = 0, j = 0; haystack[i] && needle[j]; ++i) + if (tolower(haystack[i]) == tolower(needle[j])) { + ++j; + } else if (j) { + i -= j; + j = 0; + } + + if (!needle[j]) /* Got all */ + return (char *)(haystack + i - j); + return NULL; +} + + +/* + *- dStr ---------------------------------------------------------------------- + */ + +/* + * Private allocator + */ +static void dStr_resize(Dstr *ds, int n_sz, int keep) +{ + if (n_sz >= 0) { + if (keep && n_sz > ds->len) { + ds->str = (Dstr_char_t*) dRealloc (ds->str, n_sz*sizeof(Dstr_char_t)); + ds->sz = n_sz; + } else { + if (ds->str) + free(ds->str); + ds->str = dNew(Dstr_char_t, n_sz); + ds->sz = n_sz; + ds->len = 0; + ds->str[0] = 0; + } + } +} + +/* + * Create a new string with a given size. + * Initialized to "" + */ +Dstr *dStr_sized_new (int sz) +{ + if (sz < 2) + sz = 2; + + Dstr *ds = dNew(Dstr, 1); + ds->str = NULL; + dStr_resize(ds, sz, 0); + return ds; +} + +/* + * Return memory if there's too much allocated + */ +void dStr_fit (Dstr *ds) +{ + dStr_resize(ds, ds->len + 1, 1); +} + +/* + * Insert a C string, at a given position, into a Dstr (providing length). + * Note: It also works with embedded nil characters. + */ +void dStr_insert_l (Dstr *ds, int pos_0, const char *s, int l) +{ + int n_sz; + + if (ds && s && l && pos_0 >= 0 && pos_0 <= ds->len) { + for (n_sz = ds->sz; ds->len + l >= n_sz; n_sz *= 2); + if (n_sz > ds->sz) { + dStr_resize(ds, n_sz, (ds->len > 0) ? 1 : 0); + } + if (pos_0 < ds->len) + memmove(ds->str+pos_0+l, ds->str+pos_0, ds->len-pos_0); + memcpy(ds->str+pos_0, s, l); + ds->len += l; + ds->str[ds->len] = 0; + } +} + +/* + * Insert a C string, at a given position, into a Dstr + */ +void dStr_insert (Dstr *ds, int pos_0, const char *s) +{ + if (s) + dStr_insert_l(ds, pos_0, s, strlen(s)); +} + +/* + * Append a C string to a Dstr (providing length). + * Note: It also works with embedded nil characters. + */ +void dStr_append_l (Dstr *ds, const char *s, int l) +{ + dStr_insert_l(ds, ds->len, s, l); +} + +/* + * Append a C string to a Dstr. + */ +void dStr_append (Dstr *ds, const char *s) +{ + dStr_append_l(ds, s, strlen(s)); +} + +/* + * Create a new string. + * Initialized to 's' or empty if 's == NULL' + */ +Dstr *dStr_new (const char *s) +{ + Dstr *ds = dStr_sized_new(0); + if (s) + dStr_append(ds, s); + return ds; +} + +/* + * Free a dillo string. + * if 'all' free everything, else free the structure only. + */ +void dStr_free (Dstr *ds, int all) +{ + if (ds) { + if (all && ds->str) + free(ds->str); + free(ds); + } +} + +/* + * Append one character. + */ +void dStr_append_c (Dstr *ds, int c) +{ + char cs[2]; + + if (ds) { + if (ds->sz > ds->len + 1) { + ds->str[ds->len++] = (Dstr_char_t)c; + ds->str[ds->len] = 0; + } else { + cs[0] = (Dstr_char_t)c; + cs[1] = 0; + dStr_append_l (ds, cs, 1); + } + } +} + +/* + * Truncate a Dstr to be 'len' bytes long. + */ +void dStr_truncate (Dstr *ds, int len) +{ + if (ds && len < ds->len) { + ds->str[len] = 0; + ds->len = len; + } +} + +/* + * Erase a substring. + */ +void dStr_erase (Dstr *ds, int pos_0, int len) +{ + if (ds && pos_0 >= 0 && len > 0 && pos_0 + len <= ds->len) { + memmove(ds->str + pos_0, ds->str + pos_0 + len, ds->len - pos_0 - len); + ds->len -= len; + ds->str[ds->len] = 0; + } +} + +/* + * vsprintf-like function that appends. + * Used by: dStr_vsprintf(), dStr_sprintf() and dStr_sprintfa(). + */ +void dStr_vsprintfa (Dstr *ds, const char *format, va_list argp) +{ + int n, n_sz; + + if (ds && format) { + while (1) { + n = vsnprintf(ds->str + ds->len, ds->sz - ds->len, format, argp); + if (n > -1 && n < ds->sz - ds->len) { + ds->len += n; /* Success! */ + break; + } else if (n > -1) { /* glibc >= 2.1 */ + n_sz = ds->len + n + 1; + } else { /* old glibc */ + n_sz = ds->sz * 2; + } + dStr_resize(ds, n_sz, (ds->len > 0) ? 1 : 0); + } + } +} + +/* + * vsprintf-like function. + */ +void dStr_vsprintf (Dstr *ds, const char *format, va_list argp) +{ + if (ds) { + dStr_truncate(ds, 0); + dStr_vsprintfa(ds, format, argp); + } +} + +/* + * Printf-like function + */ +void dStr_sprintf (Dstr *ds, const char *format, ...) +{ + va_list argp; + + if (ds && format) { + va_start(argp, format); + dStr_vsprintf(ds, format, argp); + va_end(argp); + } +} + +/* + * Printf-like function that appends. + */ +void dStr_sprintfa (Dstr *ds, const char *format, ...) +{ + va_list argp; + + if (ds && format) { + va_start(argp, format); + dStr_vsprintfa(ds, format, argp); + va_end(argp); + } +} + +/* + *- dList --------------------------------------------------------------------- + */ + +/* + * Create a new empty list + */ +Dlist *dList_new(int size) +{ + if (size <= 0) + return NULL; + + Dlist *l = dNew(Dlist, 1); + l->len = 0; + l->sz = size; + l->list = dNew(void*, l->sz); + return l; +} + +/* + * Free a list (not its elements) + */ +void dList_free (Dlist *lp) +{ + if (!lp) + return; + + dFree(lp->list); + dFree(lp); +} + +/* + * Insert an element at a given position [0 based] + */ +void dList_insert_pos (Dlist *lp, void *data, int pos0) +{ + int i; + + if (!lp || pos0 < 0 || pos0 > lp->len) + return; + + if (lp->sz == lp->len) { + lp->sz *= 2; + lp->list = (void**) dRealloc (lp->list, lp->sz*sizeof(void*)); + } + ++lp->len; + + for (i = lp->len - 1; i > pos0; --i) + lp->list[i] = lp->list[i - 1]; + lp->list[pos0] = data; +} + +/* + * Append a data item to the list + */ +void dList_append (Dlist *lp, void *data) +{ + dList_insert_pos(lp, data, lp->len); +} + +/* + * Prepend a data item to the list + */ +void dList_prepend (Dlist *lp, void *data) +{ + dList_insert_pos(lp, data, 0); +} + +/* + * For completing the ADT. + */ +int dList_length (Dlist *lp) +{ + if (!lp) + return 0; + return lp->len; +} + +/* + * Remove a data item without preserving order. + */ +void dList_remove_fast (Dlist *lp, const void *data) +{ + int i; + + if (!lp) + return; + + for (i = 0; i < lp->len; ++i) { + if (lp->list[i] == data) { + lp->list[i] = lp->list[--lp->len]; + break; + } + } +} + +/* + * Remove a data item preserving order. + */ +void dList_remove (Dlist *lp, const void *data) +{ + int i, j; + + if (!lp) + return; + + for (i = 0; i < lp->len; ++i) { + if (lp->list[i] == data) { + --lp->len; + for (j = i; j < lp->len; ++j) + lp->list[j] = lp->list[j + 1]; + break; + } + } +} + +/* + * Return the nth data item, + * NULL when not found or 'n0' is out of range + */ +void *dList_nth_data (Dlist *lp, int n0) +{ + if (!lp || n0 < 0 || n0 >= lp->len) + return NULL; + return lp->list[n0]; +} + +/* + * Return the found data item, or NULL if not present. + */ +void *dList_find (Dlist *lp, const void *data) +{ + int i = dList_find_idx(lp, data); + return (i >= 0) ? lp->list[i] : NULL; +} + +/* + * Search a data item. + * Return value: its index if found, -1 if not present. + * (this is useful for a list of integers, for finding number zero). + */ +int dList_find_idx (Dlist *lp, const void *data) +{ + int i, ret = -1; + + if (!lp) + return ret; + + for (i = 0; i < lp->len; ++i) { + if (lp->list[i] == data) { + ret = i; + break; + } + } + return ret; +} + +/* + * Search a data item using a custom function. + * func() is given the list item and the user data as parameters. + * Return: data item when found, NULL otherwise. + */ +void *dList_find_custom (Dlist *lp, const void *data, dCompareFunc func) +{ + int i; + void *ret = NULL; + + if (!lp) + return ret; + + for (i = 0; i < lp->len; ++i) { + if (func(lp->list[i], data) == 0) { + ret = lp->list[i]; + break; + } + } + return ret; +} + +/* + * QuickSort implementation. + * This allows for a simple compare function for all the ADT. + */ +static void QuickSort(void **left, void **right, dCompareFunc compare) +{ + void **p = left, **q = right, **t = left; + + while (1) { + while (p != t && compare(*p, *t) < 0) + ++p; + while (q != t && compare(*q, *t) > 0) + --q; + if (p > q) + break; + if (p < q) { + void *tmp = *p; + *p = *q; + *q = tmp; + if (t == p) + t = q; + else if (t == q) + t = p; + } + if (++p > --q) + break; + } + + if (left < q) + QuickSort(left, q, compare); + if (p < right) + QuickSort(p, right, compare); +} + +/* + * Sort the list using a custom function + */ +void dList_sort (Dlist *lp, dCompareFunc func) +{ + if (lp && lp->len > 1) { + QuickSort(lp->list, lp->list + lp->len - 1, func); + } +} + +/* + * Insert an element into a sorted list. + * The comparison function receives two list elements. + */ +void dList_insert_sorted (Dlist *lp, void *data, dCompareFunc func) +{ + int i, st, min, max; + + if (lp) { + min = st = i = 0; + max = lp->len - 1; + while (min <= max) { + i = (min + max) / 2; + st = func(lp->list[i], data); + if (st < 0) { + min = i + 1; + } else if (st > 0) { + max = i - 1; + } else { + break; + } + } + dList_insert_pos(lp, data, (st >= 0) ? i : i+1); + } +} + +/* + * Search a sorted list. + * Return the found data item, or NULL if not present. + * func() is given the list item and the user data as parameters. + */ +void *dList_find_sorted (Dlist *lp, const void *data, dCompareFunc func) +{ + int i, st, min, max; + void *ret = NULL; + + if (lp && lp->len) { + min = 0; + max = lp->len - 1; + while (min <= max) { + i = (min + max) / 2; + st = func(lp->list[i], data); + if (st < 0) { + min = i + 1; + } else if (st > 0) { + max = i - 1; + } else { + ret = lp->list[i]; + break; + } + } + } + + return ret; +} + +/* + *- Misc utility functions ---------------------------------------------------- + */ + +/* + * Return the current working directory in a new string + */ +char *dGetcwd () +{ + size_t size = 128; + + while (1) { + char *buffer = dNew(char, size); + if (getcwd (buffer, size) == buffer) + return buffer; + dFree (buffer); + if (errno != ERANGE) + return 0; + size *= 2; + } +} + +/* + * Return the home directory in a static string (don't free) + */ +char *dGethomedir () +{ + static char *homedir = NULL; + + if (!homedir) { + if (getenv("HOME")) { + homedir = dStrdup(getenv("HOME")); + + } else if (getenv("HOMEDRIVE") && getenv("HOMEPATH")) { + homedir = dStrconcat(getenv("HOMEDRIVE"), getenv("HOMEPATH"), NULL); + } + } + return homedir; +} + +/* + * Get a line from a FILE stream. + * It handles backslash as line-continues character. + * Return value: read line on success, NULL on EOF. + */ +char *dGetline (FILE *stream) +{ + int ch; + Dstr *dstr; + char *line; + + dReturn_val_if_fail (stream, 0); + + dstr = dStr_sized_new(64); + while((ch = fgetc(stream)) != EOF) { + if (ch == '\\') { + /* continue with the next line */ + while((ch = fgetc(stream)) != EOF && ch != '\n'); + continue; + } + dStr_append_c(dstr, ch); + if (ch == '\n') + break; + } + + line = (dstr->len) ? dstr->str : NULL; + dStr_free(dstr, (line) ? 0 : 1); + return line; +} + diff --git a/dlib/dlib.h b/dlib/dlib.h new file mode 100644 index 00000000..6caf7c97 --- /dev/null +++ b/dlib/dlib.h @@ -0,0 +1,158 @@ +#ifndef __DLIB_H__ +#define __DLIB_H__ + +#include <stdio.h> /* for FILE* */ +#include <stddef.h> /* for size_t */ +#include <stdarg.h> /* for va_list */ +#include <string.h> /* for strerror */ +#include <strings.h> /* for strcasecmp, strncasecmp (POSIX 2001) */ + + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* + *-- Common macros ----------------------------------------------------------- + */ +#ifndef FALSE +#define FALSE (0) +#endif + +#ifndef TRUE +#define TRUE (!FALSE) +#endif + +#undef MAX +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) + +#undef MIN +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) + +/* + *-- Casts ------------------------------------------------------------------- + */ +/* TODO: include a void* size test in configure.in */ +#define VOIDP2INT(p) ((int)(p)) +#define INT2VOIDP(i) ((void*)(i)) + +/* + *-- Memory ------------------------------------------------------------------- + */ +#define dNew(type, count) \ + ((type *) dMalloc ((unsigned) sizeof (type) * (count))) +#define dNew0(type, count) \ + ((type *) dMalloc0 ((unsigned) sizeof (type) * (count))) + +void *dMalloc (size_t size); +void *dRealloc (void *mem, size_t size); +void *dMalloc0 (size_t size); +void dFree (void *mem); + +/* + *- Debug macros -------------------------------------------------------------- + */ +#define D_STMT_START do +#define D_STMT_END while (0) +#define dReturn_if_fail(expr) \ + D_STMT_START{ \ + if (!(expr)) { return; }; \ + }D_STMT_END +#define dReturn_val_if_fail(expr,val) \ + D_STMT_START{ \ + if (!(expr)) { return val; }; \ + }D_STMT_END + +/* + *- C strings ----------------------------------------------------------------- + */ +char *dStrdup(const char *s); +char *dStrndup(const char *s, size_t sz); +char *dStrconcat(const char *s1, ...); +char *dStrstrip(char *s); +char *dStrnfill(size_t len, char c); +char *dStrsep(char **orig, const char *delim); +char *dStristr(const char *haystack, const char *needle); + +/* these are in POSIX 2001. Could be implemented if a port requires it */ +#define dStrcasecmp strcasecmp +#define dStrncasecmp strncasecmp +#define dStrerror strerror + +/* + *-- dStr --------------------------------------------------------------------- + */ +#define Dstr_char_t char + +typedef struct _dstr { + int sz; /* allocated size (private) */ + int len; + Dstr_char_t *str; +} Dstr; + +Dstr *dStr_new (const char *s); +Dstr *dStr_sized_new (int sz); +void dStr_fit (Dstr *ds); +void dStr_free (Dstr *ds, int all); +void dStr_append_c (Dstr *ds, int c); +void dStr_append (Dstr *ds, const char *s); +void dStr_append_l (Dstr *ds, const char *s, int l); +void dStr_insert (Dstr *ds, int pos_0, const char *s); +void dStr_insert_l (Dstr *ds, int pos_0, const char *s, int l); +void dStr_truncate (Dstr *ds, int len); +void dStr_erase (Dstr *ds, int pos_0, int len); +void dStr_vsprintfa (Dstr *ds, const char *format, va_list argp); +void dStr_vsprintf (Dstr *ds, const char *format, va_list argp); +void dStr_sprintf (Dstr *ds, const char *format, ...); +void dStr_sprintfa (Dstr *ds, const char *format, ...); + +/* + *-- dList -------------------------------------------------------------------- + */ +struct Dlist_ { + int sz; /* allocated size (private) */ + int len; + void **list; +}; + +typedef struct Dlist_ Dlist; + +/* dCompareFunc: + * Return: 0 if parameters are equal (for dList_find_custom). + * Return: 0 if equal, < 0 if (a < b), > 0 if (b < a) --for insert sorted. + * + * For finding a data node with an external key, the comparison function + * parameters are: first the data node, and then the key. + */ +typedef int (*dCompareFunc) (const void *a, const void *b); + + +Dlist *dList_new(int size); +void dList_free (Dlist *lp); +void dList_append (Dlist *lp, void *data); +void dList_prepend (Dlist *lp, void *data); +int dList_length (Dlist *lp); +void dList_remove (Dlist *lp, const void *data); +void dList_remove_fast (Dlist *lp, const void *data); +void *dList_nth_data (Dlist *lp, int n0); +void *dList_find (Dlist *lp, const void *data); +int dList_find_idx (Dlist *lp, const void *data); +void *dList_find_custom (Dlist *lp, const void *data, dCompareFunc func); +void dList_sort (Dlist *lp, dCompareFunc func); +void dList_insert_sorted (Dlist *lp, void *data, dCompareFunc func); +void *dList_find_sorted (Dlist *lp, const void *data, dCompareFunc func); + +/* + *- Misc utility functions ---------------------------------------------------- + */ +char *dGetcwd (); +char *dGethomedir (); +char *dGetline (FILE *stream); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __DLIB_H__ */ + diff --git a/doc/Cache.txt b/doc/Cache.txt new file mode 100644 index 00000000..ac1ecf87 --- /dev/null +++ b/doc/Cache.txt @@ -0,0 +1,185 @@ + June 2000, --Jcid + Last update: Oct 2004 + + ------- + CACHE + ------- + + The cache module is the main abstraction layer between +rendering and networking. + + The capi module acts as a discriminating wrapper which either +calls the cache or the dpi routines depending on the type of +request. + + Every URL must be requested using a_Capi_open_url, no matter +if it is a http, file, dpi or whatever type of request. The capi +asks the dpi module for dpi URLs and the Cache for everything +else. + + Here we'll document non dpi requests. + + The cache, at its turn, sends the requested-data from memory +(if cached), or opens a new network connection (if not cached). + + This means that no mattering whether the answer comes from +memory or the net, the client requests it through the capi +wrapper, in a single uniform way. + + + ---------------- + CACHE PHILOSOPHY + ---------------- + + Dillo's cache is very simple, every single resource that's +retrieved (URL) is kept in memory. NOTHING is saved. This is +mainly for three reasons: + + - Dillo encourages personal privacy and it assures there'll be +no recorded tracks of the sites you visited. + + - The Network is full of intermediate transparent proxys that +serve as caches. + + - If you still want to have cached stuff, you can install an +external cache server (as WWWOFFLE), and benefit from it. + + + --------------- + CACHE STRUCTURE + --------------- + + Currently, dillo's cache code is spread in different sources: +mainly in cache.[ch], dicache.[ch] and it uses some other +functions from mime.c, Url.c and web.c. + + Cache.c is the principal source, and it also is the main +responsible for processing cache-clients (held in a queue). +Dicache.c is the "decompressed image cache" and it holds the +original data and its corresponding decompressed RGB +representation (more on this subject in Images.txt). + + Url.c, mime.c and web.c are used for secondary tasks; as +assigning the right "viewer" or "decoder" for a given URL. + + +---------------- +A bit of history +---------------- + + Some time ago, the cache functions, URL retrieving and +external protocols were a whole mess of mixed code, and it was +getting REALLY hard to fix, improve or extend the functionality. +The main idea of this "layering" is to make code-portions as +independent as possible so they can be understood, fixed, +improved or replaced without affecting the rest of the browser. + + An interesting part of the process is that, as resources are +retrieved, the client (dillo in this case) doesn't know the +Content-Type of the resource at request-time. It only gets known +when the resource header is retrieved (think of http), and it +happens when the cache has the control so, the cache sets the +proper viewer for it! (unless the Callback function is specified +with the URL request). + + You'll find a good example in http.c. + + Note: Files don't have a header, but the file handler inside +dillo tries to determine the Content-Type and sends it back in +HTTP form! + + +------------- +Cache clients +------------- + + Cache clients MUST use a_Cache_open_url to request an URL. The +client structure and the callback-function prototype are defined, +in cache.h, as follows: + +struct _CacheClient { + gint Key; /* Primary Key for this client */ + const char *Url; /* Pointer to a cache entry Url */ + guchar *Buf; /* Pointer to cache-data */ + guint BufSize; /* Valid size of cache-data */ + CA_Callback_t Callback; /* Client function */ + void *CbData; /* Client function data */ + void *Web; /* Pointer to the Web structure of our client */ +}; + +typedef void (*CA_Callback_t)(int Op, CacheClient_t *Client); + + + Notes: + + * Op is the operation that the callback is asked to perform + by the cache. { CA_Send | CA_Close | CA_Abort }. + + * Client: The Client structure that originated the request. + + + +-------------------------- +Key-functions descriptions +-------------------------- + +································································ +int a_Cache_open_url(const char *Url, CA_Callback_t Call, void *CbData) + + if Url is not cached + Create a cache-entry for that URL + Send client to cache queue + Initiate a new connection + else + Feed our client with cached data + +································································ +ChainFunction_t a_Url_get_ccc_funct(const char *Url) + + Scan the Url handlers for a handler that matches + If found + Return the CCC function for it + else + Return NULL + + * Ex: If Url is an http request, a_Http_ccc is the matching +handler. + +································································ + +---------------------- +Redirections mechanism + (HTTP 30x answers) +---------------------- + + This is by no means complete. It's a work in progress. + + Whenever an URL is served under an HTTP 30x header, its cache +entry is flagged with 'CA_Redirect'. If it's a 301 answer, the +additional 'CA_ForceRedirect' flag is also set, if it's a 302 +answer, 'CA_TempRedirect' is also set (this happens inside the +Cache_parse_header() function). + + Later on, in Cache_process_queue(), when the entry is flagged +with 'CA_Redirect' Cache_redirect() is called. + + + + + + + +----------- +Notes +----------- + + The whole process is asynchronous and very complex. I'll try +to document it in more detail later (source is commented). + Currently I have a drawing to understand it; hope the ASCII +translation serves the same as the original. + If you're planning to understand the cache process troughly, +write me a note, just to assign a higher priority on further +improving of this doc. + Hope this helps! + + diff --git a/doc/Cookies.txt b/doc/Cookies.txt new file mode 100644 index 00000000..8b5111b8 --- /dev/null +++ b/doc/Cookies.txt @@ -0,0 +1,85 @@ +Jan 2002, Jörgen Viksell - jorgen.viksell@telia.com, + Jorge Arellano Cid -- +Last update: Dec 2004 + + +================== + Cookies in Dillo +================== + + The cookie support in Dillo aims to support cookies of the old +original Netscape style, as well as the kind specified in RFC 2109. + Between sessions, the cookies get saved to ~/.dillo/cookies. +At the moment the only enforcements on the amount of cookies to +save to disk is max 20 per domain. + There's also a file for controlling cookies: ~/.dillo/cookiesrc. Dillo +initially sets it to ignore (reject) all cookies, so if you want to use +cookies, change it to meet your needs. + + If you don't want cookies at all, you have two options: + +1.- Delete ~/.dillo/cookiesrc (or leave it just as dillo creates it). +2. Configure Dillo with ./configure --disable-cookies. Then all the + cookie stuff will be skipped at compilation. + + +===================== + Controlling cookies +===================== + + There is a small and simple way to restrict urls from setting cookies +in Dillo. In the file ~/.dillo/cookiesrc You may specify rules +for different domains. The syntax looks something like this: + +DEFAULT DENY +slashdot.org ACCEPT +.host.com ACCEPT_SESSION + + The first line says that we should deny all cookies from all domains +by default. + The second one tells Dillo to save all cookies from slashdot.org +across sessions, until it expires. + And finally, the third says that all subdomains of host.com should be +allowed to set cookies. But these cookies will only be saved in +memory until you exit. + + +=================== + Cookies & Privacy +=================== + + Cookies can be a severe threat to personal privacy. The pages you +visit can be tracked, logged, and associated to a peronal data-record, +allowing the possibility of building a detailed profile of your +browsing habits. + + This data is sold to companies that profit from direct use of such +information (SPAM, Spying, etc). + + If this data is cross-referenced with other databases, they can end up +with more information than you have about yourself. + + Some people may tell you this is "paranoid". But please, take my words +as those of someone that has written a web browser, a cookies implementation, +and that has deep understanding of HTTP (RFC-2068) and cookies (RFC-2965). + + Non technical persons may like to read: + http://www.junkbusters.com/cookies.html + http://www.newsfactor.com/perl/story/16455.html (about user-spying) + + The dillo project is especially concerned about privacy and security +issues. Our advice is to avoid cookies whenever possible and at most set +ACCEPT_SESSION to specific, trusted sites. -- You have been warned. + + +============== + Restrictions +============== + + If you use a single dillo with multiple windows, then there's no +problem, but if you launch different dillos the latter ones will +have cookies disabled. + + + +Thats all folks! diff --git a/doc/Dillo.txt b/doc/Dillo.txt new file mode 100644 index 00000000..47f89780 --- /dev/null +++ b/doc/Dillo.txt @@ -0,0 +1,103 @@ +"Eliminate the guesswork and quality goes up." + + + ------- + DILLO + ------- + + These notes are written with a view to make it less hard, not +easier yet ;), to get into Dillo development. + When I first got into it, I was totally unaware of the browser +internals. Now that I've made my way deep into the core of it, +(we rewrote it 90% and modified the rest), is time to write some +documentation, just to make a less steep learning curve for new +developers. + + --Jcid + + + + -------- + OVERVIEW + -------- + + Dillo can be viewed as the sum of five main parts: + + 1.- Dillo Widget: A custom widget, FLTK2 based, that holds the +neccesary data structures and mechanisms for graphical rendering. +(Described in Dw*.txt, dw*.c files among the sources.) + + 2.- Dillo Cache: Integrated with a signal driven Input/Output +engine that handles file descriptor activity, the cache acts as +the main abstraction layer between rendering and networking. + Every URL, whether cached or not, must be retrieved using +a_Cache_open_url (Described briefly in Cache.txt, source +contained in cache.c). + IO is described in IO.txt (recommended), source in IO/. + + 3.- The HTML parser: A streamed parser that joins the Dillo +Widget and the Cache functionality to make browsing possible +(Described in HtmlParser.txt, source mainly inside html.c). + + 4.- Image processing code: The part that handles image +retrieving, decoding, caching and displaying. (Described in +Images.txt. Sources: image.c, dw_image.c, dicache.c, gif.c, +jpeg.c and png.c) + + 5.- The dpi framework: a gateway to interface the browser with +external programs (Example: the bookmarks server plugin). +Dpi spec: http://www.dillo.org/dpi1.html + + + ------------------------- + HOW IS THE PAGE RENDERED? + ------------------------- + +(A short description of the internal function calling process) + + When the user requests a new URL, a_Interface_entry_open_url +is queried to do the job; it calls a_Nav_push (The highest level +URL dispatcher); a_Nav_push updates current browsing history and +calls Nav_open_url. Nav_open_url closes all open connections by +calling a_Interface_stop and a_Interface_stop, and then calls +a_Capi_open_url wich calls a_Cache_open_url (or the dpi module if +this gateway is used). + + If Cache_search hits (due to a cached url :), the client is +fed with cached data, but if the URL isn't cached yet, a new CCC +(Concomitant Control Chain) is created and commited to fetch the +URL. Note that a_Cache_open_url will return the requested URL, +whether cached or not. + + The next CCC link is dynamically assigned by examining the +URL's protocol. It can be: + + a_Http_ccc + a_File_ccc + a_About_ccc + a_Plugin_ccc (not implemented yet) + + + If we have a HTTP URL, a_Http_ccc will succeed, and the http +module will be linked; it will create the proper HTTP query and +link the IO module to submit and deliver the answer. + + Note that as the Content-type of the URL is not always known +in advance, the answering branch decides where to dispatch it to +upon HTTP header arrival. + + + What happens then? + + Well, the html parser gets fed, and proper functions are +called for each tag (to parse and call the appropriate methods) +and the whole page is contructed in a streamed way. + Somewhere in the middle of it, resize and repaint functions +are activated and idle functions draw to screen what has been +processed. + + (The process for images is described in Images.txt) + + + + diff --git a/doc/Dpid.txt b/doc/Dpid.txt new file mode 100644 index 00000000..8f69843e --- /dev/null +++ b/doc/Dpid.txt @@ -0,0 +1,454 @@ +Aug 2003, Jorge Arellano Cid, + Ferdi Franceschini -- +Last update: Dec 2004 + + + ------ + dpid + ------ + +------------- +Nomenclature: +------------- + + dpi: + generic term referring to dillo's plugin system (version1). + + dpi1: + specific term for dillo's plugin spec version 1. + at: http://www.dillo.org/dpi1.html + + dpi program: + any plugin program itself. + + dpi framework: + the code base inside and outside dillo that makes dpi1 + working possible (it doesn't include dpi programs). + + dpip: + dillo plugin protocol. The HTML/XML like set of command tags + and information that goes inside the communication sockets. + Note: not yet fully defined, but functional. + Note2: it was designed to be extensible. + + dpid: + dillo plugin daemon. + + server plugin: + A plugin that is capable of accepting connections on a socket. Dpid will + never run more than one instance of a server plugin at a time. + + filter plugin: + Any program/script that can read or write to stdio. If you can write a + shell script you can write one of these (see examples at the end). + Warning, dpid will run multiple instances of filter plugins if requested. + This is safe if the plugin only writes to stdout which is what the filter + type dpis do at the moment. + +----------- +About dpid: +----------- + + * dpid is a program which manages dpi connections. + * dpid is a daemon that serves dillo using unix domain + sockets (UDS). + * dpid launches dpi programs and arranges socket communication + between the dpi program and dillo. + + The concept and motivation is similar to that of inetd. The +plugin manager (dpid) listens for a service request on a Unix +domain socket and returns the socket name of a plugin that +handles the service. It also watches sockets of inactive plugins +and starts them when a connection is requested. + + +----------------------------------------------------------- +What's the problem with managing dpi programs inside dillo? +----------------------------------------------------------- + + That's the other way to handle it, but it started to show some +problems (briefly outlined here): + + * When having two or more running instances of Dillo, one + should prevail, and take control of dpi managing (but all + dillos carry the managing code). + * If the managing dillo exits, it must pass control to another + instance, or leave it void if there's no other dillo running! + * The need to synchronise all the running instances of + dillo arises. + * If the controlling instance finishes and quits, all the + dpi-program PIDs are lost. + * Terminating hanged dpis is hard if it's not done with signals + (PIDs) + * Forks can be expensive (Dillo had to fork its dpis). + * When a managing dillo exits, the new one is no longer the + parent of the forked dpis. + * If the Unix domain sockets for the dpis were to be named + randomly, it gets very hard to recover their names if the + controlling instance of dillo exits and another must "take + over" the managing. + * It increments dillo's core size. + * ... + + That's why the managing daemon scheme was chosen. + + +---------------------- +What does dpid handle? +---------------------- + + It solves all the above mentioned shortcomings and also can do: + + * Multiple dillos: + dpid can communicate and serve more than one instance + of dillo. + + * Multiple dillo windows: + two or more windows of the same dillo instance accessing dpis + at the same time. + + * Different implementations of the same service + dpi programs ("dpis") are just an implementation of a + service. There's no problem in having more than one for the + same service. + + * Upgrading a service: + to a new version or implementation without requiring + bringing down the dpid or patching dillo's core. + + + And finally, being aware that this design can support the +following functions is very helpful: + + SCHEME Example + ------------------------------------------------------------ + * "one demand/one response" man, preferences, ... + * "resident while working" downloads, mp3, ... + * "resident until TERM signal" bookmarks, ... + + * "one client only" cd burner, ... + * "one client per instance" man, ... + * "multiple clients/one instance" downloads, cookies ... + + +-------- +Features +-------- + * Dpi programs go in: "EPREFIX/dillo/dpi" or "~/.dillo/dpi". The binaries + are named <name>.dpi as "bookmarks.dpi" and <name>.filter.dpi as in + "hello.filter.dpi". The ".filter" plugins simply read and write to stdio + and can be implemented with a shell script easily. + * Register/update/remove dpis from list of available dpis when a + <dpi cmd='register_all'> is received. + * dpid terminates when it receives a <dpi cmd='DpiBye'> command. + * dpis can be terminated with a <dpi cmd='DpiBye'> command. + * dpidc control program for dpid, currently allows register and stop. + + +----- +todo: +----- + + These features are already designed, waiting for implementation: + + * How to register/update/remove/ individual dpis? + * How to kill dpis? (signals) + + How: + + A useful and flexible way is to have a "control program" for +dpid (it avoids having to find its PID among others). + + Let's say: + + dpidc [register | upgrade | stop | ...] + + It can talk to a dpid UDS that serves for that (the same that +dillo would use). That way we may also have a dpidc dpi! :-) + + Seriously, what I like from this approach is that it is very +flexible and can be implemented incrementally ("dpidc register" +is enough to start). + + It also avoids the burden of having to periodically check the +dpis directory structure for changes). + + It also lets shell scripts an easy way to do the "dirty" work +of installing dpis; as is required with distros' package +systems. + +<note> + How do we tell a crashed dpi? That's the question. + We're thinking about using the "lease" concept (as in JINI). +</note> + + +----------------- +How does it work? +----------------- + +o on startup dpid reads dpidrc for the path to the dpi directory + (usually EPREFIX/lib/dillo/dpi). ~/.dillo/dpi is scanned first. + +o both directories are scanned for the list of available plugins. + ~/.dillo/dpi overrides system-wide dpis. + +o ~/.dillo/dpi_socket_dir is then checked for the name of the dpi socket + directory, if dpi_socket_dir does not exist it will be created. + +o next it creates Unix domain sockets for the available plugins and + then listens for service requests on its own socket (dpid.srs) + and for connections to the sockets of inactive plugins. + +o dpid returns the name of a plugin's socket when a client (dillo) + requests a service. + +o if the requested plugin is a 'server' then + 1) dpid stops watching the socket for activity + 2) forks and starts the plugin + 3) resumes watching the socket when the plugin exits + +o if the requested plugin is a 'filter' then + 1) dpid accepts the connection + 2) duplicates the connection on stdio + 3) forks and starts the plugin + 4) continues to watch the socket for new connections + + + + +--------------------------- +dpi service process diagram +--------------------------- + + These drawings should be worth a thousand words! :) + + +(I) + .--- s1 s2 s3 ... sn + | + [dpid] [dillo] + | + '--- srs + + The dpid is running listening on several sockets. + + +(II) + .--- s1 s2 s3 ... sn + | + [dpid] [dillo] + | | + '--- srs ------------------' + + dillo needs a service so it connects to the service request + socket of the dpid (srs) and asks for the socket name of the + required plugin (using dpip). + + +(III) + .--- s1 s2 s3 ... sn + | | + [dpid] | [dillo] + | | | + '--- srs '---------------' + + then it connects to that socket (s3, still serviced by dpid!) + + +(IV) + .--- s1 s2 s3 ... sn + | | + .[dpid] | [dillo] + . | | | + . '--- srs '---------------' + . + .............[dpi program] + + when s3 has activity (incoming data), dpid forks the dpi + program for it... + + +(V) + .--- s1 s2 (s3) ... sn + | + [dpid] [dillo] + | | + '--- srs .---------------' + | + [dpi program] + + ... and lets it "to take over" the socket. + + Once there's a socket channel for dpi and dillo, the whole +communication process takes place until the task is done. When +the dpi program exits, dpid resumes listening on the socket (s3). + + +----------------------------------------------- +How are the unix-domain-sockets for dpis named? +----------------------------------------------- + + Let's say we have two users, "fred" and "joe". + + When Fred's dillo starts its dpid, the dpid creates the +following directory (rwx------): + + /tmp/fred-XXXXXX + + using mkdtemp(). + + and saves that filename within "~/.dillo/dpi_socket_dir". + + That way, another dillo instance of user Fred can easily find +the running dpid's service request socket at: + + /tmp/fred-XXXXXX/dpid.srs + + (because it is saved in "~/.dillo/dpi_socket_dir"). + + Now, we have a dpi directory per user, and its permissions are +locked so only the user has access, thus the following directory +tree structure should pose no problems: + + /tmp/fred-XXXXXX/bookmarks + /downloads + /cookies + /ftp + ... + dpid.srs + + If user Joe starts his dillo, the same happens for him: + + /tmp/joe-XXXXXX/bookmarks + /downloads + /cookies + /ftp + ... + dpid.srs + + + What should dpid do at start time: + + Check if both, ~/.dillo/dpi_socket_dir and its directory, exist + (it can also check the ownership and permissions). + + If (both exist) + use them! + else + delete ~/.dillo/dpi_socket_dir + create another /tmp/<user>-XXXXXX directory + save the new directory name into ~/.dillo/dpi_socket_dir + (we could also add some paranoid level checks) + + To some degree, this scheme solves the tmpnam issue, different +users of dillo at the same time, multiple dillo instances, +polluting /tmp (cosmetic), and reasonably accounts for an +eventual dillo or dpid crash. + + It has worked very well so far! + + +-------------------------------- +So, how do I make my own plugin? +-------------------------------- + + First, at least, read the "Developing a dillo plugin" section +of dpi1 spec! :-) + + Note that the dpi1 spec may not be absolutely accurate, but the +main ideas remain. + + Once you've got the concepts, contrast them with the drawings +in this document. Once it all makes sense, start playing with +hello.dpi, you can run it by starting dillo with + dillo dpi:/hello/ +or entering + dpi:/hello/ +as the url. Then try to understand how it works (use the drawings) +and finally look at its code. + + Really, the order is not that important, what really matters is +to do it all. + + Start modifying hello.dpi, and then some more. When you feel +like trying new things, review the code of the other plugins for +ideas. + + The hardest part is to try to modify the dpi framework code +inside dillo; you have been warned! It already supports a lot of +functionality, but if you need to do some very custom stuff, try +extending the "chat" command. + + +--------------------------------- +Examples: Simple 'filter' plugins +--------------------------------- + + For a quick and dirty introduction to dpis try the following shell scripts. + + #!/bin/sh + + read -d'>' dpip_tag # Read dillo's request + + # Don't forget the empty line after the Content-type + cat <<EOF + <dpi cmd='start_send_page' url='dpi:/hi/hi.filter.dpi'> + Content-type: text + + EOF + + echo Hi + + Of course you should use html in a real application (perl makes this easy). + + A more useful example uses the "si" system info viewer: + + #!/bin/sh + # si - System Information Viewer + + read -d'>' dpip_tag + + # We don't need to send the Content-type because "si --html" does this + # for us. + cat <<EOF + <dpi cmd='start_send_page' url='dpi:/si/si.dpi.filter'> + EOF + + si --html + + just make sure that you have si installed or you wont get far. + +To try out the examples create two directories for the scripts under your home directory as follows: + mkdir -p ~/.dillo/dpi/hi + mkdir -p ~/.dillo/dpi/si + +then create the scripts and put them in the dpi service directories so that you end up with + ~/.dillo/dpi/hi/hi.filter.dpi + ~/.dillo/dpi/si/si.filter.dpi + +Don't forget to make them executable. + +If dpid is already running register the new plugins with + dpidc register + +You can now test them by entering + dpi:/hi/ +or + dpi:/si/ +as the url. Or simply passing the url to dillo on startup + + dillo dpi:/si/ + + + You can edit the files in place while dpid is running and reload them in +dillo to see the result, however if you change the file name or add a new +script you must run 'dpidc register'. + +WARNING +Multiple instances of a filter plugin may be run concurrently, this could be a +problem if your plugin records data in a file, however it is safe if you simply +write to stdout. Alternatively you could write a 'server' plugin instead as +they are guaranteed not to run concurrently. + >>>>>>>>>>>>>>>>>>>>> <<<<<<<<<<<<<<<<<<<<< + diff --git a/doc/Dw.txt b/doc/Dw.txt new file mode 100644 index 00000000..6ffd55d0 --- /dev/null +++ b/doc/Dw.txt @@ -0,0 +1,383 @@ +Jan 2001, S.Geerken@ping.de +Last update: Dec 2004 + +================ +Dw: Dillo Widget +================ + +NOTE (Aug 2004): The rendering has changed quite much, and many other +documents are out of date, in ways described in DwRendering.txt. They +will be updated some day. + +Dw is mainly the module for rendering HTML. It provides a framework +for widgets, based on the Gtk+ object framework, and is very similar +to Gtk+, so that experiences in using and extending Gtk+ help very +much in understanding Dw. There is some documentation at www.gtk.org +(and probably on your local harddisk, somewhere in /usr/doc/*gtk*), +you should especially have read the chapter "Writing Your Own +Widgets" in the tutorial. + + +Why Not Gtk+? +============= + +There are two reasons for designing a new model instead of simply +using Gtk+ objects: + + 1. Most important, Gtk+ widgets are limited in size, because X + windows are so. + 2. There are a few extensions which are due to the different needs + for HTML rendering compared to GUI's. (Of course, this could + have been solved by defining a new object derived from + GtkWidget.) + + +Notes On Naming +=============== + +According to the naming standards, functions beginning with "a_Dw_" +may be used outside of Dw, while, as an extention, functions used +within Dw (e.g. p_Dw_widget_queue_resize) are prefixed with "p_Dw_". +[todo: This could be included in NC_design.txt.] + +Non-static functions beginning with "Dw_" are only used between +GtkDwViewport and DwWidget (e.g. Dw_gtk_viewport_remove_dw), they +belong to the core of Dw. And, of course, functions only used within a +sub-module (e.g. a specific widget) start with "Dw_" and are static +(e.g. Dw_page_find_line_index). + +Dw widgets and some other structures have the prefix "Dw", while Gtk+ +widgets in Dw have the prefix "GtkDw", but functions of them begin +with "Dw_gtk_" or "a_Dw_gtk", respectively. + + +Basic Overview +============== + +Dw widgets are objects derived from DwWidget, which itself derives +from GtkObject. DwWidget is quite similar to GtkWidget, the main +difference is that Dw widgets are always windowless and that they are +presented in a viewport, so there is no need to limit the size. Much +of the functionality normally provided by the X server is simulated +by Dw. + +The interface between Gtk+ and Dw is the Gtk+ widget GtkDwViewport, +which contains (at most) one top-level Dw widget. + +A Few Definitions: + + - world coordinates: coordinates relative to the upper left corner + of the whole scrolled area ("the world") + - viewport coordinates: coordinates relative to the upper left + corner of the visible area + - widget coordinates: coordinates relative to the upper left corner + of the widget + +Dw widgets draw into the viewport window, and must adhere to +*viewport coordinates*: the "world" is only an abstract term, there +is no window for it. When GtkDwViewport processes expose events, they +are automatically delivered to the Dw widgets. Redrawing requests due +to scrolling of the viewport is done by the base object GtkLayout, +you will not find any code for this in Dw. + +Mouse events also contain viewport coordinates. Dw will try to find +the right Dw widget to deliver the event to. + +Resizing the GtkDwViewport will not resize the top-level Dw widget, +but the latter will get some hints, so that, e.g., the page widget +rewraps the lines at the appropriate width. + +See DwWidget.txt for more details. + + +Embedding Gtk+ Widgets In Dw +---------------------------- +Dw Widgets may embed Gtk+ widgets, this is done by the Dw widget +DwEmbedGtk. For Gtk+, these embedded Gtk+ widgets are themselves +children of the GtkDwViewport, since Gtk+ does not care about Dw. + +Of course, embedded Gtk+ widgets are again limited in size, but but +in position: GtkDwViewport is derived from GtkLayout which is exactly +designed for positioning widgets in an infinite scrolled area. + + +How To Get The Top-Level Dw Widget From A BrowserWindow +------------------------------------------------------- +The member "docwin" of BrowserWindow points on a GtkDwScrolledWindow, +which contains a GtkDwScrolledFrame, which contains a GtkDwViewport. +The member "child" of the latter points on the top-level Dw widget, +or may be NULL. The top-level Dw is (currently) a DwPage (HTML and +plain text documents) or a DwImage (images). + +There is a function a_Dw_gtk_scrolled_window_get_dw for this. + + +Sizes +----- +A feature adapted from the old Dw are baselines. As well DwAllocation +as DwRequisition do not have a height member, but instead ascent and +descent, both positive or zero. (Originally I removed this, but there +will be a few widgets in future depending on this, e.g., math +formulas.) + +Unlike in Gtk, sizes of zero are allowed. The upper limit for the +size of a widget is 2^31 (this will be enough to show the contents of +a small library in a web page). + + +Resizing +======== + +From outside: When writing a new widget, you should implement the +signal "size_request". When the widget changes its size, it should +call p_Dw_widget_queue_resize, as in a_Dw_image_size. See "Incremental +Resizing" below for a way to increase the speed. + +Even if the implementation of "size_request" gets quite expensive, +you do not have to check whether the size has changed, this is done +by a_Dw_widget_size_request. + +Inside: q_Dw_widget_queue_resize will set the DW_NEEDS_RESIZE flag, a +further call of a_Dw_widget_size_request will only then emit the +"size_request" signal. Furthermore, mark_size_change and +mark_extremes_change are called (see below). After that, the resizing +is done in an idle loop, this prevents too many size requests. The +algorithm is quite simple: any widget with a child which needs +resizing, needs resizing, thus all parents up to top-level widget are +marked. + +Incremental Resizing +--------------------- +A widget may calculate its size based on size calculations already +done before. In this case, a widget must exactly know the reasons, why +a call of size_request is necessary. To make use of this, a widget +must implement the following: + + 1. There is a member in DwWidget, called parent_ref, which is + totally under control of the parent widget (and so sometimes not + used at all). It is necessary to define how parent_ref is used + by a specific parent widget, and it has to be set to the correct + value whenever necessary. + + 2. The widget must implement mark_size_change and + mark_extremes_change, these methods are called in two cases: + + a) directly after q_Dw_widget_queue_resize, with the argument + ref was passed to q_Dw_widget_queue_resize, and + b) if a child widget has called q_Dw_widget_queue_resize, + with the value of the parent_ref member of this child. + +This way, a widget can exactly keep track on size changes, and so +implement resizing in a faster way. A good example on how to use this +is the DwPage widget, see DwPage.txt for details. + + +Anchors and Scrolling +===================== + +Anchors +------- +todo: This section is out of sync with the actual code. + +To set the anchor a page is viewed at, you can use one of the +following functions: + + - void a_Dw_gtk_viewport_set_anchor (GtkDwViewport *viewport, + gchar *anchor) + + Scroll directly to an anchor. The anchor does not need to exist + already, see below. + + - void a_Dw_gtk_viewport_queue_anchor (GtkDwViewport *viewport, + gchar *anchor) + + Set the anchor for the next top-level DwWidget (the next call + of a_Dw_gtk_viewport_add_dw). + +There are wrappers, a_Dw_gtk_scrolled_window_queue_anchor and +a_Dw_gtk_scrolled_window_set_anchor. + +After a_Dw_gtk_viewport_set_anchor has been called (indirectly by +Nav_open_url, or by a_Dw_gtk_viewport_add_dw), changes of anchor +positions (e.g., if widgets change there size or the anchor was not +known before) will correct the viewport adjustment (in function +p_Dw_gtk_viewport_update_anchor), but only as long as the user did not +change it directly. Look at Dw_gtk_scrolled_window_init for details +about the latter. + +Use p_Dw_widget_set_anchor to add anchors to a widget, see +DwWidget.txt. + +Scrolling +--------- +Here is an overview on more functions for scrolling: + + - To scroll to a given position, there are two possibilities: + a_Dw_gtk_viewport_set_scrolling_position simply scrolls to this + position, while Dw_gtk_viewport_scroll_to has more facilities: + you specify a rectangle you want to see, and the way how it is + seen (at the border, centered, or just scroll as much as + necessary, that it is seen). If you have a widget, you can also + use Dw_widget_scroll_to. There is also a wrapper for + GtkDwScrolledWindow, + a_Dw_gtk_scrolled_window_set_scrolling_position, and two + functions for getting the position, + a_Dw_gtk_scrolled_window_get_scrolling_position_x, and + a_Dw_gtk_scrolled_window_get_scrolling_position_y. + + - If you have a region, and want to display it, use + a_Dw_iterator_scroll_to. For example, the findtext module makes + use of it. There are equivalents for DwExtIterator and + DwWordIterator. See comments on and in the function for more + informations. + + - If you just want to determine where some content is allocated, + represented by an iterator, you can use + a_Dw_iterator_get_allocation. There are equivalents for + DwExtIterator and DwWordIterator. + + +The Objects +=========== + +This is the hierarchy of all objects of Dw: + + (GtkObject) + +-DwWidget + | +----DwBullet + | +----DwContainer + | | `----DwPage + | +----DwEmbedGtk + | +----DwHruler + | `----DwImage + `----(GtkWidget) + `----(GtkContainer) + +----(GtkBin) + | +----(GtkScrolledWindow) + | | `----GtkDwScrolledWindow + | `----GtkDwScrolledFrame + `----(GtkLayout) + `----GtkDwViewport + +Objects in parentheses are part of Gtk+, not of Dw. + + +DwBullet +-------- +Simple widget used for unnumbered list (<ul>). + + +DwContainer +----------- +The base object for Dw widgets which contain other Dw widgets. As in +Gtk+, containers are responsible for storing the children, there is +no common data structure. There are a few signals: + + - void add (DwContainer *container, + DwWidget *child); + + Currently not used, but may be in future. + + - void remove (DwContainer *container, + DwWidget *widget); + + *Recognize* that a widget is destroyed, i.e., an implementation + should remove *the pointer* from the list or so, but not + destroy the child widget. It is called by Dw_widget_shutdown. + + - void forall (DwContainer *container, + DwCallback callback, + gpointer callback_data); + + Process callback for all children, in the form + (*callback)(child, callback_data). + + The include_internals of the Gtk+ equivalent was not adapted, + since it is used for more sophisticated purposes not needed in + Dw. + + +DwEmbedGtk +---------- +This Dw widget is used to embed Gtk+ widgets into Dw container +widgets. The Gtk+ widget is set by a_Dw_embed_gtk_add_gtk, and can +simply be removed by destroying it. + +If the DwEmbedGtk contains no Gtk+ widget, it always returns 0x0x0 as +size, so, for speed reasons, first add the Gtk+ widget into the +DwEmbedGtk, and then the DwEmbedGtk into the other Dw widget, as at +the end of Html_tag_open_input. + + +DwHruler +-------- +Simple widget used for the <hr> tag. + + +DwImage +------- +Widget for displaying image. See DwImage.txt for details. + + +DwPage +------ +A widget for displaying texts. See DwPage.txt for details. + + +DwTable +------- +A container widget for rendering tables. See DwTable.txt for details. + + +DwWidget +-------- +The base object for all Dw widgets. See DwWidget.txt for details. + + +GtkDwScrolledWindow +------------------- +Adds a few functionalities to GtkScrolledWindow: it creates the +GtkDwScrolledFrame and the GtkDwViewport, connects some signals, and +provides some wrappers for using the GtkDwViewport. + + +GtkDwScrolledFrame +------------------ +General purpose scrolled widget containing another scrolled widget, +adding a border and a focus frame. Furthermore, it processes key +presses and mouse drags (button 2, as in Gimp) to move the viewport. + +There are two signals (except "set_scroll_adjustments"), +"user_hchanged" and "user_vchanged", which are emitted when the user +changed the viewport adjustments horizontally/vertically by using the +keys or button 2 dragging. + + +GtkDwViewport +------------- +The interface between Gtk+ and Dw. It is responsible for displaying +Dw Widgets and processing their events. It is derived from GtkLayout, +to make embedding Gtk+ widgets into Dw widgets simpler, see the +documentation of GtkLayout in the Gtk+ tutorial for details. + +GtkDwViewport contains at most one top-level Dw Widget, if it exists. +The Gtk+ methods of GtkDwViewport are more or less mapped on the +methods of the DwWidget. In detail: + + - Dw_gtk_viewport_size_allocate will call a_Dw_widget_set_width, + a_Dw_widget_set_ascent (with allocation->height) and + a_Dw_widget_set_descent (with zero as argument), and then allocate + the Dw widget at the size returned by a_Dw_widget_size_request. + + - Dw_gtk_viewport_draw and Dw_gtk_viewport_expose will call + a_Dw_widget_draw, which will emit the "draw" signal. + + - Handling of mouse events is mostly done in Dw_widget_mouse_event, + see DwWidget.txt for details. Note that the functions return + FALSE, if the event was not processed, so that they are delivered + to the parent widget(s) of the GtkDwViewport, this scheme e.g. + prevents dragging of the viewport (done by GtkScrolledFrame) when + pressing mouse button 2 on a link. + +You may call gtk_container_set_border_width for a border around the +scrolled area. diff --git a/doc/DwImage.txt b/doc/DwImage.txt new file mode 100644 index 00000000..d79f50bf --- /dev/null +++ b/doc/DwImage.txt @@ -0,0 +1,201 @@ +Jan 2001, S.Geerken@ping.de +Last update: Dec 2004 + +======= +DwImage +======= + +A widget for displaying images and handling image maps. + + +Image Maps +========== + +Client Side Image Maps +---------------------- +You must first create a list of image maps: Allocate a DwImageMapList, +and initialize it by calling a_Dw_image_map_list_init. Adding a map is +done by a_Dw_image_map_list_add_map. a_Dw_image_map_list_add_shape +adds a shape to the last map. For the meaning of the link argument, +see Section "Signals". + +Image maps are referred by a URL (not only by a name). But currently, +the image map list is stored in DilloHtmlLB and there is no +possibility to parse documents without rendering, so images can only +use maps defined in the same document. + +To use a map in an image, call a_Dw_image_set_usemap with the image, +the map list, and the URL of the map. Passing the whole list makes it +possible to use maps parsed after the image is created. + + +Server Side Image Maps +---------------------- +To use images for server side image maps, you must call +a_Dw_image_set_ismap and the style must contain a valid link +element. See section "Signals" for more details. + + +Signals +======= + +There are five signals, which can be connected to process actions with +links. All have at least three arguments: + + - link is the link element of the DwStyle (server side image maps) + or DwImageMapShape (client side image maps). The value is an + integer, which is currently only used for hypertext links. But + generally, it depends on the signal callback how this value is + used. + + - x and y are, when server side image maps are used, the relative + coordinates of the mouse pointer, otherwise always -1. + +Note that, unlike by DwPage before, no cursors are set. Instead, the +signal callback function has to do this. + +The signals: + + - void link_entered (DwImage *image, + gint link, gint x, gint y) + + Emitted when the link the mouse pointer is over has + changed. "Changed" includes link, x and y, so this signal is also + emitted each time the pointer is moved within a server side image + map. If the pointer is outside of a link, all arguments have the + value -1. + + + - void link_pressed (DwImage *image, + gint link, gint x, gint y, + GdkEventButton *event) + + Emitted when the user presses a mouse button _inside_ a link, + this signal is never emitted with link = -1. You can use the + event to get information about the button, shift keys, etc. + + + - void link_released (DwImage *image, + gint link, gint x, gint y, + GdkEventButton *event) + + - void link_clicked (DwImage *image, + gint link, gint x, gint y, + GdkEventButton *event) + + Analogue to link_pressed. + + - void void (*image_pressed) (DwImage *page, + GdkEventButton *event) + + Emitted when the user presses the mouse button on an image which + has no related map. In some cases, it may be neccessary to + suppress event processing by a_Dw_widget_set_button_sensitive(). + + +Future Extentions +================= + +(See also Imgbuf.txt, what is named DilloImageBuffer here, has been +implemented under the name Imgbuf.) + +These are some ideas for a different design, which will solve several +problems (image transparency, memory usage when implementing a global +size factor): + +1. Instead of a guchar array, a new data type, DilloImageBuffer, + should be used (DICacheEntry::ImageBuffer and DwImage::buffer). Any + access is done by function calls. Copying the lines (in + a_Image_write) is done by a_Image_buffer_copy_line, etc. The call to + Image_line becomes obsolete, since DilloImageBuffer will deal with + different types: indexed, RGB, gray, RGBA, gray-alpha(?). This may + be useful for a more efficient implementation of DilloImageBuffer. + +2. The modules Png, Jpeg, Gif, Image and DICache deal with the + original image size (read by the decoders), while DwImage gets the + "viewed" size (original size, multiplied by the global image + scaling factor) from DilloImageBuffer. + +3. For DwImage, there are further methods which replace the current + direct access. Note to worth: + + - Scaled buffers are shared, reference counters are used. Copying + rows will automatically also affect the related scaled buffers. + + - There are two methods for drawing, one called after expose events + (Dw_image_draw) and another called by a_Dw_image_draw_row. The + exact rules, how the buffer is scaled (this can, e.g., differ by a + pixel or so), is hidden by DilloImageBuffer. + + - As noted above, all DwImage code is based on the viewed size. + +4. The global image scaling factor is used in two places. The HTML + parser must apply it on the WIDTH and HEIGHT attributes of the + <IMG> tag and DilloImageBuffer must use it to scale the inherent + size of an image. There are two modes, which the user can switch + by a dillorc option: + + (i) The original image data is preserved, and an additional scaled + buffer is created: + + +-------+ +------------------+ additional + | Image | -- copy --> | DilloImageBuffer | --> scaled + +-------+ rows | (original size) | buffers + +------------------+ + | ^ + | | + scale requests + each row for scaled + | buffers + | | + v | + +------------------+ + | DilloImageBuffer | + | (viewed size) | + +------------------+ + ^ + | + +---------+ + | DwImage | + +---------+ + + (ii) The original data gets lost just after scaling: + + +-------+ +------------------+ + | Image | -- copy --> | DilloImageBuffer | --> scaled + +-------+ rows | (viewed size) | buffers + +------------------+ + ^ + | + +---------+ + | DwImage | + +---------+ + + (ii) uses generally less memory, while in some cases leads to a + lower rendering quality, as in this example: + + "foo.png" has a size of 100x100 pixels, the image scaling factor is + 50%, and the following HTML sniplet is rendered: + + <img src="foo.png"> + <img src="foo.png" width=200 height=200> + + The first image is displayed at a size of 50x50, the second at + 100x100. (i) will, for the second, use the original buffer, but + (ii) will enlarge the buffer, which was scaled down before, so + resulting in a "pixelized" image. + +5. Any implementation of DilloImageBuffer will handle transparency + independent of the background, i.e., the background colour will not + be used anymore. The first implementation may be based on GdkRGB + and (when needed) dithered clipping bitmaps. Better implementations + may then be developed in the future. + +6. Background images: The modules Image and DICache do no longer + access the DwImage directly, all calls are replaced by an + Interface, which is then implemented by DwImage and an appropriate + structure for background images (part of DwStyle). The interface is + in C realized by a struct of function pointers, and a generic + pointer on DwImage, etc. + +7. If someone really needs it, animated GIF's may be considered. diff --git a/doc/DwPage.txt b/doc/DwPage.txt new file mode 100644 index 00000000..71e6af88 --- /dev/null +++ b/doc/DwPage.txt @@ -0,0 +1,152 @@ +Nov 2001, S.Geerken@ping.de +Last update: Dec 2004 + +====== +DwPage +====== + +A widget for displaying texts. It is (currently) the main widget for +rendering HTML documents. + + +Signals +======= + +DwPage defines the same signals as DwImage, except "image_pressed", +with the exception that the coordinates are always -1. See +DwImage.txt for more details. + + +Collapsing Spaces +================= + +The idea behind this is that every text box has a specific vertical +space around and that they are combined to one space, according to +rules stated below. A rule is either a paragraph within a DwPage +widget, or a DwPage within a DwPage widget, in a single line; the +latter is used for indented boxes and list items. + +The rules: + + 1. If a box is following another, the space between them is the + maximum of both box spaces: + + +---------+ + |/////////| + |/////////| +---------+ + +---------+ |/////////| + | A | |/////////| + +---------+ +---------+ + |/////////| | A | + |/////////| +---------+ + +---------+ are combined like this: |/////////| + |XXXXXXXXX| + +---------+ +---------+ + |\\\\\\\\\| | B | + +---------+ +---------+ + | B | |\\\\\\\\\| + +---------+ +---------+ + |\\\\\\\\\| + +---------+ + + 2. a) If one box is the first box within another, the upper space + of these boxes collapse. b) The analogue is the case for the + last box: + + +---------+ If B and C are put into A, + |/////////| the result is: + |/////////| + +---------+ +---------+ +---------+ + | A | <--+-- |\\\\\\\\\| |/////////| + +---------+ ¦ +---------+ |XXXXXXXXX| + |/////////| | | B | +---------+ + |/////////| | +---------+ | B | + +---------+ | |\\\\\\\\\| +---------+ + | +---------+ |\\\\\\\\\| + | |\\\\\\\\\| + | +---------+ |\\\\\\\\\| + `-- |\\\\\\\\\| +---------+ + |\\\\\\\\\| | C | + |\\\\\\\\\| +---------+ + +---------+ |\\\\\\\\\| + | C | |XXXXXXXXX| + +---------+ |XXXXXXXXX| + |\\\\\\\\\| +---------+ + |\\\\\\\\\| + |\\\\\\\\\| + +---------+ + +For achieving this, there are some features of DwPage: + + - Consequent breaks are automatically combined, according to + rule 1. See the code of a_Dw_page_add_break for details. + + - If a break is added as the first word of the DwPage within + another DwPage, collapsing according to rule 2a is done + automatically. See the code of a_Dw_page_add_break. + + - To collapse spaces according to rule 2b, + a_Dw_page_hand_over_break must be called for the *inner* + widget. The HTML parser does this in Html_eventually_pop_dw. + + +Collapsing Margins +================== + +Collapsing margins, as defined in the CSS2 specification, are, +supported in addition to collapsing spaces. Also, spaces and margins +collapse themselves. I.e., the space between two paragraphs is the +maximum of the space calculated as described in "Collapsing Spaces" +and the space calculated according to the rules for collapsing margins. + +(This is an intermediate hybrid state, collapsing spaces are used in +the current version of dillo, while I implemented collapsing margins +for CSS and integrated it already into the main trunk. For a pure +CSS-based dillo, collapsing spaces will not be needed anymore, and may +be removed for simplicity.) + + +Some Internals +============== + +There are two lists, words and lines. The word list is quite static; +only new words may be added. A word is either text, a widget, a break +or an anchor. Anchors are stored in the text, because it may be +necessary to correct the scroller positions at rewrapping. + +Lines refer to the word list (first and last), they are completely +redundant, i.e., they can be rebuilt from the words. Lines can be +rewrapped either completely or partially (see "Incremental Resizing" +below). For the latter purpose, several values are accumulated in the +lines. See the file "dw_page.h" for details. + + +Incremental Resizing +-------------------- +DwPage makes use of incremental resizing as described in Dw.txt, +section "Resizing". The parent_ref is, for children of a DwPage, +simply the number of the line. + +Generally, there are three cases which may change the size of the +widget: + + 1. The available size of the widget has changed, e.g., because the + user has changed the size of the browser window. In this case, + it is necessary to rewrap all the lines. + + 2. A child widget has changed its size. In this case, only a rewrap + down from the line where this widget is located is necessary. + + (This case is very important for tables. Tables are quite at the + bottom, so that a partial rewrap is relevant. Otherwise, tables + change their size quite often, so that this is necessary for a + fast, non-blocking rendering) + + 3. A word (or widget, break etc.) is added to the page. This makes + it possible to reuse the old size by simply adjusting the + current width and height, so no rewrapping is necessary. + +The state of the size calculation is stored in wrap_ref within DwPage, +which has the value -1 if no rewrapping of lines necessary, or +otherwise the line from which a rewrap is necessary. + diff --git a/doc/DwRender.txt b/doc/DwRender.txt new file mode 100644 index 00000000..73fd0ded --- /dev/null +++ b/doc/DwRender.txt @@ -0,0 +1,620 @@ +Aug 2004, S.Geerken@ping.de + +=========================== + Dw Render Abstraction +=========================== + +This document describes the rendering abstraction used in Dw. At the +time this document was written, some other documents about Dw were out +of date, and have to be rewritten. + +Sometimes, you will find remarks about the old design (with one +concrete Gtk+ widget instead of the layout/multiple rendering views); +those are useful for those, who are already familiar with the old +design. Furthermore, it makes the explanation often simpler, because +the new design is a bit more complex. + +Naming +------ +As stated in "Dw.txt", the prefix "p_Dw_" is used for functions used +within the whole Dw module, but not outside. Typically, these are +protected functions, which are called by inherited classes. E.g., a +specific widget will have to call p_Dw_widget_queue_resize(), but this +function should not called outside from Dw. + +The "Dw_" prefix is not only used for functions, which use is +restricted to a sub-module, but also for functions used only within +the core of Dw. The distinction between both can simply made, whether +the function is declared as static or not. The core of Dw consists of +the modules Dw_widget and Dw_render, anything else is inherited from +these two modules (widgets, platforms, views). As an example, +Dw_gtk_viewport_queue_resize() is only called in the Dw_widget module, +it should not be called by a widget implementation. + + +Overview +======== + +Rendering of Dw is done in a way resembling the model-view pattern, at +least formally. Actually, the counterpart of the model, the layout +(DwRenderLayout), does a bit more than a typical model, namely +calculating the layout, and the views do a bit less than a typical +view, i.e. only the actual drawing. + +Additionally, there is a structure representing common properties of +the platform, views generally work only together with one specific +platform. A platform is typically related to the underlying UI +toolkit, but other uses may be thought of. + +The general structure looks like this: + + 1 +------------------+ + .---->| DwRenderPlatform | + +----------------+ 1 / +------------------+ + | DwRenderLayout |--< ^ 1 + +----------------+ \ | * + 1 ^ | 1 \ +--------------+ + | | `---->| DwRenderView | + | | * +--------------+ + | | + layout | | toplevel_dw + | | + * | V 0..1 + +----------+ 0..1 + | DwWidget |--. parent + +----------+ | + * | | + `-------' + (children) + +(This is a bit simplified, actually, only DwContainer has child +widgets, and the relation is only defined in an abstract way). + +DwRenderLayout and DwWidget (including sub classes) are platform and +view independant, i.e., it should be possible to adopt them into +another platform, after suitable implementations of DwRenderPlatform +and DwRenderView have been developed. + +(The only exception is DwEmbedGtk, a widget which, in the old design, +embeds Gtk+ widgets into the DwWidget tree. This will still remain a +platform specific widget, see notes in the section "UI Widgets". + +Furthermore, this design does not yet cover DwStyle, which is still +bound to the Gtk+ platform. It may be simply replaced for other +platforms, but this will lose the possibility to have multiple +implementations of DwRenderPlatform running within the same program. +The same aplies to DwTooltip and Imgbuf.) + +This new design helps to archieve two important goals: + + 1. Abstraction of the actual rendering. Currently, we only have the + normal viewport, but a list of planned implementations can be + found below in this text. + + 2. It makes different views of the same document simple, e.g. the + normal viewport and the preview window. + + 3. It makes portability simpler. + +Vieports are handles by this design, but only in a rather abstract +way, and may be removed completely, i.e., they will be only part of +specific view implementations. This also means, that the distinction +between world coordinates and viewport coordinates vanishes from most +parts of Dw. + + +World +===== + +The world is the whole area, in which content is rendered. For a view +without a viewport, this is the whole itself. A view with a viewport +provides a way, so that the user may see some part of the world within +a (in most cases) smaller window, the viewport. + +The world may have a base line, so that the worls size is described by +three numbers, width, ascent and descent. + +Any view must implement the method set_world_size(), which is called, +whenever the world size changes. For viewports, this will change +scroll bars etc., for views without viewport, this will normally +change the size of the view itself. (Although on this level, "view +size" is not defined. This should not be confused with the viewport +size!) + + +Drawing +======= + +A view must implement several drawing methods, which work on the whole +world. If it is neccesary to convert them (e.g. in DwGtkViewport), +this is done in a way fully transparent to DwWidget and +DwRenderingLayout, instead, this is done by the view implementation. + +There exist following situations: + + - A view gets an expose event: It will delegate this to the + rendering layout, which will then pass it to the widgets, with + the view as a parameter. Eventually, the widgets will call + drawing methods of the view. + + - A widget requests a redraw: In this case, the widget will + delegate this to the layout. The drawing requests are queued, and + compressed. in an this idle function, DwWidget::draw is called + for the toplevel widget, for each view. + + (This is still something to consider, the queueing may be moved + again into the view implementations.) + +If the draw method of a widget is implemented in a way that it may +draw outside of the widget's allocation, it should draw into a +clipping view. A clipping view is a rendering view related to the +actual rendering view, which guarantees that the parts drawn outside +are discarded. At the end, the clipping view is merged into the actual +view. Sample code for widget DwFoo: + + void Dw_foo_draw (DwWidget *widget, + DwRenderView *view, + DwRectangle *area) + { + DwRenderView *clip_view; + + /* 1. Create a clipping view. */ + clip_view = + p_Dw_render_view_get_clipping_view (view, + widget->allocation.x, + widget->allocation.y, + widget->allocation.width + DW_WIDGET_HEIGHT (widget)); + + /* 2. Draw into clip_view. */ + Dw_render_view_do_some_drawing (clip_view, ...); + + /* 3. Draw the children, they receive the clipping view as argument. */ + for (<all relevant children>) { + if (p_Dw_widget_intersect (button->child, area, &child_area)) + p_Dw_widget_draw (child, clip_view, child_area); + } + + p_Dw_render_view_merge_clipping_view (view, clip_view); + } + +A drawing process is always embedded into calls of +DwRenderView::start_drawing() and DwRenderView::finish_drawing(). An +implementation of this are backing pixmaps, to prevent flickering. + + +Viewports and Scrolling Positions +================================= + +Although the design implies that the usage of viewports should be +fully transparent to the Dw_render module, this cannot be fully +archived, for the following reasons: + + 1. Some features, which are used on the level of DwWidget, + e.g. anchors, refer to scrolling positions. + + 2. Some code in DwWidget is currently tightly bound to viewports. + This may be change, by delegating some of this functionality to + viewports, and defining scrolling positions in a more abstract + way. + +Therefor, DwRenderLayout keeps track of the viewport size, the +viewport position, and even the thickness of the scrollbars (or, +generally, "viewport marker"), they are relevant, see below. These +sizes are always equal in all views. However, a given view may not use +viewports at all, and there may be the case, that no view related to a +layout uses viewports, in this case, the viewport size is not defined +at all. + +Unlike world sized, viewports are not considered to have a base line, +i.e., they are described by only two numbers, width and height. + +Viewport Size +------------- +All viewport sizes and positions are the same in all views, which uses +viewports. There are two cases, in which the viewport size changes: + + 1. As an reaction on a user event, e.g. when the user changes the + window size. In this case, the affected view delegates this + change to the layout, by calling + p_Dw_render_layout_vieport_size_changed(). All other views are + told about this, by calling DwRenderView::set_viewport_size(). + + 2. The viewport size may also depend on the visibility of UI + widgets, which depend on the world size, e.g scrollbars, + generally called "viewport markers". This is described in an own + section. + +After the creation of the layout, the viewport size is undefined. When +a view is attached to a layout, and this view is already to be able to +define its viewport size, it may already call +p_Dw_render_layout_vieport_size_changed() within the implementation of +set_layout. If not, it may do this, as soon as the viewport size gets +known. + +Each call of p_Dw_render_layout_vieport_size_changed() will change the +viewport size of all other views, which use viewports, so this +function has to be called with care. + +If there is no view attached, which used viewports, the viewport size +remains undefined. + +Viewport Markers +---------------- +Viewport markers are UI widgets, which display to the user the +relation of the world size and the widget size. Most commonly, +scrollbars will be used. + +When they are not needed, they are hidden, as in the following figure: + + + +--------------------+ + | This is only a | + | very short text. | + | | + | | + | | + +--------------------+ + +but shown, as soon as the world size exceeds the viewport size: + + +------------------+-+ + | In this example, |^| + | there is some |#| + | more text, so |#| + | that the | | + | vertical |v| + +------------------+-+ + +A view using viewports must provide, how large the differences +are. Generally, there are four cases, from the combinations of whether +the world width is smaller (-) or greater (+) than the viewport width, +and whether the world height is smaller (-) or greater (+) than the +viewport height. So there are eight numbers, the horizontal difference +dh, and the vertical difference dv, for the cases --, -+, +-, and ++. + +For scrollbars, the values are rather simple: + + - dh is 0 for the cases -- and -+, and the thickness of the + vertical scrollbar in the cases +- and ++. + + - dv is 0 for the cases -- and +-, and the thickness of the + horizontal scrollbar in the cases -+ and ++. + +For any view implementation, the following rules must be fullfeeded +(dx means either dh or dv): + + - dx(-+) >= d(--) + - dx(++) >= d(+-) + - dx(+-) >= d(--) + - dx(++) >= d(-+) + +In short, when smaller world dimensions (-) grow (switch to +), the +differences may not become less. + +The sizes of viewport markers are part of the viewport size, the +method DwRenderView::set_viewport_size() has four parameters: + + - two for the size of the viewport, *including* the markers + (i.e. the markers never change the size), and + + - two, which denote the differences between the above and the + actual viewport size, caused by the markers. If a value of these + is 0, the respective marker is hidden, if it is greater than 0, + it is shown. In the latter case, the maximun is calculated, and + passed to all views. + +(Actually, the decision, whether the markers are visible or not, is a +bit more complicated, since the vieport size also defines the size +hints for the topmost widget, which may affect the widget size, and so +the world size. Handling this problem is done within DwRenderLayout, +look at the comments there.) + +Scrolling Positions +------------------- +The scrolling position is the world position at the upper left corner +of the viewport. Views using viewports must + + 1. change this value on request, and + 2. tell other changes to the layout, e.g. caused by user events. + +Applications of scrolling positions (anchors, test search etc.) are +handled by the layout, in a way fully transparent to the views. + + +An Example with Nested Views +============================ +The following example refers to graphical plugins, which are not yet +realized (I have some working proof-of-concept code), but which will +most likely follow the design, which is here shortly described, as +needed to understand the example (since there is no specification of +graphical plugins yet). I included this, to demonstrate, how nested +layouts can be used. + +Graphical plugins will communicate with dillo via two protocols, dpi1 +(for anything not directly related to rendering), and a new protocol, +which is an extension of the XEmbed protocol (see somewhere at +http://freedesktop.org, this is the protocol, which GtkPlug and +GtkSocket use). Within dillo, a socket window will be created, which +window id will be (via dpi1?) passed to the plugin. The plugin will +then create a plugin window, in which the contents of a web recource +is shown, which dillo cannot show natively. + +XEmbed will be extended, so that the plugins may make use of the +extensions, which the Dw size model adds to the XEmbed size +model. Such a plugin behaves (on an abstract level) like a DwWidget, +so following extensions are necessary: + + - a plugin may not simply have a size, but instead distinguish + between ascent and descent, + - a plugin may return width extremes, and + - it is possible to send size hints to the plugin. + +Within Dw, the socket will be realized by a special plugin, +DwSocketGtk (or a respective widget for other platforms), which is a +sub class of DwEmbedGtk, and embeds another UI widget, which will +provide the socket window (for Gtk+, either GtkSocket, or a sub class +of this). + +The plugins may be implemented independently of Dw, they either do not +support the extensions to XEmbed (then, the respective features behave +in a default way), or they may contain there own implementation. +However, Dw may be extracted from dillo, to become an own library. A +plugin using this library may then use a special type of view, +GtkDwFlatView (for plugins based on Gtk+, actually, the UI toolkit for +dillo and the plugin must not be the same, since the protocol will be +UI toolkit independant.) + +This will lead to a structure like this: + + top_layout:DwRenderLayout ----- top_page:DwPage + / \ | + :GtkDwPlatform top_view:GtkDwView `- table:DwTable + | + `- cell:DwTableCell + | + `- socket:DwSocketGtk + | + DILLO gtk_socket:GtkSocket + . +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - . (extension of + . XEmbed) + PLUGIN + plugin_layout:DwRenderLayout ----- dw_flat_view:GtkDwFlatView + \ + `------- foo:DwFoo + +GtkDwFlatView is both an extension of GtkSocket, as well as an +implementation of DwRenderView. + +This case must be equivalent to the case, that the widget "foo" is a +direct child of the widget "cell", since all objects between them only +delegate all functionality. Size hints must so sent as in the +following scenario: + + 1. The view "top_view" receieves an event, which will change the + viewport size, and delegates this to the layout. + + 2. The layout will make size hints of this viewport size, i.e., it + calls the set_* methods of the widget "top_page". + + 3. DwPage::set_* will queue a resize request, which is delegated + to the layout. + + 4. Handling the resize request is done within a idle loop. Here, + size_request is called for the widget "top_page", to determine + the preferred size. + + 5. DwPage::size_request will depend on the size hints, especially, + it will use the hinted size to determine the word wrap width. + Within DwPage::size_request, size hints will be sent to the + child widgets. The size hints sent to the children are those + sent to the page, minus the respective border widths (margins, + borders, paddings). (How the ascent and descent hints should + generally be delegated to the children, is still a matter of + consideration, and may change.) + + 6. DwTable::size_request, which is called within this context, has + a different way to delegate the size hints to the children: + ascent and descent are handled the same way, but the widths are + calculated from the column widths, which are calculated within + DwTable::size_request. + + 7. Via the widget "cell", finally the widget "socket" receives + appropriate size hints. These must be (equally) delegated to + the widget "foo", this is done in a way described in the + following steps. + + 8. The size hints are transmitted via the protocol extending + XEmbed. The Gtk+ widget "dw_flat_view" receives them (via the + widget "gtk_socket"), and delegates them to the respective + layout "plugin_layout". (TODO: How is this done? What has to be + done is to send the size hints to the toplevel widget, but a + view has no access to it.) + + 9. The layout "plugin_layout" will then delegate this (what ever + "this" means), as size hints, to the widget "foo". + + 10. The widget "foo" will queue a size_request, which is delegated + to the layout. In the idle function handling this request, the + recalculation of the widget size will change the world size, + which is delegated to the view "dw_flat_view". + + 11. For GtkDwFlatView, the world size is the view size. For this + reason, it changes its size, to fit the world size. This size + is send to the DwSocket, which will then request a size + request. + + +UI Widgets +========== +A note before: This chapter refers often to Gtk+ as base UI toolkit, +just to make naming simpler, For other UI toolkits, the same, mostly +only with other names, applies. + +In some cases, it is necessary to embed widgets from the UI toolkit +into the view, e.g. to realize HTML forms in dillo. + +The Dw_render module provides no interface at all, instead, this is +completely left to the platform implementations. Below, two design +approaches are discussed, which have following in common: + + 1. There is a special Dw widget, which embeds something related to + the UI widgets (below called DwEmbedGtk). + + 2. This Dw widget, the platform, and the views are tightly bound, + beyond the methods, which the respective interfaces provide. The + Dw widget can simply refer to the platform by + (DwWidget::render_layout->platform, and the platform may refer + to the views, when DwPlatform::attach_view and + DwPlatform::detach_view are implemented. + +General Problems +---------------- +For most UI toolkits, there have to be multiple instances of widgets +in different views. Furthermore, there may be, on the same platform, +different classes for the same widgets. Consider this simple example: + + :DwRenderLayout ------------------- :DwPage + / \ | + ,----' `----. dw:DwEmbedGtk + / \ + view1:GtkDwViewport view2:GtkDwViewport + | | + gtk1:GtkButton gtk2:GtkButton + +This structure represents a page with one button in it, which is, in +the DwWidget tree, represented by the DwEmbedGtk "dw", and, in the +views "view1" and "view2", by the Gtk+ widgets "gtk1" and "gtk2", +respectively. The old approach, where the UI widget (in this case +GtkButton) is not directly applicable, since there must be multiple +instances of this UI widget. + +Here is a more complex example: + + :DwRenderLayout ------------------- :DwPage + / \ | + ,----' `----. dw:DwEmbedGtk + / \ + view1:GtkDwFooView view2:GtkDwBarView + | | + gtk1:GtkFooButton gtk2:GtkBarButton + +In this case, the different views GtkDwFooView and GtkDwBarView deal +with different classes for buttons, GtkFooButton and GtkBarButton. + +Simple Approach +--------------- +Within dillo, the following limitations are reasonable: + + 1. There is only one view instance, which actually needs UI widgets + (GtkDwViewport for Gtk+). There may be multiple views, but these + additional views do not need instances of widgets, e.g. in the + preview, a UI widget is simply shown as a grey box. + + 2. There is only one type of UI widget, i.e. normal Gtk+ widgets. + +Because of these limitations, the implementation of UI widgets is +currently quite simple in dillo. As before, the widget DwEmbedGtk +refers to an instance of a concrete Gtk+ widget. This Gtk+ widget is +told to the platform, of which DwEmbedGtk knows, that it is an +instance of GtkDwPlatform. GtkDwPlatform will add this Gtk+ widget to +the single view, which needs it, and DwEmbedGtk will be responsible of +allocating etc. of the Gtk+ widget. + +Advanced Approach +----------------- +This is a short overview of an approach, which will overcome the +limitations of the simple approach, but with the costs of a greater +complexity. It will be detailed, in the case, that it is implemented. + + +------------+ factory +---------------------+ + | DwEmbedGtk | ---------> | DwGtkWidgetFactory | + +------------+ 1 1 +---------------------+ + | create_foo_widget() | + | create_bar_widget() | + +---------------------+ + . . . + /_\ /_\ /_\ + | | | + - - - - - - - - - - - - - - - - - . + | | + +---------------------+ +---------------------+ | + | DwGtkButtonFactory | | DwGtkListFactoty | (other ...) + +---------------------+ +---------------------+ + | create_foo_widget() | | create_foo_widget() | + | create_bar_widget() | | create_bar_widget() | + +---------------------+ | add_list_item(...) | + +---------------------+ + +DwEmbedGtk refers to a factory, which creates the UI widgets for each +view. For each general widget type (e.g. button, list, ...), there is +an implementation of this interface. + +This interface DwGtkWidgetFactory contains different method for +different views. E.g., when a button is shown, and so DwEmbedGtk +refers to a DwGtkButtonFactoty, GtkDwFooView may need a GtkFooButton, +which is created by create_foo_widget(), while GtkDwBarView may call +create_bar_widget(), to get a GtkBarButton. (This design still makes +it hard to add new views, which then need, say, GtkBazButtons.) + +The concrete factories may contain additional methods, in the diagram +above, DwGtkListFactory must provide a function to add items to the +lists. This method will add a item to all list widget, which were +created by this factory. + + +More Ideas for View Implementations +=================================== + +Preview Window +-------------- +The status bar gets a new button, on which the user may click to get a +small image of the small page, in which he can select the viewport +position by dragging a rubber-band rectangle. The actual scaling +factor will probably depend on the aspect ratio of the page. + +(This has already been implemented for Gtk+, as GtkDwPreview and +GtkDwPreviewButton.) + +Graphical Plugin +---------------- +See section "An Example with Nested Views" for explanations. + +====================================================================== + +Alternative design for handling UI widgets: + +- There is a platform *independant* widget, the platform dependencies + are within the factories. + + DwEmbedUI::draw + -> DwRenderView::draw_ui_widget(..., factory) + +Implementation of "draw_ui_widget" in a preview view: + -> draw some gray boxes etc. + +Implementation of "draw_ui_widget" in a normal "bar" view (for platform "foo"): + -> determine that is is a factory for a "baz" widget? + -> (BarFactory*)factory->get_foo_bar_widget(this) + +This method either returns an already created widget, or creates one +with the current state of the factory. "This" is taken as a key. + +---- + + | general | widget specific +----------------------+------------------+------------------------ + platform independant | nothing? | e.g. adding list items +----------------------+------------------+------------------------ + platform dependant | e.g. creating a | ??? + | widget, or | + | informations | + | about drawing in | + | a preview | + +====================================================================== + +Abstraction of DwStyle +---------------------- +structures needed: diff --git a/doc/DwStyle.txt b/doc/DwStyle.txt new file mode 100644 index 00000000..9a1ec4e5 --- /dev/null +++ b/doc/DwStyle.txt @@ -0,0 +1,310 @@ +Apr 2001, S.Geerken@ping.de +Last update: Dec 2004 + +======= +DwStyle +======= + +Styles of Dillo Widgets + + +Note +==== + +DwStyle has derived from DwPageAttr, and its current structure is very +similar to it. In the future, there will be some changes and extensions. +Namely: + + - image maps will be handled differently (done), + - margins, borders, paddings (done), + - background colors/images, and + - cursors and tooltips will perhaps move into DwStyle. + +Furthermore, widgets will probably refer to different styles for +different states. + + +Overview +======== + +DwStyle provides some resources and attributes for drawing widgets, as +well as for parts of a widget (e.g., DwPage uses DwStyle's for its +words). Creating a style is done by filling a DwStyle with the +attributes (except the ref_count), and calling Dw_style_new: + + DwStyle style_attrs, *style; + + style_attrs.foo = bar; + // etc. + style = a_Dw_style_new (&style_attrs, random_window); + // do something with style + +After this, the attributes of style should not be changed anymore, +since styles are often shared between different widgets etc. (see +below). Most times, you simply copy the attributes of another style +and modify them: + + style_attrs = *another_style; + style_attrs.foo = bar; + style = a_Dw_style_new (&style_attrs, random_window); + +The font structure can be created by Dw_style_font_new, in a similar +way (the GdkFont in font_attrs will be ignored), and colors by +Dw_style_color_new, passing 0xrrggbb as an argument. Note that fonts +and colors are only intended to be used in conjunction with DwStyle. + + +Lengths and Percentages +======================= + +DwStyleLength is a simple data type for lengths and percentages: + + - A length refers to an absolute measurement. It is used to + represent the HTML type %Pixels; and the CSS type <length>. + + For CSS lenghts, there are two units: (i) pixels and absolute + units, which have to be converted to pixels (a pixel is, unlike + in the CSS specification, treated as absolute unit), and (ii) the + relative units "em" and "ex" (see below). + + - A percentage refers to a value relative to another value. It is + used for the HTML type %Length; (except %Pixels;), and the CSS + type <percentage>. + + - A relative length can be used in lists of HTML MultiLengths. + +Since many values in CSS may be either lengths or percentages, a +single type is very useful. + +Useful macros and functions +--------------------------- +Macros for creating lengths: + + DW_STYLE_CREATE_LENGTH (n) Returns a length of n pixels. + + DW_STYLE_CREATE_EX_LENGTH (n) Returns a length of n times the + 'x-height' + + DW_STYLE_CREATE_EM_LENGTH (n) Returns a length of n times the + 'font-size' + + DW_STYLE_CREATE_PERCENTAGE (n) Returns a percentage, n is relative + to 1, not to 100. + + DW_STYLE_CREATE_RELATIVE (n) Returns a relative length. + + DW_STYLE_UNDEF_LENGTH Used to indicate unspecified sizes, + errors, and the end of a list of + lengths. + +Furthermore, there are some functions in html.c: + + DwStyleLength Html_parse_length (gchar *attr); + + Returns a length or a percentage, or DW_STYLE_UNDEF_LENGTH in + case of an error. + + DwStyleLength* Html_parse_multi_length (gchar *attr); + + Returns a vector of lengths/percentages. The caller has to free + the result when it is not longer used. + +Macros for examining lengths: + + DW_STYLE_IS_LENGTH (l) Returns TRUE if l is a length. + + DW_STYLE_IS_PERCENTAGE (l) Returns TRUE if l is a percentage. + + DW_STYLE_IS_RELATIVE (l) Returns TRUE if l is a relative + length. + + DW_STYLE_GET_LENGTH (l, f) Returns the value of a length in + pixels, as an integer. f is the + font, this is used if l is based on + font sizes. + + DW_STYLE_GET_PERCENTAGE (l) Returns the value of a percentage, + relative to 1, as a float. + + DW_STYLE_GET_RELATIVE (l) Returns the value of a relative + length, as a float. + + +Representation +-------------- +Notes: + + 1. This is not part of the interface and may change! Use the + macros described above. + 2. Negative numbers may not work yet. + +DwStyleLength is represented by an integer (n is the number of bits of +an integer): + + - Undefined lengths are represented by 0. + + - Lenghts in pixel: + + +---+ - - - +---+---+---+---+ + | int value | 0 | 1 | + +---+ - - - +---+---+---+---+ + n-1 3 2 1 0 + + - Lengths in in x-height: + + +---+ - - - +---+---+---+---+ + | real value | 0 | 1 | 1 | + +---+ - - - +---+---+---+---+ + n-1 3 2 1 0 + + - Lengths in in font-size: + + +---+ - - - +---+---+---+---+ + | real value | 1 | 1 | 1 | + +---+ - - - +---+---+---+---+ + n-1 3 2 1 0 + + - Percentages: + + +---+ - - - +---+---+---+---+ + | real value | 0 | 1 | 0 | + +---+ - - - +---+---+---+---+ + n-1 3 2 1 0 + + - Relative lengths: + + +---+ - - - +---+---+---+---+ + | real value | 1 | 1 | 0 | + +---+ - - - +---+---+---+---+ + n-1 3 2 1 0 + +A "real value" is a fixed point number consisting of (m is the number +of bits of the value, not the whole integer): + + +---+ - - - +---+---+ - - - +---+ + | integer part | rest | + +---+ - - - +---+---+ - - - +---+ + m 16 15 0 + +For *internal* use, there are two converting macros, +DW_STYLE_REAL_TO_FLOAT and DW_STYLE_FLOAT_TO_REAL. + + +DwStyle Boxes +============= + +The CSS Box Model +----------------- +For borders, margins etc., DwStyle uses the box model defined by +CSS2. DwStyle contains some members defining these attributes. A +widget must use these values for any calculation of sizes. There are +some helper functions (see dw_style.h). A DwStyle box looks quite +similar to a CSS box: + + + ,-- margin.left + | ,-- border.left + | | ,-- padding.left + |---+---+---| + +---------------------------------------+ --- + | | | margin.top + | +-------------------------------+ | -+- + | | Border | | | border.top + | | +-----------------------+ | | -+- + | | | Padding | | | | padding.top + new widget | | | +---------------+ | | | --- + allocation -->| | | | | | | | + | | | | Content | | | | + former widget ------------>| | | | | + allocation | | | +---------------+ | | | --- + | | | | | | | margin.bottom + | | +-----------------------+ | | -+- + | | | | | border.bottom + | +-------------------------------+ | -+- + | | | padding.bottom + +---------------------------------------+ --- + |---+---+---| + padding.right --' | | + border.right --' | + margin.right --' + +Background colors +----------------- +The background color is stored in style->background_color, which be +NULL (the background color of the parent widget is shining through). + +For toplevel widgets, this color is set as the background color of the +viewport, for other widgets, a filled rectangle is drawn, covering the +content and padding. (This is compliant with CSS2, the background +color of the toplevel element covers the whole canvas.) + +Drawing +------- +There is a new function Dw_widget_draw_widget_box, which should be +called at the beginning of Dw_foo_draw. For parts referring to styles +(e.g., words in a page), Dw_widget_draw_box should be used. + + +Notes on Memory Management +========================== + +Memory management is done by reference counting, a_Dw_style_new +returns a pointer to DwStyle with an increased reference counter, so +you should care about calling Dw_style_unref if it is not used +anymore. You do *not* need to care about the reference counters of +fonts and styles. + +In detail: + + - a_Dw_style_ref is called in + + * a_Dw_widget_set_style, to assign a style to a widget, + + * a_Dw_page_add_text, a_Dw_page_add_widget, + a_Dw_page_add_anchor, to assign a style to a word, + + * and Html_push_tag (often the reference counter is again + decreased shortly after this). + + - a_Dw_unref_style is called in: + + * Dw_page_destroy, Dw_widget_destroy, Html_cleanup_tag, + Html_pop_tag, Html_close, + + * a_Dw_widget_set_style, Html_set_top_font (and several + Html_tag_open_... functions), these functions overwrite an + existing style. + + +HTML Stack +========== + +(This is not DwStyle specific, but may be useful if you are working on +the HTML parser.) + +The topmost element of the HTML stack contains a (reference to a) +style which will be used to add the text to the current page. If you +use a style only for a short while (see Html_tag_open_frame for an +example), you may use it this way: + + style_attrs = *html->stack[html->stack_top].style; + style_attrs.foo = bar; + style = a_Dw_style_new (&style_attrs, random_window); + +Do not forget to unref it afterwards. A good choice for random_window +is html->bw->main_window->window. + +In many cases, you want to set the style for the content of an element +(e.g., <A>). Then you must store it in the stack: + + DwStyle style_attrs, *old_style; + + old_style = html->stack[html->stack_top].style; + style_attrs = *old_style; + style_attrs.foo = bar; + html->stack[html->stack_top].style = + a_Dw_style_new (&style_attrs, random_window); + a_Dw_style_unref (old_style); + +The macro HTML_SET_TOP_ATTR can be used for single attributes, for +changing more attributes, this code should be copied for efficiency. diff --git a/doc/DwTable.txt b/doc/DwTable.txt new file mode 100644 index 00000000..07c06180 --- /dev/null +++ b/doc/DwTable.txt @@ -0,0 +1,205 @@ +May 2001, S.Geerken@ping.de +Last update: Dec 2004 + +======= +DwTable +======= + +A container widget for rendering tables. + + +The DwTable Widget +================== + +DwTable is a container widget for rendering tables. It aligns other +DwWidgets (normally DwPage), according to the following rules: + + 1. All columns have have the same width W, except: + + - W is less than the minimal column width, or + - W is greater than the maximal column width. + + Furthermore, W is + + - less than all minimal widths of columns not having W as + width, and + - greater than all maximal widths of columns not having W as + width. + + 2. The table tries to use exactly the whole available width, except + if it is not possible, because the it is less/greater than the + minimal/maximal table width. + +This is simple to implement for columns with COLSPAN == 1, using +a_Dw_get_extremes for getting the minimal and maximal widths. For +arbitrary COLSPAN values, an approach described in "Subtables" is +used to get optimal results (as described above) in most cases, while +the rendering remains fast. + + +Subtables +========= + +A table is divided into subtables, which do not (in most cases) share +spanning cells, until single columns are left. Cells spanning the +whole width are removed before dividing further. Example: + + +---+-------+---+ + | A | B | C | + +---+-------+---+ + | D | E | + +---+-------+---+ + | F | G | H | + +---+-------+---+ + ' ' ` ` + ' ' ` ` + +---+-------+ +---+ + | A | B | | C | + +---+-------+ +---+ + removed --> | D | | E | + +---+-------+ +---+ + | F | G | | H | + +---+-------+ +---+ + ' ' ` ` final + ' ' ` ` + +---+ +-------+ + | A | | B | <-. + +---+ +-------+ >- removed + | F | | G | <-' + +---+ +-------+ + final ' ' ` ` + ' ' ` ` + [empty] [empty] + final final + +There is a structure, DwTableSub, for holding all the information. It +is rebuilt when new cells are added. Do not confuse this with nested +tables, these are represented by the Dw widget hierarchy. + +If table cells overlap horizontally, they are (virtually) divided. The +minimal and maximal widths are apportioned to the other columns +(resulting in a non optimal layout): + + +-------+---+---+ + | A | B | C | + +---+---+---+---+ + | D | E | + +---+-----------+ + ' ' ` ` + ' ' ` ` + +-------+ +---+---+ + | A | | B | C | + +---+---+ +---+---+ + | D |1/3| | 2/3 E | + | | E | | | + +---+---+ +-------+ + +Example for a non-optimal case +------------------------------ +The HTML document fragment + + <table> + <tr> + <td colspan="2">Text + <td>LongText + <tr> + <td>Text + <td colspan="2">LongText + </table> + +will result in: + + | 0 | 1 | 2 | + + +------------+----------+ + | Text | LongText | + +------+-----+----------+ + | Text | LongText | + +------+----------------+ + +The width of column 1 is determined by the half of the minimal width +of the LongText. An optimal rendering would be something like: + + ,- 1 + | 0 || 2 | + + +-------+----------+ + | Text | LongText | + +------++----------+ + | Text | LongText | + +------+-----------+ + + +Algorithms +========== + +Calculating extremes +-------------------- +The extremes of all subtables are calculated by +Dw_table_sub_get_extremes and stored in DwTableSub: + + minimal/maximal width (sub table) = + - for single column: maximum of all minimal/maximal widths + - otherwise: maximum of + 1. all minimal/maximal widths of cells spanning + the whole width, and + 2. the sum of the minimal/maximal widths of the + sub-subtables + + In step 1, the width argument is used to adjust the maximum + and minimum width of the whole subtable and mark it as fixed. + +todo: describe percentages. + +Calculating column widths +------------------------- +The calculation is based on a fixed width, which is, at the top, the +width set by a_Dw_widget_set_width. This is corrected by the minimal and +maximal width of the whole table, and, if given, the width attribute +of the table. At each level, the available width is always between the +minimal and the maximal width of the subtable. + +For single columns, the width is the passed fixed width. Otherwise: + + 1. Calculate relative widths, they effect the minimal and maximal + widths. (Temporally, not permanently!) + + 2. The sum of these corrected minima may be greater as the fixed + width of the subtable. In this case, decrease them again to + match exactly the fixed width, then use them as sub-subtable + fixed widths and finish. Otherwise, continue: + + 3. If the extremes of the spanning widths of the subtable are + greater than the sum of sub-subtables extremes, adjust the + extremes of sub-subtables which are not fixed, i.e., where no + width argument (either percentage or fixed) freezes the width. + + 4. Use an iteration on the subtables, to determine the column + widths, see Dw_table_sub_calc_col_widths for details. + + 5. After this, apply this recursively on all subtables and pass the + subtable width as fixed width. + + +Borders, Paddings, Spacing +========================== + +Currently, DwTable supports only the separated borders model (see CSS +specification). Borders, paddings, spacing is done by creating DwStyle +structures with values equivalent to following CSS: + + TABLE { + border: outset <table-border>; + border-collapse: separate; + border-spacing: <table-cellspacing> + background-color: <table-bgcolor> + } + + TD TH { + border: inset <table-border>; + padding: <table-cellspacing> + background-color: <td/th-bgcolor> + } + +Here, <foo-bar> refers to the attribute bar of the tag foo. See +Html_open_table and Html_open_table_cell for more details. diff --git a/doc/DwWidget.txt b/doc/DwWidget.txt new file mode 100644 index 00000000..fed0a3be --- /dev/null +++ b/doc/DwWidget.txt @@ -0,0 +1,339 @@ +Jan 2001, S.Geerken@ping.de +Last update: Dec 2004 + +======== +DwWidget +======== + +The base object for all Dw widgets. + + +Structures +========== + +DwRectangle +----------- +A replacement for GdkRectangle, the only difference is the use of 32 +instead of 16 bit integers. + + +DwAllocation, DwRequisition +--------------------------- +Similar to GtkAllocation and GtkRequisition. Instead of a height, you +have two members, ascent and descent. + + +DwExtremes +---------- +A structure containing the minimal and maximal width of a widget. See +get_extremes below for details. + + +DwWidget +-------- +Some members you may use: + + parent The parent widget. NULL for toplevel widgets. + + flags See below. + + style The style attached to the widget, this must + always be defined, but is not created + automatically. Instead, this has to be done + immediately after creating the widget + (e.g., in a_Web_dispatch_by_type). This style + contains attributes and resources for general + drawing. See DwStyle.txt for details. + +These members should only be used within the "core" (GtkDw*, DwWidget +and DwEmbedGtk): + + viewport The viewport containing the widget. Defined + for all widgets. + + allocation, + requisition, + extremes, + user_requisition These are used to optimize several wrappers, + see below. + + anchors_table See notes on achors. + +Flags +----- +Flags can be set and unset with DW_WIDGET_SET_FLAGS and +DW_WIDGET_UNSET_FLAGS. For reading flags use the macros DW_WIDGET_... + + DW_NEEDS_RESIZE + DW_EXTREMES_CHANGED Denotes that the widget must be resized. Used + only internally. See Dw.txt and the source + for more details. + + DW_NEEDS_ALLOCATE Set to overide the optimation in + a_Dw_widget_size_allocate. Used only + internally. + + DW_REALIZED Set when the widget is realized. Should be + used to prevent crashes in certain + situations. + +Following flags describe characteristics of widgets and are typically +set in the init function: + + DW_USES_HINTS A widget with this flag set has a complex way + to deal with sizes, and should + + - implement the functions set_width, + set_ascent, set_descent, and + get_extremes, and + - deal completely with width and height + in widget->style. + + Examples are DwPage and DwTable. Other + widgets, like DwImage and DwHRuler, are much + simpler and don't have to set this flag. For + these widgets, much of the size calculation + is done by the parent widget. + + This flag is unset by default. + + DW_HAS_CONTENT If this flag is set, more space is reserved + for the widget in some circumstances. E.g., + if an image has a width of 100%, it makes + sense to use more space within a table cell, + as compared to a horizontal ruler, which does + not "have a content". + + This flag is set by default. + + +Signal Prototypes +================= + + - void size_request (DwWidget *widget, + DwRequisition *requisition); + + Similar to Gtk. + + void get_extremes (DwWidget *widget, + DwExtremes *extremes); + + Return the maximal and minimal width of the widget, equivalent + to the requisition width after calling set_width with zero and + infinitive, respectively. This is important for fast table + rendering. Simple widgets do not have to implement this; the + default is the requisition width for both values. + + - void size_allocate (DwWidget *widget, + DwAllocation *allocation); + + Similar in Gtk. Note: allocation has world coordinates. + + - void set_width (DwWidget *widget, + guint32 width); + + - void set_height (DwWidget *widget, + guint32 height); + + These are hints by the caller, which *may* influence the size + returned by size_request. The implementation should call + Dw_widget_queue_resize if necessary. In most cases, these + signals do not have to be implemented. Currently, only the + DwPage widget uses this to determine the width for rewrapping + text (note that the resulting width returned by + Dw_page_size_request may be _bigger_) and relative sizes of the + children. + + - void draw (DwWidget *widget, + DwRectangle *area, + GdkEventExpose *event); + + Draw the widget's content in the specified area. It may either + be caused by an expose event, or by an internal drawing request + (e.g., followed by resizing of widgets). In the first case, you + get the *original* expose event as third argument. In the + latter, event is NULL. The area argument has widget + coordinates. A DwContainer is responsible for drawing its + children. + + (Since DwWidget's are always windowless, there was no need for + two signals, "draw" and "expose_event".) + + - void realize (DwWidget *widget); + + Create all necessary X resources. Called when either the + viewport (top-level widgets) or, respectively, the parent Dw + widget is realized, or an widget is added to an already + realized Dw widget/viewport. + + - void unrealize (DwWidget *widget); + + Remove created X resources. + + - gint button_press_event (DwWidget *widget, + guint32 x, + guint32 y, + GdkEventButton *event); + + This signal is emitted when the user presses a mouse button in + a DwWidget. x and y are the coordinates relative to the origin + of the widget, event is the *original* event, which may, e.g., + be used to determine the number of the pressed button, the state + of the shift keys, etc. The implementation of this signal + should return TRUE, if the event has been processed, otherwise + FALSE. + + A DwContainer is *not* responsible for delivering button press + events to its children. Instead, Dw first emits the + button_press_event signal for the most inner widgets and + continues this for the parents, until TRUE is returned. + + - gint button_release_event (DwWidget *widget, + guint32 x, + guint32 y, + GdkEventButton *event); + + Compare button_press_event. + + - gint motion_notify_event (DwWidget *widget, + guint32 x, + guint32 y, + GdkEventMotion *event); + + Compare button_press_event. event may be NULL when the call was + caused by something different than a "real" motion notify event. + E.g., either when widgets are moved (not yet implemented), or the + viewport. + + - gint enter_notify_event (DwWidget *widget, + DwWidget *last_widget, + GdkEventMotion *event); + + These "events" are simulated based on motion nofify events. + event is the *original* event (may also be NULL in some cases). + last_widget is the widget in which the pointer was in before. + + - gint leave_notify_event (DwWidget *widget, + DwWidget *next_widget, + GdkEventMotion *event); + + Compare enter_notify_event. next_widget is the widget the + pointer is now in. + + +Useful Internal Functions and Macros +==================================== + + - gint Dw_widget_intersect (DwWidget *widget, + DwRectangle *area, + DwRectangle *intersection); + + Calculates the intersection of widget->allocation and area, + returned in intersection (in widget coordinates!). Typically + used by containers when drawing their children. Returns whether + intersection is not empty. + + - gint32 Dw_widget_x_viewport_to_world (DwWidget *widget, + gint16 viewport_x); + + - gint32 Dw_widget_y_viewport_to_world (DwWidget *widget, + gint16 viewport_y); + + - gint16 Dw_widget_x_world_to_viewport (DwWidget *widget, + gint32 world_x); + + - gint16 Dw_widget_y_world_to_viewport (DwWidget *widget, + gint32 world_y); + + These functions convert between world and viewport coordinates. + + - void Dw_widget_queue_draw (DwWidget *widget); + + - void Dw_widget_queue_draw_area (DwWidget *widget, + gint32 x, + gint32 y, + guint32 width, + guint32 height); + + - void Dw_widget_queue_clear (DwWidget *widget); + + - void Dw_widget_queue_clear_area (DwWidget *widget, + gint32 x, + gint32 y, + guint32 width, + guint32 height); + + Equivalents to the Gtk+ functions. They (currently) result in a + call of gtk_widget_xxx_area with the viewport as first + argument. x and y are widget coordinates. + + - void Dw_widget_queue_resize (DwWidget *widget, + gint ref, + gboolean extremes_changed); + + Similar to gtk_widget_queue_resize. Call this function when the + widget has changed its size. The next call to + Dw_xxx_size_request should then return the new size. + + See Dw.txt for explanation on how to use the ref argument, + extremes_changed specifies whether the extremes have changed + (the latter is often not the case for an implementations of + set_{width|ascent|descent}). + + - void Dw_widget_set_anchor (DwWidget *widget, + gchar *name, + int pos); + + Add an anchor to a widget. The name will not be copied, it has + to be stored elsewhere (DwPage e.g. stores it in the DwPageWord + structure). + + - void a_Dw_widget_set_cursor (DwWidget *widget, + GdkCursor *cursor) + + Set the cursor for a DwWidget. cursor has to be stored + elsewhere, it is not copied (and not destroyed). If cursor is + NULL, the cursor of the parent widget is used. + + (This will probably be changed in the future and replaced by a + common style mechanism.) + + - DW_WIDGET_WINDOW (widget) + + Returns the window a widget should draw into. + + +External Functions +================== + + - void a_Dw_widget_set_usize (DwWidget *widget, + guint32 width, + guint32 ascent, + guint32 descent); + + Override the "desired" size of a widget. Further calls of + a_Dw_widget_request_size will return these values, except those + specified as -1. A possible use shows Html_add_widget in + html.c. + + (This will probably be removed. Instead DwStyle should be used.) + + + - void a_Dw_widget_set_bg_color (DwWidget *widget, + gint32 color); + + Set the background color of a widget. This works currently only + for the top-level widget. In this case, the background color of + the GtkDwViewport is changed. In future, background colors for + all widgets will be needed, e.g., for table cells (will be + DwPage's), this will (probably) be based on filled rectangles. + + - void a_Dw_widget_scroll_to (DwWidget *widget, + int pos) + + Scroll viewport to pos (vertical widget coordinate). + +There are furthermore wrappers for the signals, in some cases they +are optimized and/or provide further functionality. In (almost) no +case should you emit the signals directly. See dw_widget.c for more +details. diff --git a/doc/HtmlParser.txt b/doc/HtmlParser.txt new file mode 100644 index 00000000..ec64164d --- /dev/null +++ b/doc/HtmlParser.txt @@ -0,0 +1,116 @@ + October 2001, --Jcid + Last update: Dec 2004 + + --------------- + THE HTML PARSER + --------------- + + + Dillo's parser is more than just a HTML parser, it does XHTML +and plain text also. It has parsing 'modes' that define its +behaviour while working: + + typedef enum { + DILLO_HTML_PARSE_MODE_INIT, + DILLO_HTML_PARSE_MODE_STASH, + DILLO_HTML_PARSE_MODE_STASH_AND_BODY, + DILLO_HTML_PARSE_MODE_BODY, + DILLO_HTML_PARSE_MODE_VERBATIM, + DILLO_HTML_PARSE_MODE_PRE + } DilloHtmlParseMode; + + + The parser works upon a token-grained basis, i.e., the data +stream is parsed into tokens and the parser is fed with them. The +process is simple: whenever the cache has new data, it gets +passed to Html_write, which groups data into tokens and calls the +appropriate functions for the token type (TAG, SPACE or WORD). + + Note: when in DILLO_HTML_PARSE_MODE_VERBATIM, the parser +doesn't try to split the data stream into tokens anymore, it +simply collects until the closing tag. + +------ +TOKENS +------ + + * A chunk of WHITE SPACE --> Html_process_space + + + * TAG --> Html_process_tag + + The tag-start is defined by two adjacent characters: + + first : '<' + second: ALPHA | '/' | '!' | '?' + + Note: comments are discarded ( <!-- ... --> ) + + + The tag's end is not as easy to find, nor to deal with!: + + 1) The HTML 4.01 sec. 3.2.2 states that "Attribute/value + pairs appear before the final '>' of an element's start tag", + but it doesn't define how to discriminate the "final" '>'. + + 2) '<' and '>' should be escaped as '<' and '>' inside + attribute values. + + 3) The XML SPEC for XHTML states: + AttrValue ::== '"' ([^<&"] | Reference)* '"' | + "'" ([^<&'] | Reference)* "'" + + Current parser honors the XML SPEC. + + As it's a common mistake for human authors to mistype or + forget one of the quote marks of an attribute value; the + parser solves the problem with a look-ahead technique + (otherwise the parser could skip significative amounts of + well written HTML). + + + + * WORD --> Html_process_word + + A word is anything that doesn't start with SPACE, and that's + outside of a tag, up to the first SPACE or tag start. + + SPACE = ' ' | \n | \r | \t | \f | \v + + +----------------- +THE PARSING STACK +----------------- + + The parsing state of the document is kept in a stack: + + struct _DilloHtml { + [...] + DilloHtmlState *stack; + gint stack_top; /* Index to the top of the stack [0 based] */ + gint stack_max; + [...] + }; + + struct _DilloHtmlState { + char *tag; + DwStyle *style, *table_cell_style; + DilloHtmlParseMode parse_mode; + DilloHtmlTableMode table_mode; + gint list_level; + gint list_number; + DwWidget *page, *table; + gint32 current_bg_color; + }; + + + Basically, when a TAG is processed, a new state is pushed into +the 'stack' and its 'style' is set to reflect the desired +appearance (details in DwStyle.txt). + + That way, when a word is processed later (added to the Dw), all +the information is within the top state. + + Closing TAGs just pop the stack. + + diff --git a/doc/IO.txt b/doc/IO.txt new file mode 100644 index 00000000..fb691223 --- /dev/null +++ b/doc/IO.txt @@ -0,0 +1,468 @@ + +This is the updated base of a paper I wrote with Horst. +It provides a good introduction to Dillo's internals. +(Highly recommended if you plan to patch or develop in Dillo) + +It may not be exactly accurate (it's quite old), but it explains +the theory behind in some detail, so it's more than recommended +reading material. +--Jcid + + +----------------------------------------------------- +Parallel network programming of the Dillo web browser +----------------------------------------------------- + + Jorge Arellano-Cid <jcid@dillo.org> + Horst H. von Brand <vonbrand@inf.utfsm.cl> + + +-------- +Abstract +-------- + + Network programs face several delay sources when sending or +retrieving data. This is particularly problematic in programs +which interact directly with the user, most notably web browsers. +We present a hybrid approach using threads communicated through +pipes and signal driven I/O, which allows a non-blocking main +thread and overlapping waiting times. + + +------------ +Introduction +------------ + + The Dillo project didn't start from scratch but mainly working +on the code base of gzilla (a light web browser written by Raph +Levien). As the project went by, the code of the whole source was +standardized, and the networking engine was replaced with a new, +faster design. Later, the parser was changed, the streamed +handling of data and its error control was put under the control +of the CCC (Concomitant Control Chain), and the old widget system +was replaced with a new one (Dw). The source code is currently +regarded as "very stable beta", and is available at +<http://www.dillo.org>. Dillo is a project licensed under the GNU +General Public License. + + This paper covers basic design aspects of the hybrid approach +that the Dillo web browser uses to solve several latency +problems. After introducing the main delay-sources, the main +points of the hybrid design will be addressed. + + +------------- +Delay sources +------------- + + Network programs face several delay-sources while sending or +retrieving data. In the particular case of a web browser, they +are found in: + + DNS querying: + The time required to solve a name. + + Initiating the TCP connection: + The three way handshake of the TCP protocol. + + Sending the query: + The time spent uploading queries to the remote server. + + Retrieving data: + The time spent expecting and receiving the query answer. + + Closing the TCP connection: + The four packet-sending closing sequence of the TCP protocol. + + In a WAN context, every single item of this list has an +associated delay that is non deterministic and often measured in +seconds. If we add several connections per browsed page (each one +requiring at least the 4 last steps), the total latency can be +considerable. + + +----------------------------------- +The traditional (blocking) approach +----------------------------------- + + The main problems with the blocking approach are: + + When issuing an operation that can't be completed + immediately, the process is put to sleep waiting for + completion, and the program doesn't do any other + processing in the meantime. + + When waiting for a specific socket operation to complete, + packets that belong to other connections may be arriving, + and have to wait for service. + + Web browsers handle many small transactions, + if waiting times are not overlapped + the latency perceived by the user can be very annoying. + + If the user interface is just put to sleep during network + operations, the program becomes unresponsive, confusing + and perhaps alarming the user. + + Not overlapping waiting times and processing makes + graphical rendering (which is arguably the central function + of a browser) unnecessarily slow. + + +--------------------- +Dillo's hybrid design +--------------------- + + Dillo uses threads and signal driven I/O extensively to +overlap waiting times and computation. Handling the user +interface in a thread that never blocks gives a good interactive +``feel.'' The use of GTK+, a sophisticated widget framework for +graphical user interfaces, helped very much to accomplish this +goal. All the interface, rendering and I/O engine was built upon +its facilities. + + The design is said to be ``hybrid'' because it uses threads +for DNS querying and reading local files, and signal driven I/O +for TCP connections. The threaded DNS scheme is potentially +concurrent (this depends on underlying hardware), while the I/O +handling (both local files and remote connections) is +definitively parallel. + + To simplify the structure of the browser, local files are +encapsulated into HTTP streams and presented to the rest of the +browser as such, in exactly the same way a remote connection is +handled. To create this illusion, a thread is launched. This +thread opens a pipe to the browser, it then synthesizes an +appropriate HTTP header, sends it together with the file to the +browser proper. In this way, all the browser sees is a handle, +the data on it can come from a remote connection or from a local +file. + + To handle a remote connection is more complex. In this case, +the browser asks the cache manager for the URL. The name in the +URL has to be resolved through the DNS engine, a socket TCP +connection must be established, the HTTP request has to be sent, +and finally the result retrieved. Each of the steps mentioned +could give rise to errors, which have to be handled and somehow +communicated to the rest of the program. For performance reasons, +it is critical that responses are cached locally, so the remote +connection doesn't directly hand over the data to the browser; +the response is passed to the cache manager which then relays it +to the rest of the browser. The DNS engine caches DNS responses, +and either answers them from the cache or by querying the DNS. +Querying is done in a separate thread, so that the rest of the +browser isn't blocked by long waits here. + + The activities mentioned do not happen strictly in the order +stated above. It is even possible that several URLs are being +handled at the same time, in order to overlap waiting and +downloading. The functions called directly from the user +interface have to return quickly to maintain interactive +response. Sometimes they return connection handlers that haven't +been completely set up yet. As stated, I/O is signal-driven, when +one of the descriptors is ready for data transfer (reading or +writing), it wakes up the I/O engine. + + Data transfer between threads inside the browser is handled by +pipes, shared memory is little used. This almost obviates the +need for explicit synchronization, which is one of the main areas +of complexity and bugs in concurrent programs. Dillo handles its +threads in a way that its developers can think of it as running +on a single thread of control. This is accomplished by making the +DNS engine call-backs happen within the main thread, and by +isolating file loading with pipes. + + Using threads in this way has three big advantages: + + The browser doesn't block when one of its child threads + blocks. In particular, the user interface is responsive + even while resolving a name or downloading a file. + + Developers don't need to deal with complex concurrent + concerns. Concurrency is hard to handle, and few developers + are adept at this. This gives access a much larger pool of + potential developers, something which can be critical + in an open-source development project. + + By making the code mostly sequential, debugging the code + with traditional tools like gdb is possible. Debugging + parallel programs is very hard, and appropriate tools are + hard to come by. + + Because of simplicity and portability concerns, DNS querying +is done in a separate thread. The standard C library doesn't +provide a function for making DNS queries that don't block. The +alternative is to implement a new, custom DNS querying function +that doesn't block. This is certainly a complex task, integrating +this mechanism into the thread structure of the program is much +simpler. + + Using a thread and a pipe to read a local file adds a +buffering step to the process (and a certain latency), but it has +a couple of significative advantages: + + By handling local files in the same way as remote + connections, a significant amount of code is reused. + + A preprocessing step of the file data can be added easily, + if needed. In fact, the file is encapsulated into an HTTP + data stream. + + +----------- +DNS queries +----------- + + Dillo handles DNS queries with threads, letting a child thread +wait until the DNS server answers the request. When the answer +arrives, a call-back function is called, and the program resumes +what it was doing at DNS-request time. The interesting thing is +that the call-back happens in the main thread, while the child +thread simply exits when done. This is implemented through a +server-channel design. + + +The server channel +------------------ + + There is one thread for each channel, and each channel can +have multiple clients. When the program requests an IP address, +the server first looks for a cached match; if it hits, the client +call-back is invoked immediately, but if not, the client is put +into a queue, a thread is spawned to query the DNS, and a GTK+ +idle client is set to poll the channel 5~times per second for +completion, and when it finally succeeds, every client of that +channel is serviced. + + This scheme allows all the further processing to continue on +the same thread it began: the main thread. + + +------------------------ +Handling TCP connections +------------------------ + + Establishing a TCP connection requires the well known +three-way handshake packet-sending sequence. Depending on network +traffic and several other issues, significant delay can occur at +this phase. + + Dillo handles the connection by a non blocking socket scheme. +Basically, a socket file descriptor of AF_INET type is requested +and set to non-blocking I/O. When the DNS server has resolved the +name, the socket connection process begins by calling connect(2); + {We use the Unix convention of identifying the manual section + where the concept is described, in this case + section 2 (system calls).} +which returns immediately with an EINPROGRESS error. + + After the connection reaches the EINPROGRESS ``state,'' the +socket waits in background until connection succeeds (or fails), +when that happens, a callback function is awaked to perform the +following steps: set the I/O engine to send the query and expect +its answer (both in background). + + The advantage of this scheme is that every required step is +quickly done without blocking the browser. Finally, the socket +will generate a signal whenever I/O is possible. + + +---------------- +Handling queries +---------------- + + In the case of a HTTP URL, queries typically translate into a +short transmission (the HTTP query) and a lengthy retrieval +process. Queries are not always short though, specially when +requesting forms (all the form data is attached within the +query), and also when requesting CGI programs. + + Regardless of query length, query sending is handled in +background. The thread that was initiated at TCP connecting time +has all the transmission framework already set up; at this point, +packet sending is just a matter of waiting for the +write signal (G_IO_OUT) to come and then sending the data. When +the socket gets ready for transmission, the data is sent using +IO_write. + + +-------------- +Receiving data +-------------- + + Although conceptually similar to sending queries, retrieving +data is very different as the data received can easily exceed the +size of the query by many orders of magnitude (for example when +downloading images or files). This is one of the main sources of +latency, the retrieval can take several seconds or even minutes +when downloading large files. + + The data retrieving process for a single file, that began by +setting up the expecting framework at TCP connecting time, simply +waits for the read signal (G_IO_IN). When it happens, the +low-level I/O engine gets called, the data is read into +pre-allocated buffers and the appropriate call-backs are +performed. Technically, whenever a G_IO_IN event is generated, +data is received from the socket file descriptor, by using the +IO_read function. This iterative process finishes upon EOF (or on +an error condition). + + +---------------------- +Closing the connection +---------------------- + + Closing a TCP connection requires four data segments, not an +impressive amount but twice the round trip time, which can be +substantial. When data retrieval finishes, socket closing is +triggered. There's nothing but a IO_close_fd call on the socket's +file descriptor. This process was originally designed to split +the four segment close into two partial closes, one when query +sending is done and the other when all data is in. This scheme is +not currently used because the write close also stops the reading +part. + + +The low-level I/O engine +------------------------ + + Dillo I/O is carried out in the background. This is achieved +by using low level file descriptors and signals. Anytime a file +descriptor shows activity, a signal is raised and the signal +handler takes care of the I/O. + + The low-level I/O engine ("I/O engine" from here on) was +designed as an internal abstraction layer for background file +descriptor activity. It is intended to be used by the cache +module only; higher level routines should ask the cache for its +URLs. Every operation that is meant to be carried out in +background should be handled by the I/O engine. In the case of +TCP sockets, they are created and submitted to the I/O engine for +any further processing. + + The submitting process (client) must fill a request structure +and let the I/O engine handle the file descriptor activity, until +it receives a call-back for finally processing the data. This is +better understood by examining the request structure: + + typedef struct { + gint Key; /* Primary Key (for klist) */ + gint Op; /* IORead | IOWrite | IOWrites */ + gint FD; /* Current File Descriptor */ + gint Flags; /* Flag array */ + glong Status; /* Number of bytes read, or -errno code */ + + void *Buf; /* Buffer place */ + size_t BufSize; /* Buffer length */ + void *BufStart; /* PRIVATE: only used inside IO.c! */ + + void *ExtData; /* External data reference (not used by IO.c) */ + void *Info; /* CCC Info structure for this IO */ + GIOChannel *GioCh; /* IO channel */ + guint watch_id; /* glib's event source id */ + } IOData_t; + + To request an I/O operation, this structure must be filled and +passed to the I/O engine. + + 'Op' and 'Buf' and 'BufSize' MUST be provided. + + 'ExtData' MAY be provided. + + 'Status', 'FD' and 'GioCh' are set by I/O engine internal +routines. + + When there is new data in the file descriptor, 'IO_callback' +gets called (by glib). Only after the I/O engine finishes +processing the data are the upper layers notified. + + +The I/O engine transfer buffer +------------------------------ + + The 'Buf' and 'BufSize' fields of the request structure +provide the transfer buffer for each operation. This buffer must +be set by the client (to increase performance by avoiding copying +data). + + On reads, the client specifies the amount and where to place +the retrieved data; on writes, it specifies the amount and source +of the data segment that is to be sent. Although this scheme +increases complexity, it has proven very fast and powerful. For +instance, when the size of a document is known in advance, a +buffer for all the data can be allocated at once, eliminating the +need for multiple memory reallocations. Even more, if the size is +known and the data transfer is taking the form of multiple small +chunks of data, the client only needs to update 'Buf' and +BufSize' to point to the next byte in its large preallocated +reception buffer (by adding the chunk size to 'Buf'). On the +other hand, if the size of the transfer isn't known in advance, +the reception buffer can remain untouched until the connection +closes, but the client must then accomplish the usual buffer +copying and reallocation. + + The I/O engine also lets the client specify a full length +transfer buffer when sending data. It doesn't matter (from the +client's point of view) if the data fits in a single packet or +not, it's the I/O engine's job to divide it into smaller chunks +if needed and to perform the operation accordingly. + + +------------------------------------------ +Handling multiple simultaneous connections +------------------------------------------ + + The previous sections describe the internal work for a single +connection, the I/O engine handles several of them in parallel. +This is the normal downloading behavior of a web page. Normally, +after retrieving the main document (HTML code), several +references to other files (typically images) and sometimes even +to other sites (mostly advertising today) are found inside the +page. In order to parse and complete the page rendering, those +other documents must be fetched and displayed, so it is not +uncommon to have multiple downloading connections (every one +requiring the whole fetching process) happening at the same time. + + Even though socket activity can reach a hectic pace, the +browser never blocks. Note also that the I/O engine is the one +that directs the execution flow of the program by triggering a +call-back chain whenever a file descriptor operation succeeds or +fails. + + A key point for this multiple call-back chained I/O engine is +that every single function in the chain must be guaranteed to +return quickly. Otherwise, the whole system blocks until it +returns. + + +----------- +Conclusions +----------- + + Dillo is currently in very stable beta state. It already shows +impressive performance, and its interactive ``feel'' is much +better than that of other web browsers. + + The modular structure of Dillo, and its reliance on GTK1 allow +it to be very small. Not every feature of HTML-4.01 has been +implemented yet, but no significant problems are foreseen in +doing this. + + The fact that Dillo's central I/O engine is written using +advanced features of POSIX and TCP/IP networking makes its +performance possible, but on the other hand this also means that +only a fraction of the interested hackers are able to work on it. + + A simple code base is critical when trying to attract hackers +to work on a project like this one. Using the GTK+ framework +helped both in creating the graphical user interface and in +handling the concurrency inside the browser. By having threads +communicate through pipes the need for explicit synchronization +is almost completely eliminated, and with it most of the +complexity of concurrent programming disappears. + + A clean, strictly applied layering approach based on clear +abstractions is vital in each programming project. A good, +supportive framework is of much help here. + + diff --git a/doc/Images.txt b/doc/Images.txt new file mode 100644 index 00000000..1f70b0ca --- /dev/null +++ b/doc/Images.txt @@ -0,0 +1,114 @@ + February 2001, --Jcid + + ------ + IMAGES + ------ + +* When a image tag is found within a HTML page, Html_tag_open_img +handles it by: + + - Parsing & getting attribute values. + - Creating a new image structure (DilloImage) and its + associated widget (DwImage). + i.e. If 'Image' is the var for the structure, then + 'Image->dw' is the widget. + - Requesting the image to be feeded by the cache. + - Sending some info to the browser interface. + +* The cache can either request the image data from the net, or +feed it directly from the dicache (decompressed image cache). + +* Both processes are somewhat different because the first one +requires to decode the image data into RGB format, and the second +one has the whole data already decoded. + +* Regardless of the RGB-data feeding method, the decoded data is +passed to the widget (DwImage) and drawn by GdkRGB in a streamed +way. + Note that INDEXED images are also decoded into RGB format. + + +--------------------- +Fetching from the net +--------------------- + +* a_Cache_open_url initiates the resource request, and when +finally the answer arrives, the HTTP header is examined for MIME +type and either the GIF or PNG or JPEG decoder is set to handle +the incoming data stream. + Decoding functions: a_Gif_image, a_Jpeg_image and a_Png_image. + +* The decoding function calls the following dicache methods as +the data is processed (listed in order): + + a_Dicache_set_parms + a_Dicache_set_cmap (only for indexed-GIF images) + a_Dicache_write + a_Dicache_close + +* The dicache methods call the necessary functions to connect +with the widget code. This is done by calling image.c functions: + + a_Image_set_parms + a_Image_set_cmap + a_Image_write + a_Image_close + +* The functions in image.c make the required a_Dw_image_... +calls. + + +------------------------- +Fetching from the dicache +------------------------- + +* a_Cache_open_url tests the cache for the image, and directly +enqueues a cache client for it, without asking the network for +the data. When the client queue is processed (a bit later), the +decoder is selected based on the cache entry Type. + +* When the decoder is called, it tests the dicache for the image; +if the image is found, then it sets a_Dicache_callback as the +handling function and gets out of the way (no image decoding is +needed). + +* Later on, the DwImage buffer is set to reference the +dicache-entry's buffer and the rest of the functions calls is +driven by a_Dicache_callback. + + +----------- +Misc. notes +----------- + +* Repeated images generate new cache clients, but only one of +them (the first) is handled with a_Dicache_* functions, the rest +is done with a_Dicache_callback.. + +* The cache-client callback is set when the Content-type of the +image is got. It can be: a_Png_image, a_Gif_image or a_Jpeg_image +Those are called 'decoders' because their main function is to +translate the original data into RGB format. + +* Later on, the decoder can substitute itself, if it finds the +image has been already decoded, with a_Dicache_callback function. +This avoids decoding it twice. + +* The dicache-entry and the Image structure hold bit arrays that +represent which rows had been decoded. + +* The image processing can be found in the following sources: + + - image.[ch] + - dicache.[ch] + - gif.[ch], png.[ch], jpeg.[ch] + - dw_image.[ch] + +* Bear in mind that there are three data structures for image +code: + + - DilloImage (image.h) + - DwImage (dw_image.h) + - DICacheEntry (dicache.h) + + diff --git a/doc/Imgbuf.txt b/doc/Imgbuf.txt new file mode 100644 index 00000000..1039ff9e --- /dev/null +++ b/doc/Imgbuf.txt @@ -0,0 +1,177 @@ +Aug 2004, S.Geerken@ping.de + +============= +Image Buffers +============= + +General +======= + +Image buffers depend on the platform (see DwRender.txt), but have a +general, platform independant interface, which is described in this +section. The next section describes the Gdk version of Imgbuf. + +The structure ImgBuf will become part of the image processing, between +image data decoding and the widget DwImage. Its purposes are + + 1. storing the image data, + 2. handling scaled versions of this buffer, and + 3. drawing. + +The latter must be done independently from the window. + +Storing Image Data +------------------ +Imgbuf supports five image types, which are listed in the table +below. The representation defines, how the colors are stored within +the data, which is passed to a_Imgbuf_copy_row(). + + | bytes per | + type | pixel | representation + ---------------+-----------+------------------------- + RGB | 3 | red, green, blue + RGBA | 4 | red, green, blue, alpha + gray | 1 | gray value + indexed | 1 | index to colormap + indexed alpha | 1 | index to colormap + +The last two types need a colormap, which is set by +a_Imgbuf_set_cmap(), which must be called before +a_Imgbuf_copy_row(). This function expects the colors as 32 bit +unsigned integers, which have the format 0xrrbbgg (for indexed +images), or 0xaarrggbb (for indexed alpha), respectively. + +Scaling +------- +The buffer with the original size, which was created by +a_Imgbuf_new(), is called root buffer. Imgbuf provides the ability to +scale buffers. Generally, both root buffers, as well as scaled +buffers, may be shared, memory management is done by reference +counters. + +Via a_Imgbuf_get_scaled_buf(), you can retrieve a scaled buffer. The +way, how this function works in detail, is described in the code, but +generally, something like this works always, in an efficient way: + + old_buf = cur_buf; + cur_buf = a_Imgbuf_get_scaled_buf(old_buf, with, height); + a_Imgbuf_unref (old_buf); + +Old_buf may both be a root buffer, or a scaled buffer. + +(As an exception, there should always be a reference on the root +buffer, since scaled buffers cannot exist without the root buffer, but +on the other side, do not hold references on it. So, if in the example +above, old_buf would be a root buffer, and there would, at the +beginning, only be one reference on it, new_buf would also be +destroyed, along with old_buf. Therefore, an external reference must +be added to the root buffer, which is in dillo done within the dicache +module.) + +The root buffer keeps a list of all children, and all operations +operating on the image data (a_Imgbuf_copy_row() and +a_Imgbuf_set_cmap()) are delegated to the scaled buffers, when +processed, and inherited, when a new scaled buffer is created. This +means, that they must only be performed for the root buffer. + +Drawing +------- +There are two situations, when drawing is necessary: + + 1. To react on expose events, the function a_Imgbuf_draw() can be + used. Notice that the exact signature of this function is + platform dependant. + + 2. When a row has been copied, it has to be drawn. To determine the + area, which has to be drawn, the function + a_Imgbuf_get_row_area() should be used. In dillo, the dicache + module will first call a_Img_copy_row(), and then call + a_Dw_image_draw_row() for the images connected to this image + buffer. a_Dw_image_draw_row() will then call + p_Dw_widget_queue_draw(), with an area determined by + a_Imgbuf_get_row_area(). + + +The Gdk Implementation +====================== + +The Gdk implementation is used by the Gtk+ platform. [... todo] + + +Global Scalers +============== + +In some cases, there is a context, where images have to be scaled +often, by a relatively constant factor. For example, the preview +window (GtkDwPreview) draws images via the Imgbuf draw functions, but +uses scaled buffers. Scaling such a buffer each time it is needed, +causes huge performance losses. On the other hand, if the preview +window would keep these scaled buffers (e.g. by lazy mapping of the +original buffer to the scaled buffer), the scaled buffers get never +freed, since the view is not told about, when the original buffer is +not needed anymore. (n.b., that currently, the scaled buffers are +destroyed, when the original buffer is destroyed, but this may change, +and even this would leave "zombies" in this mapping structure, where +the values refer to dead pointers). + +It is sufficient, that references on the scaled buffers are referred +somehow, so that they do not get destroyed between different +usages. The caller (in this case the preview) simply requests a scaled +buffer, but the Imgbuf returns this from the list of already scaled +buffers. + +These references are hold by special structures, which are called +"scalers". There are two types of scalers, local scalers, which are +bound to image buffers, and global scalers, which refer to multiple +scalers. + +What happens in different situations: + + - The caller (e.g. the preview) requests a scaled buffer. For this, + it uses a special method, which also passes the global image + scaler, which was created before (for the preview, there is a 1-1 + association). The Imgbuf uses this global image scaler, to + identify the caller, and keeps a list of them. If this global + scaler is not yet in the list, it is added, and a local scaler is + created. + + + + - + +There are three images in the page, i1a, i1b, and i2. I1a and i1b +refer to the same image recource, represented by the root image buffer +iba, which original size is 200 x 200. I1a is displayed in original +size, while i1b is displayed at 100 x 100. I2 refers to an other +recource, ibb, which has the size 300 x 300. I2 is shown in original +size. + + + :DwRenderLayout ------------------- :DwPage ----------. + / \ | + ,----' `----. ,------ i1a:DwImage --+ + / \ | | + view1:GtkDwViewport view2:GtkDwPreview | ,---- i1b:DwImage --| + | | | | + ,------------------------------' | | ,-- i2: DwImage --' + | | | | + | ,-------------------------------------' | | + | | ,--------------------------------' | + | | | ,----' + | | | | + | V | V + | iba:Imgbuf | ibb:Imgbuf -- 30x30 + | | | V | ^ + | | +- 100x100 ,- 20x20 ,- 10x10 | | + | | | | ^ | ^ | | + | | `----------+----|---' | `--. ,--' + | | ,--------------' | | | + | | | ,------------------' | | + | | | | | | + | lca:ImgbufLSc lcb:ImgbufLSc + | (factor 1/10) (factor 1/10) + | \ / + | `-----------. ,-------------------' + | \ / + `------------------> scl:ImgbufGSc + (factor 1/10) diff --git a/doc/Makefile.am b/doc/Makefile.am new file mode 100644 index 00000000..9142fa3a --- /dev/null +++ b/doc/Makefile.am @@ -0,0 +1,19 @@ +EXTRA_DIST = \ + Cache.txt \ + Cookies.txt \ + Dillo.txt \ + Dw.txt \ + DwImage.txt \ + DwPage.txt \ + DwRender.txt \ + DwStyle.txt \ + DwTable.txt \ + DwWidget.txt \ + HtmlParser.txt \ + IO.txt \ + Images.txt \ + Imgbuf.txt \ + NC_design.txt \ + Selection.txt \ + Dpid.txt \ + README diff --git a/doc/NC_design.txt b/doc/NC_design.txt new file mode 100644 index 00000000..6932b9cf --- /dev/null +++ b/doc/NC_design.txt @@ -0,0 +1,80 @@ + + _________________________________________________________________ + + Naming&Coding design + _________________________________________________________________ + + Dillo's code is divided into modules. For instance: bookmark, cache, + dicache, gif. + + Lets think of a module named "menu", then: + * Every internal routine of the module, should start with "Menu_" + prefix. + * "Menu_" prefixed functions are not meant to be called from outside + the module. + * If the function is to be exported to other modules (i.e. it will + be called from the outside), it should be wrapped with an "a_" + prefix. + + For instance: if the function name is "Menu_create", then it's an + internal function, but if we need to call it from the outside, then it + should be renamed to "a_Menu_create". + + Why the "a_" prefix? + Because of historical reasons. + And it reads better "a_Menu_create" than "d_Menu_create" cause the + first one suggests "a Menu create" function! + + Another way of understanding this is thinking of "a_" prefixed + functions as Dillo's internal library, and the rest ("Menu_" prefixed + in our example) as a private module-library. + + Indentation: Source code must be indented with 3 blank spaces, no Tabs. + Why? + Because different editors expand or treat tabs in several ways; 8 + spaces being the most common, but that makes code really wide and + we'll try to keep it within the 80 columns bounds (printer friendly). + + Function commenting: + + Every single function of the module should start with a short comment + that explains it's purpose; three lines must be enough, but if you + think it requires more, enlarge it. + +/* + * Try finding the url in the cache. If it hits, send the contents + * to the caller. If it misses, set up a new connection. + */ +int a_Cache_open_url(const char *url, void *Data) +{ + ... + ... + ... +} + + We also have the BUG: and todo: tags. + Use them within source code comments to spot hidden issues. For + instance: + +/* BUG: this counter is not accurate */ +++i; + +/* todo: get color from the right place */ +a = color; + _________________________________________________________________ + + What do we get with this? + + * A clear module API for Dillo; every function prefixed "a_" is to + be used outside the module. + * A way for identifying where the function came from (the + capitalized word is the module name). + * An inner ADT (Abstract data type) for the module. That can be + isolated, tested and replaced independently. + * A two stage instance for bug-fixing. You can change the exported + function algorithms while someone else fixes the internal + module-ADT! + * A coding standard ;) + _________________________________________________________________ + + Naming&Coding design by Jorge Arellano Cid diff --git a/doc/README b/doc/README new file mode 100644 index 00000000..3530292d --- /dev/null +++ b/doc/README @@ -0,0 +1,53 @@ +README: Last update Oct 2007 --jcid + + This documents need a review. They were current with dillo1 and +now, with dillo2, most of them are obsolete, specially Dw*txt, but +Dw2 is fully documented in html using doxygen. + + The other documents will be reviewed when I have some time. They +will give you an overview of what's going on but take them with a +pinch of salt. + + Of course I'd like to have these as doxygen files too! +If somebody wants to make this convertion, please let me know +to assign higher priority to updating these docs. + +-- +Jorge.- + + -------------------------------------------------------------------------- + FILE DESCRIPTION STATE + -------------------------------------------------------------------------- + NC_design.txt Naming&Coding design (Be sure to Current + read it before any other doc) + Dillo.txt General overview of the program Current + IO.txt Extensive introduction Current + Cache.txt Informative description Current + Images.txt Image handling and processing Current + HtmlParser.txt A versatile parser Current + Dw.txt The New Dillo Widget (Overview) Current + DwRendering.txt Dw Rendering Abstraction Current + DwWidget.txt The base object of Dw Current + DwImage.txt Dillo Widget image handling Incomplete + DwPage.txt Dillo Widget page (shortly) Incomplete + DwStyle.txt Styles of Dillo Widgets Pending + DwTable.txt Tables in dillo Current + Imgbuf.txt Image buffers Pending + Selection.txt Selections, and link activation Current (?) + Cookies.txt Explains how to enable cookies Current + Dpid.txt Dillo plugin daemon Current + -------------------------------------------------------------------------- + [This documents cover dillo's internal working. They're NOT a user manual] + -------------------------------------------------------------------------- + + + * Ah!, there's a small program (srch) within the src/ dir. It searches + tokens within the whole code (*.[ch]). It has proven very useful. + Ex: ./srch a_Image_write + ./srch todo: + + * Please submit your patches with 'diff -pru'. + + + Happy coding! + --Jcid diff --git a/doc/Selection.txt b/doc/Selection.txt new file mode 100644 index 00000000..08fe0abc --- /dev/null +++ b/doc/Selection.txt @@ -0,0 +1,149 @@ +Apr 2003, S.Geerken@ping.de +Last update: Dec 2004 + +========= +Selection +========= + +The selection module (selection.[ch]) handles selections, as well as +activation of links, which is closely related. + + +General Overview +================ + +The selection module defines a structure "Selection", which is +associated to GtkDwViewport, and so to a widget tree. The selection +state is controlled by "abstract events", which are sent by single +widgets by calling one of the following functions: + + a_Selection_button_press for button press events, + a_Selection_button_release for button release events, and + a_Selection_button_motion for motion events (with pressed mouse + button). + +The widget must construct simple iterators (DwIterator), which will be +transferred to extended iterators (DwExtIterator), see below for more +details. All event handling functions have the same signature, the +arguments in detail are: + + - DwIterator *it the iterator pointing on the item under + the mouse pointer, + - gint char_pos the exact (character) position within + the iterator, + - gint link if this item is associated with a link, + its number (see DwImage, section + "signals" for the meaning), otherwise + -1, + - GdkEventButton *event the event itself; only the button is + used, + - gboolean within_content TRUE, if there is some selectable + content unter the mouse cursor; if set + to FALSE, the "full screen" feature is + used on double click. + +In some cases, char_pos would be difficult to determine. E.g., when +the DwPage widget decides that the user is pointing on a position +_at_the_end_ of an image (DwImage), it constructs a simple iterator +pointing on this image widget. In a simple iterator, that fact that +the pointer is at the end, would be represented by char_pos == 1. But +when transferring this simple iterator into an extended iterator, this +simple iterator is discarded and instead the stack has an iterator +pointing to text at the top. As a result, only the first letter of the +ALT text would be copied. + +To avoid this problem, widgets should in this case pass SELECTION_EOW +(end of word) as char_pos, which is then automatically reduced to the +actual length of the extended(!) iterator. + +The return value is the same as in DwWidget event handling methods. +I.e., in most cases, they should simply return it. The events +"link_pressed", "link_released" and "link_clicked" (but not +"link_entered") are emitted by these functions, so that widgets which +let the selection module handle links, should only emit "link_entered" +for themselves. (See DwImage.txt for a description of this.) + + +Selection State +=============== + +Selection interferes with handling the activation of links, so the +latter is also handled by the selection module. Details are based on +following guidelines: + + 1. It should be simple to select links and to start selection in + links. The rule to distinguish between link activation and + selection is that the selection starts as soon as the user leaves + the link. (This is, IMO, a useful feature. Even after drag and + drop has been implemented in dillo, this should be somehow + preserved.) + + 2. The selection should stay as long as possible, i.e., the old + selection is only cleared when a new selection is started. + +The latter leads to a model with two states: the selection state and +the link handling state. + +The general selection works, for events not pointing on links, like +this (numbers in parantheses after the event denote the button, "n" +means arbitrary button): + + motion(1) + ,-----. + | | + press(1) on non-link V | + NONE -----------------------> SELECTING <----------------. + ^ | | + | | release(1) | + | | | press(1) + | no V yes | + `----------------------- Anything selected? --------> SELECTED + +The selected region is represented by two DwExtIterators. + +Links are handled by a different state machine: + + ,-----------------------------. + | | + | Switch to selection + | (SELECTING) for n == 1. + | ^ + | | no + | | yes + | Still the same link? --. + | ^ | + | | | + | | motion(n) | + V press(n) on links | | + NONE ---------------------> PRESSED(n) <-----' + ^ | + | | release(n) + | | + | V yes + | Still the same link? -----------------. + | | | + | | no V + | V Send "clicked" signal. + | Switch to selection | + | (SELECTED) for n == 1. | + | | | + |`----------------------------' | + | | + `----------------------------------------------------------' + +Switching to selection simply means that the selection state will +eventually be SELECTED/SELECTING, with the original and the actual +position making up the selection region. This happens for button 1, +events with buttons other than 1 do not affect selection at all. + + +TODO +==== + +* a_Selection_button_motion currently always assumes that button 1 has + been pressed (since otherwise it would not do anything). This should + be made a bit cleaner. + +* The selection should be cleared, when the user selects something + somewhere else (perhaps switched into "non-active" mode, as some + Gtk+ widgets do). diff --git a/dpi/Makefile.am b/dpi/Makefile.am new file mode 100644 index 00000000..6aba3049 --- /dev/null +++ b/dpi/Makefile.am @@ -0,0 +1,37 @@ +bookmarksdir = $(libdir)/dillo/dpi/bookmarks +downloadsdir = $(libdir)/dillo/dpi/downloads +ftpdir = $(libdir)/dillo/dpi/ftp +httpsdir = $(libdir)/dillo/dpi/https +hellodir = $(libdir)/dillo/dpi/hello +filedir = $(libdir)/dillo/dpi/file +cookiesdir = $(libdir)/dillo/dpi/cookies +datauridir = $(libdir)/dillo/dpi/datauri +bookmarks_PROGRAMS = bookmarks.dpi +downloads_PROGRAMS = downloads.dpi +ftp_PROGRAMS = ftp.filter.dpi +https_PROGRAMS = https.filter.dpi +hello_PROGRAMS = hello.filter.dpi +file_PROGRAMS = file.dpi +cookies_PROGRAMS = cookies.dpi +datauri_PROGRAMS = datauri.filter.dpi + +bookmarks_dpi_LDADD = ../dpip/libDpip.a ../dlib/libDlib.a +downloads_dpi_LDADD = @LIBFLTK_LIBS@ ../dpip/libDpip.a ../dlib/libDlib.a +ftp_filter_dpi_LDADD = ../dpip/libDpip.a ../dlib/libDlib.a +https_filter_dpi_LDADD = @LIBSSL_LIBS@ ../dpip/libDpip.a ../dlib/libDlib.a +hello_filter_dpi_LDADD = ../dpip/libDpip.a ../dlib/libDlib.a +file_dpi_LDADD = @LIBPTHREAD_LIBS@ ../dpip/libDpip.a ../dlib/libDlib.a +cookies_dpi_LDADD = ../dpip/libDpip.a ../dlib/libDlib.a +datauri_filter_dpi_LDADD = ../dpip/libDpip.a ../dlib/libDlib.a + +file_dpi_LDFLAGS = @LIBPTHREAD_LDFLAGS@ + +bookmarks_dpi_SOURCES = bookmarks.c dpiutil.c dpiutil.h +downloads_dpi_SOURCES = downloads.cc dpiutil.c dpiutil.h +ftp_filter_dpi_SOURCES = ftp.c dpiutil.c dpiutil.h +https_filter_dpi_SOURCES = https.c dpiutil.c dpiutil.h +hello_filter_dpi_SOURCES = hello.c dpiutil.c dpiutil.h +file_dpi_SOURCES = file.c dpiutil.c dpiutil.h +cookies_dpi_SOURCES = cookies.c dpiutil.c dpiutil.h +datauri_filter_dpi_SOURCES = datauri.c dpiutil.c dpiutil.h + diff --git a/dpi/bookmarks.c b/dpi/bookmarks.c new file mode 100644 index 00000000..b0f4ca8e --- /dev/null +++ b/dpi/bookmarks.c @@ -0,0 +1,1716 @@ +/* + * Bookmarks server (chat version). + * + * NOTE: this code illustrates how to make a dpi-program. + * + * Copyright 2002-2006 Jorge Arellano Cid <jcid@dillo.org> + * + * 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 3 of the License, or + * (at your option) any later version. + * + */ + +/* Todo: this server is not assembling the received packets. + * This means it currently expects dillo to send full dpi tags + * within the socket; if that fails, everything stops. + * This is not hard to fix, mainly is a matter of expecting the + * final '>' of a tag. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <ctype.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/un.h> +#include <time.h> +#include <netdb.h> +#include <fcntl.h> +#include <signal.h> +#include "../dpip/dpip.h" +#include "dpiutil.h" + + +/* + * Debugging macros + */ +#define _MSG(...) +#define MSG(...) printf("[bookmarks dpi]: " __VA_ARGS__) + +/* This one is tricky, some sources state it should include the byte + * for the terminating NULL, and others say it shouldn't. */ +# define D_SUN_LEN(ptr) ((size_t) (((struct sockaddr_un *) 0)->sun_path) \ + + strlen ((ptr)->sun_path)) + +#define DOCTYPE \ + "<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN'>\n" + +/* + * Notes on character escaping: + * - Basically things are saved unescaped and escaped when in memory. + * - &<>"' are escaped in titles and sections and saved unescaped. + * - ' is escaped as %27 in URLs and saved escaped. + */ +typedef struct { + int key; + int section; + char *url; + char *title; +} BmRec; + +typedef struct { + int section; + char *title; + + int o_sec; /* private, for normalization */ +} BmSec; + + +/* + * Local data + */ +static char *Header = "Content-type: text/html\n\n"; +static char *BmFile = NULL; +static time_t BmFileTimeStamp = 0; +static Dlist *B_bms = NULL; +static int bm_key = 0; + +static Dlist *B_secs = NULL; +static int sec_key = 0; + +static int MODIFY_PAGE_NUM = 1; + + +/* + * Forward declarations + */ + + +/* -- HTML templates ------------------------------------------------------- */ + +char *mainpage_header = +DOCTYPE +"<html>\n" +"<head>\n" +"<title>Bookmarks</title>\n" +"</head>\n" +"<body bgcolor='#778899' link='black' vlink='brown'>\n" +"<table border='1' cellpadding='0' width='100%'>\n" +" <tr><td>\n" +" <table width='100%' bgcolor='#b4b4b4'>\n" +" <tr>\n" +" <td> Bookmarks::</td>\n" +" <td width='100%' align='right'>\n" +" [<a href='dpi:/bm/modify'>modify</a>]\n" +" </td></tr>\n" +" </table></td></tr>\n" +"</table>\n" +"<br>\n"; + +char *modifypage_header = +DOCTYPE +"<html>\n" +"<head>\n" +"<title>Bookmarks</title>\n" +"</head>\n" +"<body bgcolor='#778899' link='black' vlink='brown'>\n" +"<table border='1' cellpadding='0' width='100%'>\n" +" <tr><td>\n" +" <table width='100%' bgcolor='#b4b4b4'>\n" +" <tr>\n" +" <td> Bookmarks :: modify</td></tr>\n" +" </table></td></tr> \n" +"</table> \n" +"\n" +"<form>\n" +"<table width='100%' border='1' cellpadding='0'>\n" +" <tr><td>\n" +" <table width='100%' bgcolor='teal'>\n" +" <tr>\n" +" <td><b>Select an operation </b></td>\n" +" <td><select name='operation'>\n" +" <option value='none' selected>--\n" +" <option value='delete'>Delete\n" +" <option value='move'>Move\n" +" <option value='modify'>Modify\n" +" <option value='add_sec'>Add Section\n" +" <option value='add_url'>Add URL\n" +" </select></td>\n" +" <td><b>, mark its operands, and </b></td>\n" +" <td><input type='submit' name='submit' value='submit.'></td>\n" +" <td width='100%'></td>\n" +" </tr>\n" +" </table></td></tr>\n" +"</table>\n"; + +char *mainpage_sections_header = +"<table border='1' cellpadding='0' cellspacing='20' width='100%'>\n" +" <tr valign='top'>\n" +" <td>\n" +" <table bgcolor='#b4b4b4' border='2' cellpadding='4' cellspacing='1'>\n" +" <tr><td>\n" +" <table width='100%' bgcolor='#b4b4b4'>\n" +" <tr><td><small>Sections:</small></td></tr></table></td></tr>\n"; + +char *modifypage_sections_header = +"<table border='1' cellpadding='0' cellspacing='20' width='100%'>\n" +" <tr valign='top'>\n" +" <td>\n" +" <table bgcolor='#b4b4b4' border='1'>\n" +" <tr><td>\n" +" <table width='100%' bgcolor='#b4b4b4'>\n" +" <tr><td><small>Sections:</small></td></tr></table></td></tr>\n"; + +char *mainpage_sections_item = +" <tr><td align='center'>\n" +" <a href='#s%d'>%s</a></td></tr>\n"; + +char *modifypage_sections_item = +" <tr><td>\n" +" <table width='100%%' bgcolor='#b4b4b4'cellspacing='0' cellpadding='0'>\n" +" <tr align='center'>" +" <td width='1%%'><input type='checkbox' name='s%d'></td>\n" +" <td><a href='#s%d'>%s</a></td></tr></table></td></tr>\n"; + +char *mainpage_sections_footer = +" </table>\n"; + +char *modifypage_sections_footer = +" </table>\n"; + +char *mainpage_middle1 = +" </td>\n" +" <td width='100%'>\n"; + +char *modifypage_middle1 = +" </td>\n" +" <td width='100%'>\n"; + +char *mainpage_section_card_header = +" <a name='s%d'></a>\n" +" <table bgcolor='#bfbfbf' width='100%%' cellspacing='2'>\n" +" <tr>\n" +" <td bgcolor='#bf0c0c'><font color='white'><b>\n" +" %s </b></font></td>\n" +" <td bgcolor='white' width='100%%'> </td></tr>\n"; + +char *modifypage_section_card_header = +" <a name='s%d'></a>\n" +" <table bgcolor='#bfbfbf' width='100%%' cellspacing='2'>\n" +" <tr>\n" +" <td bgcolor='#bf0c0c'><font color='white'><b>\n" +" %s </b></font></td>\n" +" <td bgcolor='white' width='100%%'> </td></tr>\n"; + +char *mainpage_section_card_item = +" <tr><td colspan='2'>\n" +" <a href='%s'>%s</a> </td></tr>\n"; + +char *modifypage_section_card_item = +" <tr>\n" +" <td colspan='2'><input type='checkbox' name='url%d'>\n" +" <a href='%s'>%s</a></td></tr>\n"; + +char *mainpage_section_card_footer = +" </table>\n" +" <hr>\n"; + +char *modifypage_section_card_footer = +" </table>\n" +" <hr>\n"; + +char *mainpage_footer = +" </td>\n" +" </tr>\n" +"</table>\n" +"</body>\n" +"</html>\n"; + +char *modifypage_footer = +" </td>\n" +" </tr>\n" +"</table>\n" +"</form>\n" +"</body>\n" +"</html>\n"; + +/* ------------------------------------------------------------------------- */ +char *modifypage_add_section_page = +DOCTYPE +"<html>\n" +"<head>\n" +"<title>Bookmarks</title>\n" +"</head>\n" +"<body bgcolor='#778899' link='black' vlink='brown'>\n" +"<table border='1' cellpadding='0' width='100%'>\n" +" <tr><td colspan='2'>\n" +" <table bgcolor='#b4b4b4' width='100%'>\n" +" <tr><td bgcolor='#b4b4b4'> Modify bookmarks:: add section\n" +" </td></tr></table></td></tr>\n" +"</table>\n" +"<br>\n" +"<form>\n" +" <input type='hidden' name='operation' value='add_section'>\n" +"<table border='1' width='100%'>\n" +" <tr>\n" +" <td bgcolor='olive'><b>New section:</b></td>\n" +" <td bgcolor='white' width='100%'></td></tr>\n" +"</table>\n" +"<table width='100%' cellpadding='10'>\n" +"<tr><td>\n" +" <table width='100%' bgcolor='teal'>\n" +" <tr>\n" +" <td>Title:</td>\n" +" <td><input type='text' name='title' size='64'></td></tr>\n" +" </table>\n" +" </td></tr>\n" +"</table>\n" +"<table width='100%' cellpadding='4' border='0'>\n" +"<tr><td bgcolor='#a0a0a0'>\n" +" <input type='submit' name='submit' value='submit.'></td></tr>\n" +"</table>\n" +"</form>\n" +"</body>\n" +"</html>\n" +"\n"; + +/* ------------------------------------------------------------------------- */ +char *modifypage_update_header = +DOCTYPE +"<html>\n" +"<head>\n" +"<title>Bookmarks</title>\n" +"</head>\n" +"<body bgcolor='#778899' link='black' vlink='brown'>\n" +"<table border='1' cellpadding='0' width='100%'>\n" +" <tr><td colspan='2'>\n" +" <table bgcolor='#b4b4b4' width='100%'>\n" +" <tr><td bgcolor='#b4b4b4'> Modify bookmarks:: update\n" +" </td></tr></table></td></tr>\n" +"</table>\n" +"<br>\n" +"<form>\n" +"<input type='hidden' name='operation' value='modify2'>\n"; + +char *modifypage_update_title = +"<table border='1' width='100%%'>\n" +" <tr>\n" +" <td bgcolor='olive'><b>%s</b></td>\n" +" <td bgcolor='white' width='100%%'></td></tr>\n" +"</table>\n"; + +char *modifypage_update_item_header = +"<table width='100%' cellpadding='10'>\n"; + +char *modifypage_update_item = +"<tr><td>\n" +" <table width='100%%' bgcolor='teal'>\n" +" <tr>\n" +" <td>Title:</td>\n" +" <td><input type='text' name='title%d' size='64'\n" +" value='%s'></td></tr>\n" +" <tr>\n" +" <td>URL:</td>\n" +" <td>%s</td></tr>\n" +" </table>\n" +" </td></tr>\n"; + +char *modifypage_update_item2 = +"<tr><td>\n" +" <table width='100%%' bgcolor='teal'>\n" +" <tr>\n" +" <td>Title:</td>\n" +" <td><input type='text' name='s%d' size='64'\n" +" value='%s'></td></tr>\n" +" </table>\n" +" </td></tr>\n"; + +char *modifypage_update_item_footer = +"</table>\n"; + +char *modifypage_update_footer = +"<table width='100%' cellpadding='4' border='0'>\n" +"<tr><td bgcolor='#a0a0a0'>\n" +" <input type='submit' name='submit' value='submit.'></td></tr>\n" +"</table>\n" +"</form>\n" +"</body>\n" +"</html>\n"; + +/* ------------------------------------------------------------------------- */ +char *modifypage_add_url = +DOCTYPE +"<html>\n" +"<head>\n" +"<title>Bookmarks</title>\n" +"</head>\n" +"<body bgcolor='#778899' link='black' vlink='brown'>\n" +"<table border='1' cellpadding='0' width='100%'>\n" +" <tr><td colspan='2'>\n" +" <table bgcolor='#b4b4b4' width='100%'>\n" +" <tr><td bgcolor='#b4b4b4'> Modify bookmarks:: add url\n" +" </td></tr></table></td></tr>\n" +"</table>\n" +"<br>\n" +"<form>\n" +"<input type='hidden' name='operation' value='add_url2'>\n" +"<table border='1' width='100%'>\n" +" <tr>\n" +" <td bgcolor='olive'><b>Add url:</b></td>\n" +" <td bgcolor='white' width='100%'></td></tr>\n" +"</table>\n" +"<table width='100%' cellpadding='10'>\n" +"<tr><td>\n" +" <table width='100%' bgcolor='teal'>\n" +" <tr>\n" +" <td>Title:</td>\n" +" <td><input type='text' name='title' size='64'></td></tr>\n" +" <tr>\n" +" <td>URL:</td>\n" +" <td><input type='text' name='url' size='64'></td></tr>\n" +" </table>\n" +" </td></tr>\n" +"</table>\n" +"<table width='100%' cellpadding='4' border='0'>\n" +"<tr><td bgcolor='#a0a0a0'>\n" +" <input type='submit' name='submit' value='submit.'></td></tr>\n" +"</table>\n" +"</form>\n" +"</body>\n" +"</html>\n"; + + +/* ------------------------------------------------------------------------- */ + +/* + * Return a new string with spaces changed with + */ +static char *make_one_line_str(char *str) +{ + char *new_str; + int i, j, n; + + for (i = 0, n = 0; str[i]; ++i) + if (str[i] == ' ') + ++n; + + new_str = dNew(char, strlen(str) + 6*n + 1); + new_str[0] = 0; + + for (i = 0, j = 0; str[i]; ++i) { + if (str[i] == ' ') { + strcpy(new_str + j, " "); + j += 6; + } else { + new_str[j] = str[i]; + new_str[++j] = 0; + } + } + + return new_str; +} + +/* + * Given an urlencoded string, return it to the original version. + */ +static void Unencode_str(char *e_str) +{ + char *p, *e; + + for (p = e = e_str; *e; e++, p++) { + if (*e == '+') { + *p = ' '; + } else if (*e == '%') { + if (dStrncasecmp(e, "%0D%0A", 6) == 0) { + *p = '\n'; + e += 5; + } else { + *p = (isdigit(e[1]) ? (e[1] - '0') : (e[1] - 'A' + 10)) * 16 + + (isdigit(e[2]) ? (e[2] - '0') : (e[2] - 'A' + 10)); + e += 2; + } + } else { + *p = *e; + } + } + *p = 0; +} + +/* + * Send a short message to dillo's status bar. + */ +static int Bmsrv_dpi_send_status_msg(SockHandler *sh, char *str) +{ + int st; + char *d_cmd; + + d_cmd = a_Dpip_build_cmd("cmd=%s msg=%s", "send_status_message", str); + st = sock_handler_write_str(sh, 1, d_cmd); + dFree(d_cmd); + return st; +} + +/* -- ADT for bookmarks ---------------------------------------------------- */ +/* + * Compare function for searching a bookmark by its key + */ +static int Bms_node_by_key_cmp(const void *node, const void *key) +{ + return ((BmRec *)node)->key - VOIDP2INT(key); +} + +/* + * Compare function for searching a bookmark by section + */ +static int Bms_node_by_section_cmp(const void *node, const void *key) +{ + return ((BmRec *)node)->section - VOIDP2INT(key); +} + +/* + * Compare function for searching a section by its number + */ +static int Bms_sec_by_number_cmp(const void *node, const void *key) +{ + return ((BmSec *)node)->section - VOIDP2INT(key); +} + +/* + * Return the Bm record by key + */ +static BmRec *Bms_get(int key) +{ + return dList_find_custom(B_bms, INT2VOIDP(key), Bms_node_by_key_cmp); +} + +/* + * Return the Section record by key + */ +static BmSec *Bms_get_sec(int key) +{ + return dList_find_custom(B_secs, INT2VOIDP(key), Bms_sec_by_number_cmp); +} + +/* + * Add a bookmark + */ +static void Bms_add(int section, char *url, char *title) +{ + BmRec *bm_node; + + bm_node = dNew(BmRec, 1); + bm_node->key = ++bm_key; + bm_node->section = section; + bm_node->url = Escape_uri_str(url, "'"); + bm_node->title = Escape_html_str(title); + dList_append(B_bms, bm_node); +} + +/* + * Add a section + */ +static void Bms_sec_add(char *title) +{ + BmSec *sec_node; + + sec_node = dNew(BmSec, 1); + sec_node->section = sec_key++; + sec_node->title = Escape_html_str(title); + dList_append(B_secs, sec_node); +} + +/* + * Delete a bookmark by its key + */ +static void Bms_del(int key) +{ + BmRec *bm_node; + + bm_node = dList_find_custom(B_bms, INT2VOIDP(key), Bms_node_by_key_cmp); + if (bm_node) { + dList_remove(B_bms, bm_node); + dFree(bm_node->title); + dFree(bm_node->url); + dFree(bm_node); + } + if (dList_length(B_bms) == 0) + bm_key = 0; +} + +/* + * Delete a section and its bookmarks by section number + */ +static void Bms_sec_del(int section) +{ + BmSec *sec_node; + BmRec *bm_node; + + sec_node = dList_find_custom(B_secs, INT2VOIDP(section), + Bms_sec_by_number_cmp); + if (sec_node) { + dList_remove(B_secs, sec_node); + dFree(sec_node->title); + dFree(sec_node); + + /* iterate B_bms and remove those that match the section */ + while ((bm_node = dList_find_custom(B_bms, INT2VOIDP(section), + Bms_node_by_section_cmp))) { + Bms_del(bm_node->key); + } + } + if (dList_length(B_secs) == 0) + sec_key = 0; +} + +/* + * Move a bookmark to another section + */ +static void Bms_move(int key, int target_section) +{ + BmRec *bm_node; + + bm_node = dList_find_custom(B_bms, INT2VOIDP(key), Bms_node_by_key_cmp); + if (bm_node) { + bm_node->section = target_section; + } +} + +/* + * Update a bookmark title by key + */ +static void Bms_update_title(int key, char *n_title) +{ + BmRec *bm_node; + + bm_node = dList_find_custom(B_bms, INT2VOIDP(key), Bms_node_by_key_cmp); + if (bm_node) { + dFree(bm_node->title); + bm_node->title = Escape_html_str(n_title); + } +} + +/* + * Update a section title by key + */ +static void Bms_update_sec_title(int key, char *n_title) +{ + BmSec *sec_node; + + sec_node = dList_find_custom(B_secs, INT2VOIDP(key), Bms_sec_by_number_cmp); + if (sec_node) { + dFree(sec_node->title); + sec_node->title = Escape_html_str(n_title); + } +} + +/* + * Free all the bookmarks data (bookmarks and sections) + */ +static void Bms_free(void) +{ + BmRec *bm_node; + BmSec *sec_node; + + /* free B_bms */ + while ((bm_node = dList_nth_data(B_bms, 0))) { + Bms_del(bm_node->key); + } + /* free B_secs */ + while ((sec_node = dList_nth_data(B_secs, 0))) { + Bms_sec_del(sec_node->section); + } +} + +/* + * Enforce increasing correlative section numbers with no jumps. + */ +static void Bms_normalize(void) +{ + BmRec *bm_node; + BmSec *sec_node; + int i, j; + + /* we need at least one section */ + if (dList_length(B_secs) == 0) + Bms_sec_add("Unclassified"); + + /* make correlative section numbers */ + for (i = 0; (sec_node = dList_nth_data(B_secs, i)); ++i) { + sec_node->o_sec = sec_node->section; + sec_node->section = i; + } + + /* iterate B_secs and make the changes in B_bms */ + for (i = 0; (sec_node = dList_nth_data(B_secs, i)); ++i) { + if (sec_node->section != sec_node->o_sec) { + /* update section numbers */ + for (j = 0; (bm_node = dList_nth_data(B_bms, j)); ++j) { + if (bm_node->section == sec_node->o_sec) + bm_node->section = sec_node->section; + } + } + } +} + +/* -- Load bookmarks file -------------------------------------------------- */ + +/* + * If there's no "bm.txt", create one from "bookmarks.html". + */ +static void Bms_check_import(void) +{ + char *OldBmFile; + char *cmd1 = + "echo \":s0: Unclassified\" > %s"; + char *cmd2 = + "grep -i \"href\" %s | " + "sed -e 's/<li><A HREF=\"/s0 /' -e 's/\">/ /' -e 's/<.*$//' >> %s"; + Dstr *dstr = dStr_new(""); + + + if (access(BmFile, F_OK) != 0) { + OldBmFile = dStrconcat(dGethomedir(), "/.dillo/bookmarks.html", NULL); + if (access(OldBmFile, F_OK) == 0) { + dStr_sprintf(dstr, cmd1, BmFile); + system(dstr->str); + dStr_sprintf(dstr, cmd2, OldBmFile, BmFile); + system(dstr->str); + dStr_free(dstr, TRUE); + dFree(OldBmFile); + } + } +} + +/* + * Load bookmarks data from a file + */ +static int Bms_load(void) +{ + FILE *BmTxt; + char *buf, *p, *url, *title, *u_title; + int section; + struct stat TimeStamp; + + /* clear current bookmarks */ + Bms_free(); + + /* open bm file */ + if (!(BmTxt = fopen(BmFile, "r"))) { + perror("[fopen]"); + return 1; + } + + /* load bm file into memory */ + while ((buf = dGetline(BmTxt)) != NULL) { + if (buf[0] == 's') { + /* get section, url and title */ + section = strtol(buf + 1, NULL, 10); + p = strchr(buf, ' '); *p = 0; + url = ++p; + p = strchr(p, ' '); *p = 0; + title = ++p; + p = strchr(p, '\n'); *p = 0; + u_title = Unescape_html_str(title); + Bms_add(section, url, u_title); + dFree(u_title); + + } else if (buf[0] == ':' && buf[1] == 's') { + /* section = strtol(buf + 2, NULL, 10); */ + p = strchr(buf + 2, ' '); + title = ++p; + p = strchr(p, '\n'); *p = 0; + Bms_sec_add(title); + + } else { + MSG("Syntax error in bookmarks file:\n %s", buf); + } + dFree(buf); + } + fclose(BmTxt); + + /* keep track of the timestamp */ + stat(BmFile, &TimeStamp); + BmFileTimeStamp = TimeStamp.st_mtime; + + return 0; +} + +/* + * Load bookmarks data if: + * - file timestamp is newer than ours or + * - we haven't loaded anything yet :) + */ +static int Bms_cond_load(void) +{ + int st = 0; + struct stat TimeStamp; + + if (stat(BmFile, &TimeStamp) != 0) { + /* try to import... */ + Bms_check_import(); + if (stat(BmFile, &TimeStamp) != 0) + TimeStamp.st_mtime = 0; + } + + if (!BmFileTimeStamp || !dList_length(B_bms) || !dList_length(B_secs) || + BmFileTimeStamp < TimeStamp.st_mtime) { + Bms_load(); + st = 1; + } + return st; +} + +/* -- Save bookmarks file -------------------------------------------------- */ + +/* + * Update the bookmarks file from memory contents + * Return code: { 0:OK, 1:Abort } + */ +static int Bms_save(void) +{ + FILE *BmTxt; + BmRec *bm_node; + BmSec *sec_node; + struct stat BmStat; + char *u_title; + int i, j; + Dstr *dstr = dStr_new(""); + + /* make a safety backup */ + if (stat(BmFile, &BmStat) == 0 && BmStat.st_size > 256) { + char *BmFileBak = dStrconcat(BmFile, ".bak", NULL); + rename(BmFile, BmFileBak); + dFree(BmFileBak); + } + + /* open bm file */ + if (!(BmTxt = fopen(BmFile, "w"))) { + perror("[fopen]"); + return 1; + } + + /* normalize */ + Bms_normalize(); + + /* save sections */ + for (i = 0; (sec_node = dList_nth_data(B_secs, i)); ++i) { + u_title = Unescape_html_str(sec_node->title); + dStr_sprintf(dstr, ":s%d: %s\n", sec_node->section, u_title); + fwrite(dstr->str, (size_t)dstr->len, 1, BmTxt); + dFree(u_title); + } + + /* save bookmarks (section url title) */ + for (i = 0; (sec_node = dList_nth_data(B_secs, i)); ++i) { + for (j = 0; (bm_node = dList_nth_data(B_bms, j)); ++j) { + if (bm_node->section == sec_node->section) { + u_title = Unescape_html_str(bm_node->title); + dStr_sprintf(dstr, "s%d %s %s\n", + bm_node->section, bm_node->url, u_title); + fwrite(dstr->str, (size_t)dstr->len, 1, BmTxt); + dFree(u_title); + } + } + } + + dStr_free(dstr, TRUE); + fclose(BmTxt); + + /* keep track of the timestamp */ + stat(BmFile, &BmStat); + BmFileTimeStamp = BmStat.st_mtime; + + return 0; +} + +/* -- Add bookmark --------------------------------------------------------- */ + +/* + * Add a new bookmark to DB :) + */ +static int Bmsrv_add_bm(SockHandler *sh, char *url, char *title) +{ + char *u_title; + char *msg="Added bookmark!"; + int section = 0; + + /* Add in memory */ + u_title = Unescape_html_str(title); + Bms_add(section, url, u_title); + dFree(u_title); + + /* Write to file */ + Bms_save(); + + if (Bmsrv_dpi_send_status_msg(sh, msg)) + return 1; + + return 0; +} + +/* -- Modify --------------------------------------------------------------- */ + +/* + * Count how many sections and urls were marked in a request + */ +static void Bmsrv_count_urls_and_sections(char *url, int *n_sec, int *n_url) +{ + char *p, *q; + int i; + + /* Check marked urls and sections */ + *n_sec = *n_url = 0; + if ((p = strchr(url, '?'))) { + for (q = p; (q = strstr(q, "&url")); ++q) { + for (i = 0; isdigit(q[4+i]); ++i); + *n_url += (q[4+i] == '=') ? 1 : 0; + } + for (q = p; (q = strstr(q, "&s")); ++q) { + for (i = 0; isdigit(q[2+i]); ++i); + *n_sec += (q[2+i] == '=') ? 1 : 0; + } + } +} + +/* + * Send a dpi reload request + * Return code: { 0:OK, 1:Abort, 2:Close } + */ +static int Bmsrv_send_reload_request(SockHandler *sh, char *url) +{ + int st; + char *d_cmd; + + d_cmd = a_Dpip_build_cmd("cmd=%s url=%s", "reload_request", url); + st = sock_handler_write_str(sh, 1, d_cmd) ? 1 : 0; + dFree(d_cmd); + return st; +} + +/* + * Send the HTML for the modify page + * Return code: { 0:OK, 1:Abort, 2:Close } + */ +static int Bmsrv_send_modify_page(SockHandler *sh) +{ + static Dstr *dstr = NULL; + char *l_title; + BmSec *sec_node; + BmRec *bm_node; + int i, j; + + if (!dstr) + dstr = dStr_new(""); + + /* send modify page header */ + if (sock_handler_write_str(sh, 0, modifypage_header)) + return 1; + + /* write sections header */ + if (sock_handler_write_str(sh, 0, modifypage_sections_header)) + return 1; + /* write sections */ + for (i = 0; (sec_node = dList_nth_data(B_secs, i)); ++i) { + dStr_sprintf(dstr, modifypage_sections_item, + sec_node->section, sec_node->section, sec_node->title); + if (sock_handler_write_str(sh, 0, dstr->str)) + return 1; + } + /* write sections footer */ + if (sock_handler_write_str(sh, 0, modifypage_sections_footer)) + return 1; + + /* send page middle */ + if (sock_handler_write_str(sh, 0, modifypage_middle1)) + return 1; + + /* send bookmark cards */ + for (i = 0; (sec_node = dList_nth_data(B_secs, i)); ++i) { + /* send card header */ + l_title = make_one_line_str(sec_node->title); + dStr_sprintf(dstr, modifypage_section_card_header, + sec_node->section, l_title); + dFree(l_title); + if (sock_handler_write_str(sh, 0, dstr->str)) + return 1; + + /* send section's bookmarks */ + for (j = 0; (bm_node = dList_nth_data(B_bms, j)); ++j) { + if (bm_node->section == sec_node->section) { + dStr_sprintf(dstr, modifypage_section_card_item, + bm_node->key, bm_node->url, bm_node->title); + if (sock_handler_write_str(sh, 0, dstr->str)) + return 1; + } + } + + /* send card footer */ + if (sock_handler_write_str(sh, 0, modifypage_section_card_footer)) + return 1; + } + + /* finish page */ + if (sock_handler_write_str(sh, 1, modifypage_footer)) + return 1; + + return 2; +} + +/* + * Send the HTML for the modify page for "add section" + * Return code: { 0:OK, 1:Abort, 2:Close } + */ +static int Bmsrv_send_modify_page_add_section(SockHandler *sh) +{ + /* send modify page2 */ + if (sock_handler_write_str(sh, 1, modifypage_add_section_page)) + return 1; + + return 2; +} + +/* + * Send the HTML for the modify page for "add url" + * Return code: { 0:OK, 1:Abort, 2:Close } + */ +static int Bmsrv_send_modify_page_add_url(SockHandler *sh) +{ + if (sock_handler_write_str(sh, 1, modifypage_add_url)) + return 1; + return 2; +} + +/* + * Parse a modify urls request and either: + * - make a local copy of the url + * or + * - send the modify page for the marked urls and sections + * Return code: { 0:OK, 1:Abort, 2:Close } + */ +static int Bmsrv_send_modify_update(SockHandler *sh, char *url) +{ + static char *url1 = NULL; + static Dstr *dstr = NULL; + char *p, *q; + int i, key, n_sec, n_url; + BmRec *bm_node; + BmSec *sec_node; + + /* bookmarks were loaded before */ + + if (!dstr) + dstr = dStr_new(""); + + if (sh == NULL) { + /* just copy url */ + dFree(url1); + url1 = dStrdup(url); + return 0; + } + + /* send HTML here */ + if (sock_handler_write_str(sh, 0, modifypage_update_header)) + return 1; + + /* Count number of marked urls and sections */ + Bmsrv_count_urls_and_sections(url1, &n_sec, &n_url); + + if (n_sec) { + dStr_sprintf(dstr, modifypage_update_title, "Update sections:"); + sock_handler_write_str(sh, 0, dstr->str); + sock_handler_write_str(sh, 0, modifypage_update_item_header); + /* send items here */ + p = strchr(url1, '?'); + for (q = p; (q = strstr(q, "&s")); ++q) { + for (i = 0; isdigit(q[2+i]); ++i); + if (q[2+i] == '=') { + key = strtol(q + 2, NULL, 10); + if ((sec_node = Bms_get_sec(key))) { + dStr_sprintf(dstr, modifypage_update_item2, + sec_node->section, sec_node->title); + sock_handler_write_str(sh, 0, dstr->str); + } + } + } + sock_handler_write_str(sh, 0, modifypage_update_item_footer); + } + + if (n_url) { + dStr_sprintf(dstr, modifypage_update_title, "Update titles:"); + sock_handler_write_str(sh, 0, dstr->str); + sock_handler_write_str(sh, 0, modifypage_update_item_header); + /* send items here */ + p = strchr(url1, '?'); + for (q = p; (q = strstr(q, "&url")); ++q) { + for (i = 0; isdigit(q[4+i]); ++i); + if (q[4+i] == '=') { + key = strtol(q + 4, NULL, 10); + bm_node = Bms_get(key); + dStr_sprintf(dstr, modifypage_update_item, + bm_node->key, bm_node->title, bm_node->url); + sock_handler_write_str(sh, 0, dstr->str); + } + } + sock_handler_write_str(sh, 0, modifypage_update_item_footer); + } + + sock_handler_write_str(sh, 1, modifypage_update_footer); + + return 2; +} + +/* + * Make the modify-page and send it back + * Return code: { 0:OK, 1:Abort, 2:Close } + */ +static int Bmsrv_send_modify_answer(SockHandler *sh, char *url) +{ + char *d_cmd; + int st; + + d_cmd = a_Dpip_build_cmd("cmd=%s url=%s", "start_send_page", url); + st = sock_handler_write_str(sh, 1, d_cmd); + dFree(d_cmd); + if (st != 0) + return 1; + + /* Send HTTP header */ + if (sock_handler_write_str(sh, 0, Header) != 0) { + return 1; + } + + if (MODIFY_PAGE_NUM == 2) { + MODIFY_PAGE_NUM = 1; + return Bmsrv_send_modify_page_add_section(sh); + } else if (MODIFY_PAGE_NUM == 3) { + MODIFY_PAGE_NUM = 1; + return Bmsrv_send_modify_update(sh, NULL); + } else if (MODIFY_PAGE_NUM == 4) { + MODIFY_PAGE_NUM = 1; + return Bmsrv_send_modify_page_add_url(sh); + } else { + return Bmsrv_send_modify_page(sh); + } +} + + +/* Operations */ + +/* + * Parse a delete bms request, delete them, and update bm file. + * Return code: { 0:OK, 1:Abort } + */ +static int Bmsrv_modify_delete(SockHandler *sh, char *url) +{ + char *p; + int nb, ns, key; + + /* bookmarks were loaded before */ + + /* Remove marked sections */ + p = strchr(url, '?'); + for (ns = 0; (p = strstr(p, "&s")); ++p) { + if (isdigit(p[2])) { + key = strtol(p + 2, NULL, 10); + Bms_sec_del(key); + ++ns; + } + } + + /* Remove marked urls */ + p = strchr(url, '?'); + for (nb = 0; (p = strstr(p, "&url")); ++p) { + if (isdigit(p[4])) { + key = strtol(p + 4, NULL, 10); + Bms_del(key); + ++nb; + } + } + +/* -- This doesn't work because dillo erases the message upon the + * receipt of the first data stream. + * + sprintf(msg, "Deleted %d bookmark%s!>", n, (n > 1) ? "s" : ""); + if (Bmsrv_dpi_send_status_msg(sh, msg)) + return 1; +*/ + + /* Write new bookmarks file */ + if (nb || ns) + Bms_save(); + + return 0; +} + +/* + * Parse a move urls request, move and update bm file. + * Return code: { 0:OK, 1:Abort } + */ +static int Bmsrv_modify_move(SockHandler *sh, char *url) +{ + char *p; + int n, section = 0, key; + + /* bookmarks were loaded before */ + + /* get target section */ + for (p = url; (p = strstr(p, "&s")); ++p) { + if (isdigit(p[2])) { + section = strtol(p + 2, NULL, 10); + break; + } + } + if (!p) + return 1; + + /* move marked urls */ + p = strchr(url, '?'); + for (n = 0; (p = strstr(p, "&url")); ++p) { + if (isdigit(p[4])) { + key = strtol(p + 4, NULL, 10); + Bms_move(key, section); + ++n; + } + } + + /* Write new bookmarks file */ + if (n) { + Bms_save(); + } + + return 0; +} + +/* + * Parse a modify request: update urls and sections, then save. + * Return code: { 0:OK, 1:Abort } + */ +static int Bmsrv_modify_update(SockHandler *sh, char *url) +{ + char *p, *q, *title; + int i, key; + + /* bookmarks were loaded before */ + + p = strchr(url, '?'); + for ( ; (p = strstr(p, "s")); ++p) { + if (p[-1] == '&' || p[-1] == '?' ) { + for (i = 0; isdigit(p[1 + i]); ++i); + if (i && p[1 + i] == '=') { + /* we have a title/key to change */ + key = strtol(p + 1, NULL, 10); + if ((q = strchr(p + 1, '&'))) + title = dStrndup(p + 2 + i, (uint_t)(q - (p + 2 + i))); + else + title = dStrdup(p + 2 + i); + + Unencode_str(title); + Bms_update_sec_title(key, title); + dFree(title); + } + } + } + + p = strchr(url, '?'); + for ( ; (p = strstr(p, "title")); ++p) { + if (p[-1] == '&' || p[-1] == '?' ) { + for (i = 0; isdigit(p[5 + i]); ++i); + if (i && p[5 + i] == '=') { + /* we have a title/key to change */ + key = strtol(p + 5, NULL, 10); + if ((q = strchr(p + 5, '&'))) + title = dStrndup(p + 6 + i, (uint_t)(q - (p + 6 + i))); + else + title = dStrdup(p + 6 + i); + + Unencode_str(title); + Bms_update_title(key, title); + dFree(title); + } + } + } + + /* Write new bookmarks file */ + Bms_save(); + + return 0; +} + +/* + * Parse an "add section" request, and update the bm file. + * Return code: { 0:OK, 1:Abort } + */ +static int Bmsrv_modify_add_section(SockHandler *sh, char *url) +{ + char *p, *title = NULL; + + /* bookmarks were loaded before */ + + /* get new section's title */ + if ((p = strstr(url, "&title="))) { + title = dStrdup (p + 7); + if ((p = strchr(title, '&'))) + *p = 0; + Unencode_str(title); + } else + return 1; + + Bms_sec_add(title); + dFree(title); + + /* Write new bookmarks file */ + Bms_save(); + + return 0; +} + +/* + * Parse an "add url" request, and update the bm file. + * Return code: { 0:OK, 1:Abort } + */ +static int Bmsrv_modify_add_url(SockHandler *sh, char *s_url) +{ + char *p, *q, *title, *u_title, *url; + int i; + static int section = 0; + + /* bookmarks were loaded before */ + + if (sh == NULL) { + /* look for section */ + for (q = s_url; (q = strstr(q, "&s")); ++q) { + for (i = 0; isdigit(q[2+i]); ++i); + if (q[2+i] == '=') + section = strtol(q + 2, NULL, 10); + } + return 1; + } + + if (!(p = strstr(s_url, "&title=")) || + !(q = strstr(s_url, "&url="))) + return 1; + + title = dStrdup (p + 7); + if ((p = strchr(title, '&'))) + *p = 0; + url = dStrdup (q + 5); + if ((p = strchr(url, '&'))) + *p = 0; + if (strlen(title) && strlen(url)) { + Unencode_str(title); + Unencode_str(url); + u_title = Unescape_html_str(title); + Bms_add(section, url, u_title); + dFree(u_title); + } + dFree(title); + dFree(url); + section = 0; + + /* todo: we should send an "Bookmark added" message, but the + msg-after-HTML functionallity is still pending, not hard though. */ + + /* Write new bookmarks file */ + Bms_save(); + + return 0; +} + +/* + * Check the parameters of a modify request, and return an error message + * when it's wrong. + * Return code: { 0:OK, 2:Close } + */ +static int Bmsrv_check_modify_request(SockHandler *sh, char *url) +{ + char *p, *msg; + int n_sec, n_url; + + /* Count number of marked urls and sections */ + Bmsrv_count_urls_and_sections(url, &n_sec, &n_url); + + p = strchr(url, '?'); + if (strstr(p, "operation=delete&")) { + if (n_url || n_sec) + return 0; + msg = "Delete: you must mark what to delete!"; + + } else if (strstr(url, "operation=move&")) { + if (n_url && n_sec) + return 0; + else if (n_url) + msg = "Move: you must mark a target section!"; + else if (n_sec) + msg = "Move: can not move a section (yet)."; + else + msg = "Move: you must mark some urls, and a target section!"; + + } else if (strstr(url, "operation=modify&")) { + if (n_url || n_sec) + return 0; + msg = "Modify: you must mark what to update!"; + + } else if (strstr(url, "operation=modify2&")) { + /* nothing to check here */ + return 0; + + } else if (strstr(url, "operation=add_sec&")) { + /* nothing to check here */ + return 0; + + } else if (strstr(url, "operation=add_section&")) { + /* nothing to check here */ + return 0; + + } else if (strstr(url, "operation=add_url&")) { + if (n_sec <= 1) + return 0; + msg = "Add url: only one target section is allowed!"; + + } else if (strstr(url, "operation=add_url2&")) { + /* nothing to check here */ + return 0; + + } else if (strstr(url, "operation=none&")) { + msg = "No operation, just do nothing!"; + + } else { + msg = "Sorry, not implemented yet."; + } + + Bmsrv_dpi_send_status_msg(sh, msg); + return 2; +} + +/* + * Parse a and process a modify request. + * Return code: { 0:OK, 1:Abort, 2:Close } + */ +static int Bmsrv_process_modify_request(SockHandler *sh, char *url) +{ + /* check the provided parameters */ + if (Bmsrv_check_modify_request(sh, url) != 0) + return 2; + + if (strstr(url, "operation=delete&")) { + if (Bmsrv_modify_delete(sh, url) == 1) + return 1; + if (Bmsrv_send_reload_request(sh, "dpi:/bm/modify") == 1) + return 1; + + } else if (strstr(url, "operation=move&")) { + if (Bmsrv_modify_move(sh, url) == 1) + return 1; + if (Bmsrv_send_reload_request(sh, "dpi:/bm/modify") == 1) + return 1; + + } else if (strstr(url, "operation=modify&")) { + /* make a local copy of 'url' */ + Bmsrv_send_modify_update(NULL, url); + MODIFY_PAGE_NUM = 3; + if (Bmsrv_send_reload_request(sh, "dpi:/bm/modify") == 1) + return 1; + + } else if (strstr(url, "operation=modify2&")) { + if (Bmsrv_modify_update(sh, url) == 1) + return 1; + if (Bmsrv_send_reload_request(sh, "dpi:/bm/modify") == 1) + return 1; + + } else if (strstr(url, "operation=add_sec&")) { + /* this global variable tells which page to send (--hackish...) */ + MODIFY_PAGE_NUM = 2; + if (Bmsrv_send_reload_request(sh, "dpi:/bm/modify") == 1) + return 1; + + } else if (strstr(url, "operation=add_section&")) { + if (Bmsrv_modify_add_section(sh, url) == 1) + return 1; + if (Bmsrv_send_reload_request(sh, "dpi:/bm/modify") == 1) + return 1; + + } else if (strstr(url, "operation=add_url&")) { + /* this global variable tells which page to send (--hackish...) */ + MODIFY_PAGE_NUM = 4; + /* parse section if present */ + Bmsrv_modify_add_url(NULL, url); + if (Bmsrv_send_reload_request(sh, "dpi:/bm/modify") == 1) + return 1; + + } else if (strstr(url, "operation=add_url2&")) { + if (Bmsrv_modify_add_url(sh, url) == 1) + return 1; + if (Bmsrv_send_reload_request(sh, "dpi:/bm/modify") == 1) + return 1; + } + + return 2; +} + +/* -- Bookmarks ------------------------------------------------------------ */ + +/* + * Send the current bookmarks page (in HTML) + */ +static int send_bm_page(SockHandler *sh) +{ + static Dstr *dstr = NULL; + char *l_title; + BmSec *sec_node; + BmRec *bm_node; + int i, j; + + if (!dstr) + dstr = dStr_new(""); + + if (sock_handler_write_str(sh, 0, mainpage_header)) + return 1; + + /* write sections header */ + if (sock_handler_write_str(sh, 0, mainpage_sections_header)) + return 1; + /* write sections */ + for (i = 0; (sec_node = dList_nth_data(B_secs, i)); ++i) { + dStr_sprintf(dstr, mainpage_sections_item, + sec_node->section, sec_node->title); + if (sock_handler_write_str(sh, 0, dstr->str)) + return 1; + } + /* write sections footer */ + if (sock_handler_write_str(sh, 0, mainpage_sections_footer)) + return 1; + + /* send page middle */ + if (sock_handler_write_str(sh, 0, mainpage_middle1)) + return 1; + + /* send bookmark cards */ + for (i = 0; (sec_node = dList_nth_data(B_secs, i)); ++i) { + /* send card header */ + l_title = make_one_line_str(sec_node->title); + dStr_sprintf(dstr, mainpage_section_card_header, + sec_node->section, l_title); + dFree(l_title); + if (sock_handler_write_str(sh, 0, dstr->str)) + return 1; + + /* send section's bookmarks */ + for (j = 0; (bm_node = dList_nth_data(B_bms, j)); ++j) { + if (bm_node->section == sec_node->section) { + dStr_sprintf(dstr, mainpage_section_card_item, + bm_node->url, bm_node->title); + if (sock_handler_write_str(sh, 0, dstr->str)) + return 1; + } + } + + /* send card footer */ + if (sock_handler_write_str(sh, 0, mainpage_section_card_footer)) + return 1; + } + + /* finish page */ + if (sock_handler_write_str(sh, 1, mainpage_footer)) + return 1; + + return 0; +} + + +/* -- Dpi parser ----------------------------------------------------------- */ + +/* + * Parse a data stream (dpi protocol) + * Note: Buf is a zero terminated string + * Return code: { 0:OK, 1:Abort, 2:Close } + */ +static int Bmsrv_parse_buf(SockHandler *sh, char *Buf) +{ + static char *msg1=NULL, *msg2=NULL, *msg3=NULL; + char *p, *cmd, *d_cmd, *url, *title, *msg; + size_t BufSize; + int st; + + if (!msg1) { + /* Initialize data for the "chat" command. */ + msg1 = a_Dpip_build_cmd("cmd=%s msg=%s", "chat", "Hi browser"); + msg2 = a_Dpip_build_cmd("cmd=%s msg=%s", "chat", "Is it worth?"); + msg3 = a_Dpip_build_cmd("cmd=%s msg=%s", "chat", "Ok, send it"); + } + + if (!(p = strchr(Buf, '>'))) { + /* Haven't got a full tag */ + MSG("Haven't got a full tag!\n"); + return 1; + } + + BufSize = strlen(Buf); + cmd = a_Dpip_get_attr(Buf, BufSize, "cmd"); + + if (cmd && strcmp(cmd, "chat") == 0) { + dFree(cmd); + msg = a_Dpip_get_attr(Buf, BufSize, "msg"); + if (*msg == 'H') { + /* "Hi server" */ + if (sock_handler_write_str(sh, 1, msg1)) + return 1; + } else if (*msg == 'I') { + /* "I want to set abookmark" */ + if (sock_handler_write_str(sh, 1, msg2)) + return 1; + } else if (*msg == 'S') { + /* "Sure" */ + if (sock_handler_write_str(sh, 1, msg3)) + return 1; + } + dFree(msg); + return 0; + } + + /* sync with the bookmarks file */ + Bms_cond_load(); + + if (cmd && strcmp(cmd, "DpiBye") == 0) { + MSG("(pid %d): Got DpiBye.\n", (int)getpid()); + exit(0); + + } else if (cmd && strcmp(cmd, "add_bookmark") == 0) { + dFree(cmd); + url = a_Dpip_get_attr(Buf, BufSize, "url"); + title = a_Dpip_get_attr(Buf, BufSize, "title"); + if (strlen(title) == 0) { + dFree(title); + title = dStrdup("(Untitled)"); + } + if (url && title) + Bmsrv_add_bm(sh, url, title); + dFree(url); + dFree(title); + return 2; + + } else if (cmd && strcmp(cmd, "open_url") == 0) { + dFree(cmd); + url = a_Dpip_get_attr(Buf, BufSize, "url"); + + if (strcmp(url, "dpi:/bm/modify") == 0) { + st = Bmsrv_send_modify_answer(sh, url); + return st; + + } else if (strncmp(url, "dpi:/bm/modify?", 15) == 0) { + /* process request */ + st = Bmsrv_process_modify_request(sh, url); + return st; + } + + + d_cmd = a_Dpip_build_cmd("cmd=%s url=%s", "start_send_page", url); + st = sock_handler_write_str(sh, 1, d_cmd); + dFree(d_cmd); + if (st != 0) + return 1; + + /* Send HTTP header */ + if (sock_handler_write_str(sh, 1, Header) != 0) { + return 1; + } + + st = send_bm_page(sh); + if (st != 0) { + char *err = + DOCTYPE + "<HTML><body> Error on the bookmarks server...</body></html>"; + if (sock_handler_write_str(sh, 1, err) != 0) { + return 1; + } + } + return 2; + } + + return 0; +} + +/* -- Termination handlers ----------------------------------------------- */ +/* + * (was to delete the local namespace socket), + * but this is handled by 'dpid' now. + */ +static void cleanup(void) +{ + /* no cleanup required */ +} + +/* + * Perform any necessary cleanups upon abnormal termination + */ +static void termination_handler(int signum) +{ + exit(signum); +} + + +/* + * -- MAIN ------------------------------------------------------------------- + */ +int main (void) { + struct sockaddr_un spun; + int temp_sock_descriptor; + socklen_t address_size; + char *buf; + int code; + SockHandler *sh; + + /* Arrange the cleanup function for terminations via exit() */ + atexit(cleanup); + + /* Arrange the cleanup function for abnormal terminations */ + if (signal (SIGINT, termination_handler) == SIG_IGN) + signal (SIGINT, SIG_IGN); + if (signal (SIGHUP, termination_handler) == SIG_IGN) + signal (SIGHUP, SIG_IGN); + if (signal (SIGTERM, termination_handler) == SIG_IGN) + signal (SIGTERM, SIG_IGN); + + /* Initialize local data */ + B_bms = dList_new(512); + B_secs = dList_new(32); + BmFile = dStrconcat(dGethomedir(), "/.dillo/bm.txt", NULL); + /* some OSes may need this... */ + address_size = sizeof(struct sockaddr_un); + + MSG("(v.13): accepting connections...\n"); + + while (1) { + temp_sock_descriptor = + accept(STDIN_FILENO, (struct sockaddr *)&spun, &address_size); + if (temp_sock_descriptor == -1) { + perror("[accept]"); + exit(1); + } + + /* create the SockHandler structure */ + sh = sock_handler_new(temp_sock_descriptor,temp_sock_descriptor,8*1024); + + while (1) { + code = 1; + if ((buf = sock_handler_read(sh)) != NULL) { + /* Let's see what we fished... */ + code = Bmsrv_parse_buf(sh, buf); + } + if (code == 1) + exit(1); + else if (code == 2) + break; + } + + sock_handler_close(sh); + sock_handler_free(sh); + + }/*while*/ +} diff --git a/dpi/cookies.c b/dpi/cookies.c new file mode 100644 index 00000000..f669eb69 --- /dev/null +++ b/dpi/cookies.c @@ -0,0 +1,1466 @@ +/* + * File: cookies.c + * Cookies server. + * + * Copyright 2001 Lars Clausen <lrclause@cs.uiuc.edu> + * Jörgen Viksell <jorgen.viksell@telia.com> + * Copyright 2002-2006 Jorge Arellano Cid <jcid@dillo.org> + * + * 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 3 of the License, or + * (at your option) any later version. + * + */ + +/* Handling of cookies takes place here. + * This implementation aims to follow RFC 2965: + * http://www.cis.ohio-state.edu/cs/Services/rfc/rfc-text/rfc2965.txt + */ + +/* + * TODO: Cleanup this code. Shorten some functions, order things, + * add comments, remove leaks, etc. + */ + +/* Todo: this server is not assembling the received packets. + * This means it currently expects dillo to send full dpi tags + * within the socket; if that fails, everything stops. + */ + +#ifdef DISABLE_COOKIES + +int main(void) +{ + return 0; /* never called */ +} + +#else + + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/un.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> +#include <stddef.h> +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <time.h> /* for time() and time_t */ +#include <ctype.h> +#include <netdb.h> +#include <signal.h> +#include "dpiutil.h" +#include "../dpip/dpip.h" + + +/* + * Debugging macros + */ +#define _MSG(...) +#define MSG(...) printf("[cookies dpi]: " __VA_ARGS__) + + +/* This one is tricky, some sources state it should include the byte + * for the terminating NULL, and others say it shouldn't. */ +# define D_SUN_LEN(ptr) ((size_t) (((struct sockaddr_un *) 0)->sun_path) \ + + strlen ((ptr)->sun_path)) + + +/* + * a_List_add() + * + * Make sure there's space for 'num_items' items within the list + * (First, allocate an 'alloc_step' sized chunk, after that, double the + * list size --to make it faster) + */ +#define a_List_add(list,num_items,alloc_step) \ + if (!list) { \ + list = dMalloc(alloc_step * sizeof((*list))); \ + } \ + if (num_items >= alloc_step){ \ + while ( num_items >= alloc_step ) \ + alloc_step <<= 1; \ + list = dRealloc(list, alloc_step * sizeof((*list))); \ + } + +/* The maximum length of a line in the cookie file */ +#define LINE_MAXLEN 4096 + +typedef enum { + COOKIE_ACCEPT, + COOKIE_ACCEPT_SESSION, + COOKIE_DENY +} CookieControlAction; + +typedef struct { + char *domain; + CookieControlAction action; +} CookieControl; + +typedef struct { + char *domain; + Dlist *dlist; +} CookieNode; + +typedef struct { + char *name; + char *value; + char *domain; + char *path; + time_t expires_at; + uint_t version; + char *comment; + char *comment_url; + bool_t secure; + bool_t session_only; + Dlist *ports; +} CookieData_t; + +/* + * Local data + */ + +/* List of CookieNode. Each node holds a domain and its list of cookies */ +static Dlist *cookies; + +/* Variables for access control */ +static CookieControl *ccontrol = NULL; +static int num_ccontrol = 0; +static int num_ccontrol_max = 1; +static CookieControlAction default_action = COOKIE_DENY; + +static bool_t disabled; +static FILE *file_stream; +static char *cookies_txt_header_str = +"# HTTP Cookie File\n" +"# http://www.netscape.com/newsref/std/cookie_spec.html\n" +"# This is a generated file! Do not edit.\n\n"; + + +/* + * Forward declarations + */ + +static FILE *Cookies_fopen(const char *file, char *init_str); +static CookieControlAction Cookies_control_check_domain(const char *domain); +static int Cookie_control_init(void); +static void Cookies_parse_ports(int url_port, CookieData_t *cookie, + const char *port_str); +static char *Cookies_build_ports_str(CookieData_t *cookie); +static char *Cookies_strip_path(const char *path); +static void Cookies_add_cookie(CookieData_t *cookie); +static void Cookies_remove_cookie(CookieData_t *cookie); +static int Cookies_cmp(const void *a, const void *b); + +/* + * Compare function for searching a cookie node + */ +int Cookie_node_cmp(const void *v1, const void *v2) +{ + const CookieNode *n1 = v1, *n2 = v2; + + return strcmp(n1->domain, n2->domain); +} + +/* + * Compare function for searching a cookie node by domain + */ +int Cookie_node_by_domain_cmp(const void *v1, const void *v2) +{ + const CookieNode *node = v1; + const char *domain = v2; + + return strcmp(node->domain, domain); +} + +/* + * Return a file pointer. If the file doesn't exist, try to create it, + * with the optional 'init_str' as its content. + */ +static FILE *Cookies_fopen(const char *filename, char *init_str) +{ + FILE *F_in; + int fd; + + if ((F_in = fopen(filename, "r+")) == NULL) { + /* Create the file */ + fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); + if (fd != -1) { + if (init_str) + write(fd, init_str, strlen(init_str)); + close(fd); + + MSG("Created file: %s\n", filename); + F_in = Cookies_fopen(filename, NULL); + } else { + MSG("Could not create file: %s!\n", filename); + } + } + + /* set close on exec */ + fcntl(fileno(F_in), F_SETFD, FD_CLOEXEC | fcntl(fileno(F_in), F_GETFD)); + + return F_in; +} + +static void Cookies_free_cookie(CookieData_t *cookie) +{ + dFree(cookie->name); + dFree(cookie->value); + dFree(cookie->domain); + dFree(cookie->path); + dFree(cookie->comment); + dFree(cookie->comment_url); + dList_free(cookie->ports); + dFree(cookie); +} + +/* + * Initialize the cookies module + * (The 'disabled' variable is writable only within Cookies_init) + */ +void Cookies_init() +{ + CookieData_t *cookie; + char *filename; + char line[LINE_MAXLEN]; +#ifndef HAVE_LOCKF + struct flock lck; +#endif + FILE *old_cookies_file_stream; + + /* Default setting */ + disabled = TRUE; + + /* Read and parse the cookie control file (cookiesrc) */ + if (Cookie_control_init() != 0) { + MSG("Disabling cookies.\n"); + return; + } + + /* Get a stream for the cookies file */ + filename = dStrconcat(dGethomedir(), "/.dillo/cookies.txt", NULL); + file_stream = Cookies_fopen(filename, cookies_txt_header_str); + + dFree(filename); + + if (!file_stream) { + MSG("ERROR: Can't open ~/.dillo/cookies.txt, disabling cookies\n"); + return; + } + + /* Try to get a lock from the file descriptor */ +#ifdef HAVE_LOCKF + disabled = (lockf(fileno(file_stream), F_TLOCK, 0) == -1); +#else /* POSIX lock */ + lck.l_start = 0; /* start at beginning of file */ + lck.l_len = 0; /* lock entire file */ + lck.l_type = F_WRLCK; + lck.l_whence = SEEK_SET; /* absolute offset */ + + disabled = (fcntl(fileno(file_stream), F_SETLK, &lck) == -1); +#endif + if (disabled) { + MSG("The cookies file has a file lock: disabling cookies!\n"); + fclose(file_stream); + return; + } + + MSG("Enabling cookies as from cookiesrc...\n"); + + cookies = dList_new(32); + + /* Get all lines in the file */ + while (!feof(file_stream)) { + line[0] = '\0'; + fgets(line, LINE_MAXLEN, file_stream); + + /* Remove leading and trailing whitespaces */ + dStrstrip(line); + + if ((line[0] != '\0') && (line[0] != '#')) { + /* + * Split the row into pieces using a tab as the delimiter. + * pieces[0] The domain name + * pieces[1] TRUE/FALSE: is the domain a suffix, or a full domain? + * pieces[2] The path + * pieces[3] Is the cookie unsecure or secure (TRUE/FALSE) + * pieces[4] Timestamp of expire date + * pieces[5] Name of the cookie + * pieces[6] Value of the cookie + */ + CookieControlAction action; + char *piece; + char *line_marker = line; + + cookie = dNew0(CookieData_t, 1); + + cookie->session_only = FALSE; + cookie->version = 0; + cookie->domain = dStrdup(dStrsep(&line_marker, "\t")); + dStrsep(&line_marker, "\t"); /* we use domain always as sufix */ + cookie->path = dStrdup(dStrsep(&line_marker, "\t")); + piece = dStrsep(&line_marker, "\t"); + if (piece != NULL && piece[0] == 'T') + cookie->secure = TRUE; + piece = dStrsep(&line_marker, "\t"); + if (piece != NULL) + cookie->expires_at = (time_t) strtol(piece, NULL, 10); + cookie->name = dStrdup(dStrsep(&line_marker, "\t")); + cookie->value = dStrdup(dStrsep(&line_marker, "\t")); + + if (!cookie->domain || cookie->domain[0] == '\0' || + !cookie->path || cookie->path[0] != '/' || + !cookie->name || cookie->name[0] == '\0' || + !cookie->value) { + MSG("Malformed line in cookies.txt file!\n"); + Cookies_free_cookie(cookie); + continue; + } + + action = Cookies_control_check_domain(cookie->domain); + if (action == COOKIE_DENY) { + Cookies_free_cookie(cookie); + continue; + } else if (action == COOKIE_ACCEPT_SESSION) { + cookie->session_only = TRUE; + } + + /* Save cookie in memory */ + Cookies_add_cookie(cookie); + } + } + + filename = dStrconcat(dGethomedir(), "/.dillo/cookies", NULL); + if ((old_cookies_file_stream = fopen(filename, "r")) != NULL) { + dFree(filename); + MSG("WARNING: Reading old cookies file ~/.dillo/cookies too\n"); + + /* Get all lines in the file */ + while (!feof(old_cookies_file_stream)) { + line[0] = '\0'; + fgets(line, LINE_MAXLEN, old_cookies_file_stream); + + /* Remove leading and trailing whitespaces */ + dStrstrip(line); + + if (line[0] != '\0') { + /* + * Split the row into pieces using a tab as the delimiter. + * pieces[0] The version this cookie was set as (0 / 1) + * pieces[1] The domain name + * pieces[2] A comma separated list of accepted ports + * pieces[3] The path + * pieces[4] Is the cookie unsecure or secure (0 / 1) + * pieces[5] Timestamp of expire date + * pieces[6] Name of the cookie + * pieces[7] Value of the cookie + * pieces[8] Comment + * pieces[9] Comment url + */ + CookieControlAction action; + char *piece; + char *line_marker = line; + + cookie = dNew0(CookieData_t, 1); + + cookie->session_only = FALSE; + piece = dStrsep(&line_marker, "\t"); + if (piece != NULL) + cookie->version = strtol(piece, NULL, 10); + cookie->domain = dStrdup(dStrsep(&line_marker, "\t")); + Cookies_parse_ports(0, cookie, dStrsep(&line_marker, "\t")); + cookie->path = dStrdup(dStrsep(&line_marker, "\t")); + piece = dStrsep(&line_marker, "\t"); + if (piece != NULL && piece[0] == '1') + cookie->secure = TRUE; + piece = dStrsep(&line_marker, "\t"); + if (piece != NULL) + cookie->expires_at = (time_t) strtol(piece, NULL, 10); + cookie->name = dStrdup(dStrsep(&line_marker, "\t")); + cookie->value = dStrdup(dStrsep(&line_marker, "\t")); + cookie->comment = dStrdup(dStrsep(&line_marker, "\t")); + cookie->comment_url = dStrdup(dStrsep(&line_marker, "\t")); + + if (!cookie->domain || cookie->domain[0] == '\0' || + !cookie->path || cookie->path[0] != '/' || + !cookie->name || cookie->name[0] == '\0' || + !cookie->value) { + MSG("Malformed line in cookies file!\n"); + Cookies_free_cookie(cookie); + continue; + } + + action = Cookies_control_check_domain(cookie->domain); + if (action == COOKIE_DENY) { + Cookies_free_cookie(cookie); + continue; + } else if (action == COOKIE_ACCEPT_SESSION) { + cookie->session_only = TRUE; + } + + /* Save cookie in memory */ + Cookies_add_cookie(cookie); + } + } + fclose(old_cookies_file_stream); + } else { + dFree(filename); + } + +} + +/* + * Flush cookies to disk and free all the memory allocated. + */ +void Cookies_save_and_free() +{ + int i, fd; + CookieNode *node; + CookieData_t *cookie; + +#ifndef HAVE_LOCKF + struct flock lck; +#endif + + if (disabled) + return; + + rewind(file_stream); + fd = fileno(file_stream); + ftruncate(fd, 0); + fprintf(file_stream, cookies_txt_header_str); + + /* Iterate cookies per domain, saving and freeing */ + while ((node = dList_nth_data(cookies, 0))) { + for (i = 0; (cookie = dList_nth_data(node->dlist, i)); ++i) { + if (!cookie->session_only) { + /* char * ports_str = Cookies_build_ports_str(cookie); */ + fprintf(file_stream, "%s\tTRUE\t%s\t%s\t%ld\t%s\t%s\n", + cookie->domain, + cookie->path, + cookie->secure ? "TRUE" : "FALSE", + (long)cookie->expires_at, + cookie->name, + cookie->value); + /* dFree(ports_str); */ + } + + Cookies_free_cookie(cookie); + } + dList_remove(cookies, node); + dFree(node->domain); + dList_free(node->dlist); + dFree(node); + } + +#ifdef HAVE_LOCKF + lockf(fd, F_ULOCK, 0); +#else /* POSIX file lock */ + lck.l_start = 0; /* start at beginning of file */ + lck.l_len = 0; /* lock entire file */ + lck.l_type = F_UNLCK; + lck.l_whence = SEEK_SET; /* absolute offset */ + + fcntl(fileno(file_stream), F_SETLKW, &lck); +#endif + fclose(file_stream); +} + +static char *months[] = +{ "", + "Jan", "Feb", "Mar", + "Apr", "May", "Jun", + "Jul", "Aug", "Sep", + "Oct", "Nov", "Dec" +}; + +/* + * Take a months name and return a number between 1-12. + * E.g. 'April' -> 4 + */ +static int Cookies_get_month(const char *month_name) +{ + int i; + + for (i = 1; i <= 12; i++) { + if (!dStrncasecmp(months[i], month_name, 3)) + return i; + } + return 0; +} + +/* + * Return a local timestamp from a GMT date string + * Accept: RFC-1123 | RFC-850 | ANSI asctime | Old Netscape format. + * + * Wdy, DD-Mon-YY HH:MM:SS GMT + * Wdy, DD-Mon-YYYY HH:MM:SS GMT + * Weekday, DD-Mon-YY HH:MM:SS GMT + * Weekday, DD-Mon-YYYY HH:MM:SS GMT + * Tue May 21 13:46:22 1991\n + * Tue May 21 13:46:22 1991 + * + * (return 0 on malformed date string syntax) + */ +static time_t Cookies_create_timestamp(const char *expires) +{ + time_t ret; + int day, month, year, hour, minutes, seconds; + char *cp; + char *E_msg = + "Expire date is malformed!\n" + " (should be RFC-1123 | RFC-850 | ANSI asctime)\n" + " Ignoring cookie: "; + + cp = strchr(expires, ','); + if (!cp && (strlen(expires) == 24 || strlen(expires) == 25)) { + /* Looks like ANSI asctime format... */ + cp = (char *)expires; + day = strtol(cp + 8, NULL, 10); /* day */ + month = Cookies_get_month(cp + 4); /* month */ + year = strtol(cp + 20, NULL, 10); /* year */ + hour = strtol(cp + 11, NULL, 10); /* hour */ + minutes = strtol(cp + 14, NULL, 10); /* minutes */ + seconds = strtol(cp + 17, NULL, 10); /* seconds */ + + } else if (cp && (cp - expires == 3 || cp - expires > 5) && + (strlen(cp) == 24 || strlen(cp) == 26)) { + /* RFC-1123 | RFC-850 format | Old Netscape format */ + day = strtol(cp + 2, NULL, 10); + month = Cookies_get_month(cp + 5); + year = strtol(cp + 9, &cp, 10); + /* todo: tricky, because two digits for year IS ambiguous! */ + year += (year < 70) ? 2000 : ((year < 100) ? 1900 : 0); + hour = strtol(cp + 1, NULL, 10); + minutes = strtol(cp + 4, NULL, 10); + seconds = strtol(cp + 7, NULL, 10); + + } else { + MSG("%s%s\n", E_msg, expires); + return (time_t) 0; + } + + /* Error checks --this may be overkill */ + if (!(day > 0 && day < 32 && month > 0 && month < 13 && year > 1970 && + hour >= 0 && hour < 24 && minutes >= 0 && minutes < 60 && + seconds >= 0 && seconds < 60)) { + MSG("%s%s\n", E_msg, expires); + return (time_t) 0; + } + + /* Calculate local timestamp. + * [stolen from Lynx... (http://lynx.browser.org)] */ + month -= 3; + if (month < 0) { + month += 12; + year--; + } + + day += (year - 1968) * 1461 / 4; + day += ((((month * 153) + 2) / 5) - 672); + ret = (time_t)((day * 60 * 60 * 24) + + (hour * 60 * 60) + + (minutes * 60) + + seconds); + + MSG("Expires in %ld seconds, at %s", + (long)ret - time(NULL), ctime(&ret)); + + return ret; +} + +/* + * Parse a string containing a list of port numbers. + */ +static void Cookies_parse_ports(int url_port, CookieData_t *cookie, + const char *port_str) +{ + if ((!port_str || !port_str[0]) && url_port != 0) { + /* There was no list, so only the calling urls port should be allowed. */ + if (!cookie->ports) + cookie->ports = dList_new(1); + dList_append(cookie->ports, INT2VOIDP(url_port)); + } else if (port_str[0] == '"' && port_str[1] != '"') { + char *tok, *str; + int port; + + str = dStrdup(port_str + 1); + while ((tok = dStrsep(&str, ","))) { + port = strtol(tok, NULL, 10); + if (port > 0) { + if (!cookie->ports) + cookie->ports = dList_new(1); + dList_append(cookie->ports, INT2VOIDP(port)); + } + } + dFree(str); + } +} + +/* + * Build a string of the ports in 'cookie'. + */ +static char *Cookies_build_ports_str(CookieData_t *cookie) +{ + Dstr *dstr; + char *ret; + void *data; + int i; + + dstr = dStr_new("\""); + for (i = 0; (data = dList_nth_data(cookie->ports, i)); ++i) { + dStr_sprintfa(dstr, "%d,", VOIDP2INT(data)); + } + /* Remove any trailing comma */ + if (dstr->len > 1) + dStr_erase(dstr, dstr->len - 1, 1); + dStr_append(dstr, "\""); + + ret = dstr->str; + dStr_free(dstr, FALSE); + + return ret; +} + +static void Cookies_add_cookie(CookieData_t *cookie) +{ + Dlist *domain_cookies; + CookieData_t *c; + CookieNode *node; + + /* Don't add an expired cookie */ + if (!cookie->session_only && cookie->expires_at < time(NULL)) { + Cookies_free_cookie(cookie); + return; + } + + node = dList_find_sorted(cookies, cookie->domain,Cookie_node_by_domain_cmp); + domain_cookies = (node) ? node->dlist : NULL; + + if (domain_cookies) { + /* Respect the limit of 20 cookies per domain */ + if (dList_length(domain_cookies) >= 20) { + MSG("There are too many cookies for this domain (%s)\n", + cookie->domain); + Cookies_free_cookie(cookie); + return; + } + + /* Remove any cookies with the same name and path */ + while ((c = dList_find_custom(domain_cookies, cookie, Cookies_cmp))){ + Cookies_remove_cookie(c); + } + } + + /* add the cookie into the respective domain list */ + node = dList_find_sorted(cookies, cookie->domain,Cookie_node_by_domain_cmp); + domain_cookies = (node) ? node->dlist : NULL; + if (!domain_cookies) { + domain_cookies = dList_new(5); + dList_append(domain_cookies, cookie); + node = dNew(CookieNode, 1); + node->domain = dStrdup(cookie->domain); + node->dlist = domain_cookies; + dList_insert_sorted(cookies, node, Cookie_node_cmp); + } else { + dList_append(domain_cookies, cookie); + } +} + +/* + * Remove the cookie from the domain list. + * If the domain list is empty, remove the node too. + * Free the cookie. + */ +static void Cookies_remove_cookie(CookieData_t *cookie) +{ + CookieNode *node; + + node = dList_find_sorted(cookies, cookie->domain,Cookie_node_by_domain_cmp); + if (node) { + dList_remove(node->dlist, cookie); + if (dList_length(node->dlist) == 0) { + dList_remove(cookies, node); + dFree(node->domain); + dList_free(node->dlist); + } + } else { + MSG("Attempting to remove a cookie that doesn't exist!\n"); + } + + Cookies_free_cookie(cookie); +} + +/* + * Return the attribute that is present at *cookie_str. This function + * will also attempt to advance cookie_str past any equal-sign. + */ +static char *Cookies_parse_attr(char **cookie_str) +{ + char *str = *cookie_str; + uint_t i, end = 0; + bool_t got_attr = FALSE; + + for (i = 0; ; i++) { + switch (str[i]) { + case ' ': + case '\t': + case '=': + case ';': + got_attr = TRUE; + if (end == 0) + end = i; + break; + case ',': + *cookie_str = str + i; + return dStrndup(str, i); + break; + case '\0': + if (!got_attr) { + end = i; + got_attr = TRUE; + } + /* fall through! */ + default: + if (got_attr) { + *cookie_str = str + i; + return dStrndup(str, end); + } + break; + } + } + + return NULL; +} + +/* + * Get the value starting at *cookie_str. + * broken_syntax: watch out for stupid syntax (comma in unquoted string...) + */ +static char *Cookies_parse_value(char **cookie_str, + bool_t broken_syntax, + bool_t keep_quotes) +{ + uint_t i, end; + char *str = *cookie_str; + + for (i = end = 0; !end; ++i) { + switch (str[i]) { + case ' ': + case '\t': + if (!broken_syntax && str[0] != '\'' && str[0] != '"') { + *cookie_str = str + i + 1; + end = 1; + } + break; + case '\'': + case '"': + if (i != 0 && str[i] == str[0]) { + char *tmp = str + i; + + while (*tmp != '\0' && *tmp != ';' && *tmp != ',') + tmp++; + + *cookie_str = (*tmp == ';') ? tmp + 1 : tmp; + + if (keep_quotes) + i++; + end = 1; + } + break; + case '\0': + *cookie_str = str + i; + end = 1; + break; + case ',': + if (str[0] != '\'' && str[0] != '"' && !broken_syntax) { + /* A new cookie starts here! */ + *cookie_str = str + i; + end = 1; + } + break; + case ';': + if (str[0] != '\'' && str[0] != '"') { + *cookie_str = str + i + 1; + end = 1; + } + break; + default: + break; + } + } + /* keep i as an index to the last char */ + --i; + + if ((str[0] == '\'' || str[0] == '"') && !keep_quotes) { + return i > 1 ? dStrndup(str + 1, i - 1) : NULL; + } else { + return dStrndup(str, i); + } +} + +/* + * Parse one cookie... + */ +static CookieData_t *Cookies_parse_one(int url_port, char **cookie_str) +{ + CookieData_t *cookie; + char *str = *cookie_str; + char *attr; + char *value; + int num_attr = 0; + bool_t max_age = FALSE; + bool_t discard = FALSE; + + cookie = dNew0(CookieData_t, 1); + cookie->session_only = TRUE; + + /* Iterate until there is nothing left of the string OR we come + * across a comma representing the start of another cookie */ + while (*str != '\0' && *str != ',') { + /* Skip whitespace */ + while (isspace(*str)) + str++; + + /* Get attribute */ + attr = Cookies_parse_attr(&str); + if (!attr) { + MSG("Failed to parse cookie attribute!\n"); + Cookies_free_cookie(cookie); + return NULL; + } + + /* Get the value for the attribute and store it */ + if (num_attr == 0) { + /* The first attr, which always is the user supplied attr, may + * have the same name as an ordinary attr. Hence this workaround. */ + cookie->name = dStrdup(attr); + cookie->value = Cookies_parse_value(&str, FALSE, TRUE); + } else if (dStrcasecmp(attr, "Path") == 0) { + value = Cookies_parse_value(&str, FALSE, FALSE); + cookie->path = value; + } else if (dStrcasecmp(attr, "Domain") == 0) { + value = Cookies_parse_value(&str, FALSE, FALSE); + cookie->domain = value; + } else if (dStrcasecmp(attr, "Discard") == 0) { + cookie->session_only = TRUE; + discard = TRUE; + } else if (dStrcasecmp(attr, "Max-Age") == 0) { + if (!discard) { + value = Cookies_parse_value(&str, FALSE, FALSE); + + if (value) { + cookie->expires_at = time(NULL) + strtol(value, NULL, 10); + cookie->session_only = FALSE; + max_age = TRUE; + dFree(value); + } else { + MSG("Failed to parse cookie value!\n"); + Cookies_free_cookie(cookie); + return NULL; + } + } + } else if (dStrcasecmp(attr, "Expires") == 0) { + if (!max_age && !discard) { + MSG("Old netscape-style cookie...\n"); + value = Cookies_parse_value(&str, TRUE, FALSE); + if (value) { + cookie->expires_at = Cookies_create_timestamp(value); + cookie->session_only = FALSE; + dFree(value); + } else { + MSG("Failed to parse cookie value!\n"); + Cookies_free_cookie(cookie); + return NULL; + } + } + } else if (dStrcasecmp(attr, "Port") == 0) { + value = Cookies_parse_value(&str, FALSE, TRUE); + Cookies_parse_ports(url_port, cookie, value); + dFree(value); + } else if (dStrcasecmp(attr, "Comment") == 0) { + value = Cookies_parse_value(&str, FALSE, FALSE); + cookie->comment = value; + } else if (dStrcasecmp(attr, "CommentURL") == 0) { + value = Cookies_parse_value(&str, FALSE, FALSE); + cookie->comment_url = value; + } else if (dStrcasecmp(attr, "Version") == 0) { + value = Cookies_parse_value(&str, FALSE, FALSE); + + if (value) { + cookie->version = strtol(value, NULL, 10); + dFree(value); + } else { + MSG("Failed to parse cookie value!\n"); + Cookies_free_cookie(cookie); + return NULL; + } + } else if (dStrcasecmp(attr, "Secure") == 0) { + cookie->secure = TRUE; + } else { + /* Oops! this can't be good... */ + dFree(attr); + Cookies_free_cookie(cookie); + MSG("Cookie contains illegal attribute!\n"); + return NULL; + } + + dFree(attr); + num_attr++; + } + + *cookie_str = (*str == ',') ? str + 1 : str; + + if (cookie->name && cookie->value) { + return cookie; + } else { + MSG("Cookie missing name and/or value!\n"); + Cookies_free_cookie(cookie); + return NULL; + } +} + +/* + * Iterate the cookie string until we catch all cookies. + * Return Value: a list with all the cookies! (or NULL upon error) + */ +static Dlist *Cookies_parse_string(int url_port, char *cookie_string) +{ + CookieData_t *cookie; + Dlist *ret = NULL; + char *str = cookie_string; + + /* The string may contain several cookies separated by comma. + * We'll iterate until we've catched them all */ + while (*str) { + cookie = Cookies_parse_one(url_port, &str); + + if (cookie) { + if (!ret) + ret = dList_new(4); + dList_append(ret, cookie); + } else { + MSG("Malformed cookie field, ignoring cookie: %s\n", cookie_string); + } + } + + return ret; +} + +/* + * Compare cookies by name and path (return 0 if equal) + */ +static int Cookies_cmp(const void *a, const void *b) +{ + const CookieData_t *ca = a, *cb = b; + int ret; + + if (!(ret = strcmp(ca->name, cb->name))) + ret = strcmp(ca->path, cb->path); + return ret; +} + +/* + * Validate cookies domain against some security checks. + */ +static bool_t Cookies_validate_domain(CookieData_t *cookie, char *host, + char *url_path) +{ + int dots, diff, i; + bool_t is_ip; + + /* Make sure that the path is set to something */ + if (!cookie->path || cookie->path[0] != '/') { + dFree(cookie->path); + cookie->path = Cookies_strip_path(url_path); + } + + /* If the server never set a domain, or set one without a leading + * dot (which isn't allowed), we use the calling URL's hostname. */ + if (cookie->domain == NULL || cookie->domain[0] != '.') { + dFree(cookie->domain); + cookie->domain = dStrdup(host); + return TRUE; + } + + /* Count the number of dots and also find out if it is an IP-address */ + is_ip = TRUE; + for (i = 0, dots = 0; cookie->domain[i] != '\0'; i++) { + if (cookie->domain[i] == '.') + dots++; + else if (!isdigit(cookie->domain[i])) + is_ip = FALSE; + } + + /* A valid domain must have at least two dots in it */ + /* NOTE: this breaks cookies on localhost... */ + if (dots < 2) { + return FALSE; + } + + /* Now see if the url matches the domain */ + diff = strlen(host) - i; + if (diff > 0) { + if (dStrcasecmp(host + diff, cookie->domain)) + return FALSE; + + if (!is_ip) { + /* "x.y.test.com" is not allowed to set cookies for ".test.com"; + * only an url of the form "y.test.com" would be. */ + while ( diff-- ) + if (host[diff] == '.') + return FALSE; + } + } + + return TRUE; +} + +/* + * Strip of the filename from a full path + */ +static char *Cookies_strip_path(const char *path) +{ + char *ret; + uint_t len; + + if (path) { + len = strlen(path); + + while (len && path[len] != '/') + len--; + ret = dStrndup(path, len + 1); + } else { + ret = dStrdup("/"); + } + + return ret; +} + +/* + * Set the value corresponding to the cookie string + */ +void Cookies_set(char *cookie_string, char *url_host, + char *url_path, int url_port) +{ + CookieControlAction action; + CookieData_t *cookie; + Dlist *list; + int i; + + if (disabled) + return; + + action = Cookies_control_check_domain(url_host); + if (action == COOKIE_DENY) { + MSG("denied SET for %s\n", url_host); + return; + } + + if ((list = Cookies_parse_string(url_port, cookie_string))) { + for (i = 0; (cookie = dList_nth_data(list, i)); ++i) { + if (Cookies_validate_domain(cookie, url_host, url_path)) { + if (action == COOKIE_ACCEPT_SESSION) + cookie->session_only = TRUE; + Cookies_add_cookie(cookie); + } else { + MSG("Rejecting cookie for %s from host %s path %s\n", + cookie->domain, url_host, url_path); + Cookies_free_cookie(cookie); + } + } + dList_free(list); + } +} + +/* + * Compare the cookie with the supplied data to see if it matches + */ +static bool_t Cookies_match(CookieData_t *cookie, int port, + const char *path, bool_t is_ssl) +{ + void *data; + int i; + + /* Insecure cookies matches both secure and insecure urls, secure + cookies matches only secure urls */ + if (cookie->secure && !is_ssl) + return FALSE; + + /* Check that the cookie path is a subpath of the current path */ + if (strncmp(cookie->path, path, strlen(cookie->path)) != 0) + return FALSE; + + /* Check if the port of the request URL matches any + * of those set in the cookie */ + if (cookie->ports) { + for (i = 0; (data = dList_nth_data(cookie->ports, i)); ++i) { + if (VOIDP2INT(data) == port) + return TRUE; + } + return FALSE; + } + + /* It's a match */ + return TRUE; +} + +/* + * Return a string that contains all relevant cookies as headers. + */ +char *Cookies_get(char *url_host, char *url_path, + char *url_scheme, int url_port) +{ + char *domain_str, *q, *str, *path; + CookieData_t *cookie; + Dlist *matching_cookies; + CookieNode *node; + Dlist *domain_cookies; + bool_t is_ssl; + Dstr *cookie_dstring; + int i; + + if (disabled) + return dStrdup(""); + + matching_cookies = dList_new(8); + + path = Cookies_strip_path(url_path); + + /* Check if the protocol is secure or not */ + is_ssl = (!dStrcasecmp(url_scheme, "https")); + + for (domain_str = (char *) url_host; + domain_str != NULL && *domain_str; + domain_str = strchr(domain_str+1, '.')) { + + node = dList_find_sorted(cookies, domain_str, Cookie_node_by_domain_cmp); + domain_cookies = (node) ? node->dlist : NULL; + + for (i = 0; (cookie = dList_nth_data(domain_cookies, i)); ++i) { + /* Remove expired cookie. */ + if (!cookie->session_only && cookie->expires_at < time(NULL)) { + Cookies_remove_cookie(cookie); + --i; continue; + } + /* Check if the cookie matches the requesting URL */ + if (Cookies_match(cookie, url_port, path, is_ssl)) { + dList_append(matching_cookies, cookie); + } + } + } + + /* Found the cookies, now make the string */ + cookie_dstring = dStr_new(""); + if (dList_length(matching_cookies) > 0) { + CookieData_t *first_cookie = dList_nth_data(matching_cookies, 0); + + dStr_sprintfa(cookie_dstring, "Cookie: "); + + if (first_cookie->version != 0) + dStr_sprintfa(cookie_dstring, "$Version=\"%d\"; ", + first_cookie->version); + + + for (i = 0; (cookie = dList_nth_data(matching_cookies, i)); ++i) { + q = (cookie->version == 0 ? "" : "\""); + dStr_sprintfa(cookie_dstring, + "%s=%s; $Path=%s%s%s; $Domain=%s%s%s", + cookie->name, cookie->value, + q, cookie->path, q, q, cookie->domain, q); + if (cookie->ports) { + char *ports_str = Cookies_build_ports_str(cookie); + dStr_sprintfa(cookie_dstring, "; $Port=%s", ports_str); + dFree(ports_str); + } + + dStr_append(cookie_dstring, + dList_length(matching_cookies) > i + 1 ? "; " : "\r\n"); + } + } + + dList_free(matching_cookies); + dFree(path); + str = cookie_dstring->str; + dStr_free(cookie_dstring, FALSE); + return str; +} + +/* ------------------------------------------------------------- + * Access control routines + * ------------------------------------------------------------- */ + + +/* + * Get the cookie control rules (from cookiesrc). + * Return value: + * 0 = Parsed OK, with cookies enabled + * 1 = Parsed OK, with cookies disabled + * 2 = Can't open the control file + */ +static int Cookie_control_init(void) +{ + CookieControl cc; + FILE *stream; + char *filename; + char line[LINE_MAXLEN]; + char domain[LINE_MAXLEN]; + char rule[LINE_MAXLEN]; + int i, j; + bool_t enabled = FALSE; + + /* Get a file pointer */ + filename = dStrconcat(dGethomedir(), "/.dillo/cookiesrc", NULL); + stream = Cookies_fopen(filename, "DEFAULT DENY\n"); + dFree(filename); + + if (!stream) + return 2; + + /* Get all lines in the file */ + while (!feof(stream)) { + line[0] = '\0'; + fgets(line, LINE_MAXLEN, stream); + + /* Remove leading and trailing whitespaces */ + dStrstrip(line); + + if (line[0] != '\0' && line[0] != '#') { + i = 0; + j = 0; + + /* Get the domain */ + while (!isspace(line[i])) + domain[j++] = line[i++]; + domain[j] = '\0'; + + /* Skip past whitespaces */ + i++; + while (isspace(line[i])) + i++; + + /* Get the rule */ + j = 0; + while (line[i] != '\0' && !isspace(line[i])) + rule[j++] = line[i++]; + rule[j] = '\0'; + + if (dStrcasecmp(rule, "ACCEPT") == 0) + cc.action = COOKIE_ACCEPT; + else if (dStrcasecmp(rule, "ACCEPT_SESSION") == 0) + cc.action = COOKIE_ACCEPT_SESSION; + else if (dStrcasecmp(rule, "DENY") == 0) + cc.action = COOKIE_DENY; + else { + MSG("Cookies: rule '%s' for domain '%s' is not recognised.\n", + rule, domain); + continue; + } + + cc.domain = dStrdup(domain); + if (dStrcasecmp(cc.domain, "DEFAULT") == 0) { + /* Set the default action */ + default_action = cc.action; + dFree(cc.domain); + } else { + a_List_add(ccontrol, num_ccontrol, num_ccontrol_max); + ccontrol[num_ccontrol++] = cc; + } + + if (cc.action != COOKIE_DENY) + enabled = TRUE; + } + } + + fclose(stream); + + return (enabled ? 0 : 1); +} + +/* + * Check the rules for an appropriate action for this domain + */ +static CookieControlAction Cookies_control_check_domain(const char *domain) +{ + int i, diff; + + for (i = 0; i < num_ccontrol; i++) { + if (ccontrol[i].domain[0] == '.') { + diff = strlen(domain) - strlen(ccontrol[i].domain); + if (diff >= 0) { + if (dStrcasecmp(domain + diff, ccontrol[i].domain) != 0) + continue; + } else { + continue; + } + } else { + if (dStrcasecmp(domain, ccontrol[i].domain) != 0) + continue; + } + + /* If we got here we have a match */ + return( ccontrol[i].action ); + } + + return default_action; +} + +/* -- Dpi parser ----------------------------------------------------------- */ + +/* + * Parse a data stream (dpi protocol) + * Note: Buf is a zero terminated string + * Return code: { 0:OK, 1:Abort, 2:Close } + */ +static int srv_parse_buf(SockHandler *sh, char *Buf, size_t BufSize) +{ + char *p, *cmd, *cookie, *host, *path, *scheme; + int port; + + if (!(p = strchr(Buf, '>'))) { + /* Haven't got a full tag */ + MSG("Haven't got a full tag!\n"); + return 1; + } + + cmd = a_Dpip_get_attr(Buf, BufSize, "cmd"); + + if (cmd && strcmp(cmd, "DpiBye") == 0) { + dFree(cmd); + MSG("Cookies dpi (pid %d): Got DpiBye.\n", (int)getpid()); + exit(0); + + } else if (cmd && strcmp(cmd, "set_cookie") == 0) { + dFree(cmd); + cookie = a_Dpip_get_attr(Buf, BufSize, "cookie"); + host = a_Dpip_get_attr(Buf, BufSize, "host"); + path = a_Dpip_get_attr(Buf, BufSize, "path"); + p = a_Dpip_get_attr(Buf, BufSize, "port"); + port = strtol(p, NULL, 10); + dFree(p); + + Cookies_set(cookie, host, path, port); + + dFree(path); + dFree(host); + dFree(cookie); + return 2; + + } else if (cmd && strcmp(cmd, "get_cookie") == 0) { + dFree(cmd); + scheme = a_Dpip_get_attr(Buf, BufSize, "scheme"); + host = a_Dpip_get_attr(Buf, BufSize, "host"); + path = a_Dpip_get_attr(Buf, BufSize, "path"); + p = a_Dpip_get_attr(Buf, BufSize, "port"); + port = strtol(p, NULL, 10); + dFree(p); + + cookie = Cookies_get(host, path, scheme, port); + dFree(scheme); + dFree(path); + dFree(host); + + cmd = a_Dpip_build_cmd("cmd=%s cookie=%s", "get_cookie_answer", cookie); + + if (sock_handler_write_str(sh, 1, cmd)) { + dFree(cookie); + dFree(cmd); + return 1; + } + dFree(cookie); + dFree(cmd); + + return 2; + } + + return 0; +} + +/* -- Termination handlers ----------------------------------------------- */ +/* + * (was to delete the local namespace socket), + * but this is handled by 'dpid' now. + */ +static void cleanup(void) +{ + Cookies_save_and_free(); + MSG("cleanup\n"); + /* no more cleanup required */ +} + +/* + * Perform any necessary cleanups upon abnormal termination + */ +static void termination_handler(int signum) +{ + exit(signum); +} + + +/* + * -- MAIN ------------------------------------------------------------------- + */ +int main (void) { + struct sockaddr_un spun; + int temp_sock_descriptor; + socklen_t address_size; + char *buf; + int code; + SockHandler *sh; + + /* Arrange the cleanup function for terminations via exit() */ + atexit(cleanup); + + /* Arrange the cleanup function for abnormal terminations */ + if (signal (SIGINT, termination_handler) == SIG_IGN) + signal (SIGINT, SIG_IGN); + if (signal (SIGHUP, termination_handler) == SIG_IGN) + signal (SIGHUP, SIG_IGN); + if (signal (SIGTERM, termination_handler) == SIG_IGN) + signal (SIGTERM, SIG_IGN); + + Cookies_init(); + MSG("(v.1) accepting connections...\n"); + + if (disabled) + exit(1); + + /* some OSes may need this... */ + address_size = sizeof(struct sockaddr_un); + + while (1) { + temp_sock_descriptor = + accept(STDIN_FILENO, (struct sockaddr *)&spun, &address_size); + if (temp_sock_descriptor == -1) { + perror("[accept]"); + exit(1); + } + + /* create the SockHandler structure */ + sh = sock_handler_new(temp_sock_descriptor,temp_sock_descriptor,8*1024); + + while (1) { + code = 1; + if ((buf = sock_handler_read(sh)) != NULL) { + /* Let's see what we fished... */ + code = srv_parse_buf(sh, buf, strlen(buf)); + } + if (code == 1) + exit(1); + else if (code == 2) + break; + } + + _MSG("Closing SockHandler\n"); + sock_handler_close(sh); + sock_handler_free(sh); + + }/*while*/ +} + +#endif /* !DISABLE_COOKIES */ diff --git a/dpi/datauri.c b/dpi/datauri.c new file mode 100644 index 00000000..87afd2d9 --- /dev/null +++ b/dpi/datauri.c @@ -0,0 +1,323 @@ +/* + * File: datauri.c + * + * Copyright (C) 2006 Jorge Arellano Cid <jcid@dillo.org> + * + * Filter dpi for the "data:" URI scheme (RFC 2397). + * + * 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 3 of the License, or + * (at your option) any later version. + */ + +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../dpip/dpip.h" +#include "dpiutil.h" + +/* + * Debugging macros + */ +#define _MSG(...) +#define MSG(...) printf("[datauri dpi]: " __VA_ARGS__) + +/* + * Global variables + */ +static SockHandler *sh = NULL; + + + +int b64decode(unsigned char* str) +{ + unsigned char *cur, *start; + int d, dlast, phase; + unsigned char c; + static int table[256] = { + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* 00-0F */ + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* 10-1F */ + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63, /* 20-2F */ + 52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-1,-1,-1, /* 30-3F */ + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14, /* 40-4F */ + 15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1, /* 50-5F */ + -1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40, /* 60-6F */ + 41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1, /* 70-7F */ + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* 80-8F */ + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* 90-9F */ + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* A0-AF */ + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* B0-BF */ + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* C0-CF */ + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* D0-DF */ + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* E0-EF */ + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 /* F0-FF */ + }; + + d = dlast = phase = 0; + start = str; + for (cur = str; *cur != '\0'; ++cur ) + { + // jer: treat line endings as physical breaks. + //if (*cur == '\n' || *cur == '\r'){phase = dlast = 0; continue;} + d = table[(int)*cur]; + if(d != -1) + { + switch(phase) + { + case 0: + ++phase; + break; + case 1: + c = ((dlast << 2) | ((d & 0x30) >> 4)); + *str++ = c; + ++phase; + break; + case 2: + c = (((dlast & 0xf) << 4) | ((d & 0x3c) >> 2)); + *str++ = c; + ++phase; + break; + case 3: + c = (((dlast & 0x03 ) << 6) | d); + *str++ = c; + phase = 0; + break; + } + dlast = d; + } + } + *str = '\0'; + return str - start; +} + +/* Modified from src/url.c --------------------------------------------------*/ + +/* + * Given an hex octet (e.g., e3, 2F, 20), return the corresponding + * character if the octet is valid, and -1 otherwise + */ +static int Url_decode_hex_octet(const char *s) +{ + int hex_value; + char *tail, hex[3]; + + if (s && (hex[0] = s[0]) && (hex[1] = s[1])) { + hex[2] = 0; + hex_value = strtol(hex, &tail, 16); + if (tail - hex == 2) + return hex_value; + } + return -1; +} + +/* + * Parse possible hexadecimal octets in the URI path. + * Returns a new allocated string. + */ +char *a_Url_decode_hex_str(const char *str, size_t *p_sz) +{ + char *new_str, *dest; + int i, val; + + if (!str) { + *p_sz = 0; + return NULL; + } + + dest = new_str = dNew(char, strlen(str) + 1); + for (i = 0; str[i]; i++) { + *dest++ = (str[i] == '%' && (val = Url_decode_hex_octet(str+i+1)) >= 0) ? + i+=2, val : str[i]; + } + *dest = 0; + + new_str = dRealloc(new_str, sizeof(char) * (dest - new_str + 1)); + *p_sz = (size_t)(dest - new_str); + return new_str; +} + +/* end ----------------------------------------------------------------------*/ + +/* + * Send decoded data to dillo in an HTTP envelope. + */ +void send_decoded_data(const char *url, const char *mime_type, + unsigned char *data, size_t data_sz) +{ + char *d_cmd; + + /* Send dpip tag */ + d_cmd = a_Dpip_build_cmd("cmd=%s url=%s", "start_send_page", url); + sock_handler_write_str(sh, 1, d_cmd); + dFree(d_cmd); + + /* Send HTTP header. */ + sock_handler_write_str(sh, 0, "Content-type: "); + sock_handler_write_str(sh, 0, mime_type); + sock_handler_write_str(sh, 1, "\n\n"); + + /* Send message */ + sock_handler_write(sh, 0, (char *)data, data_sz); +} + +void send_failure_message(const char *url, const char *mime_type, + unsigned char *data, size_t data_sz) +{ + char *d_cmd; + char buf[1024]; + + const char *msg = +"<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN'>\n" +"<html><body>\n" +"<hr><h1>Datauri dpi</h1><hr>\n" +"<p><b>Can't parse datauri:</b><br>\n"; + const char *msg_mime_type="text/html"; + + /* Send dpip tag */ + d_cmd = a_Dpip_build_cmd("cmd=%s url=%s", "start_send_page", url); + sock_handler_write_str(sh, 1, d_cmd); + dFree(d_cmd); + + /* Send HTTP header. */ + sock_handler_write_str(sh, 0, "Content-type: "); + sock_handler_write_str(sh, 0, msg_mime_type); + sock_handler_write_str(sh, 1, "\n\n"); + + /* Send message */ + sock_handler_write_str(sh, 0, msg); + + /* send some debug info */ + snprintf(buf, 1024, "mime_type: %s<br>data size: %d<br>data: %s<br>", + mime_type, (int)data_sz, data); + sock_handler_write_str(sh, 0, buf); + + /* close page */ + sock_handler_write_str(sh, 0, "</body></html>"); +} + +/* + * Get mime type from the data URI. + * todo: there's no point in handling "charset" because current dillo + * only handles ISO-LATIN-1. The FLTK2 version (utf-8) could use it in the + * future. + */ +char *datauri_get_mime(char *url) +{ + char buf[256]; + char *mime_type = NULL, *p; + size_t len = 0; + + if (dStrncasecmp(url, "data:", 5) == 0) { + if ((p = strchr(url, ',')) && p - url < 256) { + url += 5; + len = p - url; + strncpy(buf, url, len); + buf[len] = 0; + /* strip ";base64" */ + if (len >= 7 && dStrcasecmp(buf + len - 7, ";base64") == 0) { + len -= 7; + buf[len] = 0; + } + } + + /* that's it, now handle omitted types */ + if (len == 0) { + mime_type = dStrdup("text/plain;charset=US-ASCII"); + } else if (!dStrncasecmp(buf, "charset", 7)) { + mime_type = dStrconcat("text/plain", buf, NULL); + } else { + mime_type = dStrdup(buf); + } + } + + return mime_type; +} + +/* + * Return a decoded data string. + */ +unsigned char *datauri_get_data(char *url, size_t *p_sz) +{ + char *p; + int is_base64 = 0; + unsigned char *data = NULL; + + if ((p = strchr(url, ',')) && p - url >= 12 && /* "data:;base64" */ + dStrncasecmp(p - 7, ";base64", 7) == 0) { + is_base64 = 1; + } + + if (p) { + ++p; + if (is_base64) { + data = (unsigned char *)dStrdup(p); + *p_sz = (size_t) b64decode(data); + } else { + data = (unsigned char *)a_Url_decode_hex_str(p, p_sz); + } + } else { + data = (unsigned char *)dStrdup(""); + *p_sz = 0; + } + + return data; +} + +/* + * + */ +int main(void) +{ + char *dpip_tag = NULL, *cmd = NULL, *url = NULL, *mime_type; + unsigned char *data; + size_t data_size = 0; + + /* Initialize the SockHandler */ + sh = sock_handler_new(STDIN_FILENO, STDOUT_FILENO, 8*1024); + + /* wget may need to write a temporary file... */ + chdir("/tmp"); + + /* Read the dpi command from STDIN */ + dpip_tag = sock_handler_read(sh); + MSG("[%s]\n", dpip_tag); + + cmd = a_Dpip_get_attr(dpip_tag, strlen(dpip_tag), "cmd"); + url = a_Dpip_get_attr(dpip_tag, strlen(dpip_tag), "url"); + if (!cmd || !url) { + MSG("Error, cmd=%s, url=%s\n", cmd, url); + exit (EXIT_FAILURE); + } + + /* Parse the data URI */ + mime_type = datauri_get_mime(url); + data = datauri_get_data(url, &data_size); + + MSG("mime_type: %s\n", mime_type); + MSG("data_size: %d\n", data_size); + MSG("data: {%s}\n", data); + + if (mime_type && data) { + /* good URI */ + send_decoded_data(url, mime_type, data, data_size); + } else { + /* malformed URI */ + send_failure_message(url, mime_type, data, data_size); + } + + dFree(data); + dFree(mime_type); + dFree(url); + dFree(cmd); + dFree(dpip_tag); + + /* Finish the SockHandler */ + sock_handler_close(sh); + sock_handler_free(sh); + + return 0; +} + diff --git a/dpi/downloads.cc b/dpi/downloads.cc new file mode 100644 index 00000000..b61f4914 --- /dev/null +++ b/dpi/downloads.cc @@ -0,0 +1,1133 @@ +/* + * File: downloads.cc + * + * Copyright (C) 2005 Jorge Arellano Cid <jcid@dillo.org> + * + * 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 3 of the License, or + * (at your option) any later version. + */ + +/* + * A FLTK2-based GUI for the downloads dpi (dillo plugin). + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> +#include <ctype.h> +#include <math.h> +#include <time.h> +#include <signal.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/un.h> +#include <sys/wait.h> + +#include <fltk/run.h> +#include <fltk/Window.h> +#include <fltk/Widget.h> +#include <fltk/damage.h> +#include <fltk/Box.h> +#include <fltk/draw.h> +#include <fltk/HighlightButton.h> +#include <fltk/PackedGroup.h> +#include <fltk/ScrollGroup.h> +#include <fltk/ask.h> +#include <fltk/file_chooser.h> + +#include "dpiutil.h" +#include "../dpip/dpip.h" + +using namespace fltk; + +/* + * Debugging macros + */ +#define _MSG(...) +#define MSG(...) printf("[downloads dpi]: " __VA_ARGS__) + +/* + * Internal types + */ +typedef enum { + DL_NEWFILE, + DL_CONTINUE, + DL_RENAME, + DL_OVERWRITE, + DL_ABORT +} DLAction; + +/* + * Class declarations + */ + +// ProgressBar widget -------------------------------------------------------- + +// class FL_API ProgressBar : public Widget { +class ProgressBar : public Widget { +protected: + double mMin; + double mMax; + double mPresent; + double mStep; + bool mShowPct, mShowMsg; + char mMsg[64]; + Color mTextColor; + void draw(); +public: + ProgressBar(int x, int y, int w, int h, const char *lbl = 0); + void range(double min, double max, double step = 1) { + mMin = min; mMax = max; mStep = step; + }; + void step(double step) { mPresent += step; redraw(); }; + void move(double step); + double minimum() { return mMin; } + double maximum() { return mMax; } + void minimum(double nm) { mMin = nm; }; + void maximum(double nm) { mMax = nm; }; + double position () { return mPresent; } + double step() { return mStep; } + void position(double pos) { mPresent = pos; redraw(); } + void showtext(bool st) { mShowPct = st; } + void message(char *msg) { mShowMsg = true; strncpy(mMsg,msg,63); redraw(); } + bool showtext() { return mShowPct; } + void text_color(Color col) { mTextColor = col; } + Color text_color() { return mTextColor; } +}; + +// Download-item class ------------------------------------------------------- + +class DLItem { + enum { + ST_newline, ST_number, ST_discard, ST_copy + }; + + pid_t mPid; + int LogPipe[2]; + char *shortname, *fullname; + char *target_dir; + int log_len, log_max, log_state; + char *log_text; + time_t init_time; + char **dl_argv; + time_t twosec_time, onesec_time; + int twosec_bytesize, onesec_bytesize; + int init_bytesize, curr_bytesize, total_bytesize; + int DataDone, LogDone, ForkDone, UpdatesDone, WidgetDone; + int WgetStatus; + + int gw, gh; + Group *group; + ProgressBar *prBar; + HighlightButton *prButton; + Widget *prTitle, *prGot, *prSize, *prRate, *pr_Rate, *prETA, *prETAt; + +public: + DLItem(const char *full_filename, const char *url, DLAction action); + ~DLItem(); + void child_init(); + void father_init(); + void update_size(int new_sz); + void log_text_add(char *buf, ssize_t st); + void log_text_show(); + void abort_dl(); + void prButton_cb(); + pid_t pid() { return mPid; } + void pid(pid_t p) { mPid = p; } + void child_finished(int status); + void status_msg(char *msg) { prBar->message(msg); } + Widget *get_widget() { return group; } + int widget_done() { return WidgetDone; } + void widget_done(int val) { WidgetDone = val; } + int updates_done() { return UpdatesDone; } + void updates_done(int val) { UpdatesDone = val; } + int fork_done() { return ForkDone; } + void fork_done(int val) { ForkDone = val; } + int log_done() { return LogDone; } + void log_done(int val) { LogDone = val; } + int wget_status() { return WgetStatus; } + void wget_status(int val) { WgetStatus = val; } + void update_prSize(int newsize); + void update(); +}; + +// DLItem List --------------------------------------------------------------- + +/// BUG: make dynamic +class DLItemList { + DLItem *mList[32]; + int mNum, mMax; + +public: + DLItemList() { mNum = 0; mMax = 32; } + ~DLItemList() { } + int num() { return mNum; } + void add(DLItem *i) { if (mNum < mMax) mList[mNum++] = i; } + DLItem *get(int n) { return (n >= 0 && n < mNum) ? mList[n] : NULL; } + void del(int n) { if (n >= 0 && n < mNum) mList[n] = mList[--mNum]; } +}; + +// DLWin --------------------------------------------------------------------- + +class DLWin { + DLItemList *mDList; + Window *mWin; + ScrollGroup *mScroll; + PackedGroup *mPG; + +public: + DLWin(int ww, int wh); + void add(const char *full_filename, const char *url, DLAction action); + void del(int n_item); + int num(); + int num_running(); + void listen(int req_fd); + void show() { mWin->show(); } + void hide() { mWin->hide(); } + void abort_all(); + DLAction check_filename(char **p_dl_dest); +}; + + +/* + * Global variables + */ + +// SIGCHLD mask +sigset_t mask_sigchld; + +// SIGCHLD flag +volatile sig_atomic_t caught_sigchld = 0; + +// The download window object +static class DLWin *dl_win = NULL; + + + +// ProgressBar widget -------------------------------------------------------- + +void ProgressBar::move(double step) +{ + mPresent += step; + if (mPresent > mMax) + mPresent = mMin; + redraw(); +} + +ProgressBar::ProgressBar(int x, int y, int w, int h, const char *lbl) +: Widget(x, y, w, h, lbl) +{ + mMin = mPresent = 0; + mMax = 100; + mShowPct = true; + mShowMsg = false; + box(DOWN_BOX); + selection_color(BLUE); + color(WHITE); + textcolor(RED); +} + +void ProgressBar::draw() +{ + drawstyle(style(), flags()); + if (damage() & DAMAGE_ALL) + draw_box(); + Rectangle r(w(), h()); + box()->inset(r); + if (mPresent > mMax) + mPresent = mMax; + if (mPresent < mMin) + mPresent = mMin; + double pct = (mPresent - mMin) / mMax; + + if (vertical()) { + int barHeight = int (r.h() * pct + .5); + r.y(r.y() + r.h() - barHeight); + r.h(barHeight); + } else { + r.w(int (r.w() * pct + .5)); + } + + setcolor(selection_color()); + + if (mShowPct) { + fillrect(r); + } else { + Rectangle r2(int (r.w() * pct), 0, int (w() * .1), h()); + push_clip(r2); + fillrect(r); + pop_clip(); + } + + if (mShowMsg) { + setcolor(textcolor()); + setfont(this->labelfont(), this->labelsize()); + drawtext(mMsg, Rectangle(w(), h()), ALIGN_CENTER); + } else if (mShowPct) { + char buffer[30]; + sprintf(buffer, "%d%%", int (pct * 100 + .5)); + setcolor(textcolor()); + setfont(this->labelfont(), this->labelsize()); + drawtext(buffer, Rectangle(w(), h()), ALIGN_CENTER); + } +} + + +// Download-item class ------------------------------------------------------- + +static void prButton_scb(Widget *, void *cb_data) +{ + DLItem *i = (DLItem *)cb_data; + + i->prButton_cb(); +} + +DLItem::DLItem(const char *full_filename, const char *url, DLAction action) +{ + struct stat ss; + char *p, *esc_url; + + if (pipe(LogPipe) < 0) { + MSG("pipe, %s\n", strerror(errno)); + return; + } + /* Set FD to background */ + fcntl(LogPipe[0], F_SETFL, + O_NONBLOCK | fcntl(LogPipe[0], F_GETFL)); + + fullname = strdup(full_filename); + p = strrchr(fullname, '/'); + shortname = (p) ? strdup(p + 1) : strdup("??"); + p = strrchr(full_filename, '/'); + target_dir= p ? dStrndup(full_filename,p-full_filename+1) : dStrdup("??"); + + log_len = 0; + log_max = 0; + log_state = ST_newline; + log_text = NULL; + onesec_bytesize = twosec_bytesize = curr_bytesize = init_bytesize = 0; + total_bytesize = -1; + + // Init value. Reset later, upon the first data bytes arrival + init_time = time(NULL); + + // BUG:? test a URL with ' inside. + /* escape "'" character for the shell. Is it necessary? */ + esc_url = Escape_uri_str(url, "'"); + /* avoid malicious SMTP relaying with FTP urls */ + if (dStrncasecmp(esc_url, "ftp:/", 5) == 0) + Filter_smtp_hack(esc_url); + dl_argv = new char*[8]; + int i = 0; + dl_argv[i++] = "wget"; + if (action == DL_CONTINUE) { + if (stat(fullname, &ss) == 0) + init_bytesize = (int)ss.st_size; + dl_argv[i++] = "-c"; + } + dl_argv[i++] = "--load-cookies"; + dl_argv[i++] = dStrconcat(dGethomedir(), "/.dillo/cookies.txt", NULL); + dl_argv[i++] = "-O"; + dl_argv[i++] = fullname; + dl_argv[i++] = esc_url; + dl_argv[i++] = NULL; + + DataDone = 0; + LogDone = 0; + UpdatesDone = 0; + ForkDone = 0; + WidgetDone = 0; + WgetStatus = -1; + + gw = 470, gh = 70; + group = new Group(0,0,gw,gh); + group->begin(); + prTitle = new Widget(24, 7, 290, 23, shortname); + prTitle->box(RSHADOW_BOX); + prTitle->align(ALIGN_LEFT|ALIGN_INSIDE|ALIGN_CLIP); + // Attach this 'log_text' to the tooltip + log_text_add("Target File: ", 13); + log_text_add(fullname, strlen(fullname)); + log_text_add("\n\n", 2); + + prBar = new ProgressBar(24, 40, 92, 20); + prBar->box(BORDER_BOX); // ENGRAVED_BOX + prBar->tooltip("Progress Status"); + + int ix = 122, iy = 36, iw = 50, ih = 14; + Widget *o = new Widget(ix,iy,iw,ih, "Got"); + o->box(RFLAT_BOX); + o->color((Color)0xc0c0c000); + o->tooltip("Downloaded Size"); + prGot = new Widget(ix,iy+14,iw,ih, "0KB"); + prGot->align(ALIGN_CENTER|ALIGN_INSIDE); + prGot->labelcolor((Color)0x6c6cbd00); + prGot->box(NO_BOX); + + ix += iw; + o = new Widget(ix,iy,iw,ih, "Size"); + o->box(RFLAT_BOX); + o->color((Color)0xc0c0c000); + o->tooltip("Total Size"); + prSize = new Widget(ix,iy+14,iw,ih, "??"); + prSize->align(ALIGN_CENTER|ALIGN_INSIDE); + prSize->box(NO_BOX); + + ix += iw; + o = new Widget(ix,iy,iw,ih, "Rate"); + o->box(RFLAT_BOX); + o->color((Color)0xc0c0c000); + o->tooltip("Current transfer Rate (KBytes/sec)"); + prRate = new Widget(ix,iy+14,iw,ih, "??"); + prRate->align(ALIGN_CENTER|ALIGN_INSIDE); + prRate->box(NO_BOX); + + ix += iw; + o = new Widget(ix,iy,iw,ih, "~Rate"); + o->box(RFLAT_BOX); + o->color((Color)0xc0c0c000); + o->tooltip("Average transfer Rate (KBytes/sec)"); + pr_Rate = new Widget(ix,iy+14,iw,ih, "??"); + pr_Rate->align(ALIGN_CENTER|ALIGN_INSIDE); + pr_Rate->box(NO_BOX); + + ix += iw; + prETAt = o = new Widget(ix,iy,iw,ih, "ETA"); + o->box(RFLAT_BOX); + o->color((Color)0xc0c0c000); + o->tooltip("Estimated Time of Arrival"); + prETA = new Widget(ix,iy+14,iw,ih, "??"); + prETA->align(ALIGN_CENTER|ALIGN_INSIDE); + prETA->box(NO_BOX); + + //ix += 50; + //prButton = new HighlightButton(ix, 41, 38, 19, "Stop"); + prButton = new HighlightButton(328, 9, 38, 19, "Stop"); + prButton->tooltip("Stop this transfer"); + prButton->box(UP_BOX); + prButton->clear_tab_to_focus(); + prButton->callback(prButton_scb, this); + + //group->resizable(group); + group->box(ROUND_UP_BOX); + group->end(); +} + +DLItem::~DLItem() +{ + free(shortname); + dFree(fullname); + dFree(target_dir); + free(log_text); + int idx = (strcmp(dl_argv[1], "-c")) ? 2 : 3; + dFree(dl_argv[idx]); + dFree(dl_argv[idx+3]); + delete(dl_argv); + + delete(group); +} + +/* + * Abort a running download + */ +void DLItem::abort_dl() +{ + if (!log_done()) { + close(LogPipe[0]); + remove_fd(LogPipe[0]); + log_done(1); + // Stop wget + if (!fork_done()) + kill(pid(), SIGTERM); + } + widget_done(1); +} + +void DLItem::prButton_cb() +{ + prButton->deactivate(); + abort_dl(); +} + +void DLItem::child_init() +{ + close(0); // stdin + close(1); // stdout + close(LogPipe[0]); + dup2(LogPipe[1], 2); // stderr + // set the locale to C for log parsing + setenv("LC_ALL", "C", 1); + // start wget + execvp(dl_argv[0], dl_argv); +} + +/* + * Update displayed size + */ +void DLItem::update_prSize(int newsize) +{ + char num[64]; + + if (newsize > 1024 * 1024) + snprintf(num, 64, "%.1fMB", (float)newsize / (1024*1024)); + else + snprintf(num, 64, "%.0fKB", (float)newsize / 1024); + prSize->copy_label(num); + prSize->redraw_label(); +} + +void DLItem::log_text_add(char *buf, ssize_t st) +{ + char *p, *q, *d, num[64]; + + // Make room... + if (log_len + st >= log_max) { + log_max = log_len + st + 1024; + log_text = (char *) realloc (log_text, log_max); + log_text[log_len] = 0; + prTitle->tooltip(log_text); + } + + // FSM to remove wget's "dot-progress" (i.e. "^ " || "^[0-9]+K") + q = log_text + log_len; + for (p = buf; (p - buf) < st; ++p) { + switch (log_state) { + case ST_newline: + if (*p == ' ') { + log_state = ST_discard; + } else if (isdigit(*p)) { + *q++ = *p; log_state = ST_number; + } else if (*p == '\n') { + *q++ = *p; + } else { + *q++ = *p; log_state = ST_copy; + } + break; + case ST_number: + if (isdigit(*q++ = *p)) { + // keep here + } else if (*p == 'K') { + for(--q; isdigit(q[-1]); --q); log_state = ST_discard; + } else { + log_state = ST_copy; + } + break; + case ST_discard: + if (*p == '\n') + log_state = ST_newline; + break; + case ST_copy: + if ((*q++ = *p) == '\n') + log_state = ST_newline; + break; + } + } + *q = 0; + log_len = strlen(log_text); + + // Now scan for the length of the file + if (total_bytesize == -1) { + p = strstr(log_text, "\nLength: "); + if (p && isdigit(p[9]) && strchr(p + 9, ' ')) { + for (p += 9, d = &num[0]; *p != ' '; ++p) + if (isdigit(*p)) + *d++ = *p; + *d = 0; + total_bytesize = strtol (num, NULL, 10); + // Update displayed size + update_prSize(total_bytesize); + } + } + + // Show we're connecting... + if (curr_bytesize == 0) { + prTitle->label("Connecting..."); + prTitle->redraw_label(); + } +} + +/// +void DLItem::log_text_show() +{ + MSG("\nStored Log:\n%s", log_text); +} + +void DLItem::update_size(int new_sz) +{ + char buf[64]; + + if (curr_bytesize == 0 && new_sz) { + // Start the timer with the first bytes got + init_time = time(NULL); + // Update the title + prTitle->label(shortname); + prTitle->redraw_label(); + } + + curr_bytesize = new_sz; + if (curr_bytesize > 1024 * 1024) + snprintf(buf, 64, "%.1fMB", (float)curr_bytesize / (1024*1024)); + else + snprintf(buf, 64, "%.0fKB", (float)curr_bytesize / 1024); + prGot->copy_label(buf); + prGot->redraw_label(); + if (total_bytesize == -1) { + prBar->showtext(false); + prBar->move(1); + } else { + prBar->showtext(true); + double pos = 100.0 * (double)curr_bytesize / total_bytesize; + prBar->position(pos); + } +} + +static void read_log_cb(int fd_in, void *data) +{ + DLItem *dl_item = (DLItem *)data; + int BufLen = 4096; + char Buf[BufLen]; + ssize_t st; + int ret = -1; + + do { + st = read(fd_in, Buf, BufLen); + if (st < 0) { + if (errno == EAGAIN) { + ret = 1; + break; + } + perror("read, "); + break; + } else if (st == 0) { + close(fd_in); + remove_fd(fd_in, 1); + dl_item->log_done(1); + ret = 0; + break; + } else { + dl_item->log_text_add(Buf, st); + } + } while (1); +} + +void DLItem::father_init() +{ + close(LogPipe[1]); + add_fd(LogPipe[0], 1, read_log_cb, this); // Read + + // Start the timer after the child is running. + // (this makes a big difference with wget) + //init_time = time(NULL); +} + +/* + * Our wget exited, let's check its status and update the panel. + */ +void DLItem::child_finished(int status) +{ + wget_status(status); + + if (status == 0) { + prButton->label("Done"); + prButton->tooltip("Close this information panel"); + } else { + prButton->label("Close"); + prButton->tooltip("Close this information panel"); + status_msg("ABORTED"); + if (curr_bytesize == 0) { + // Update the title + prTitle->label(shortname); + prTitle->redraw_label(); + } + } + prButton->activate(); + prButton->redraw(); + MSG("wget status %d\n", status); +} + +/* + * Convert seconds into human readable [hour]:[min]:[sec] string. + */ +void secs2timestr(int et, char *str) +{ + int eh, em, es; + + eh = et / 3600; em = (et % 3600) / 60; es = et % 60; + if (eh == 0) { + if (em == 0) + snprintf(str, 8, "%ds", es); + else + snprintf(str, 8, "%dm%ds", em, es); + } else { + snprintf(str, 8, "%dh%dm", eh, em); + } +} + +/* + * Update Got, Rate, ~Rate and ETA + */ +void DLItem::update() +{ + struct stat ss; + time_t curr_time; + float csec, tsec, rate, _rate = 0; + char str[64]; + int et; + + if (updates_done()) + return; + + /* Update curr_size */ + if (stat(fullname, &ss) == -1) { + MSG("stat, %s\n", strerror(errno)); + return; + } + update_size((int)ss.st_size); + + /* Get current time */ + time(&curr_time); + csec = (float) (curr_time - init_time); + + /* Rate */ + if (csec >= 2) { + tsec = (float) (curr_time - twosec_time); + rate = ((float)(curr_bytesize-twosec_bytesize) / 1024) / tsec; + snprintf(str, 64, (rate < 100) ? "%.1fK/s" : "%.0fK/s", rate); + prRate->copy_label(str); + prRate->redraw_label(); + } + /* ~Rate */ + if (csec >= 1) { + _rate = ((float)(curr_bytesize-init_bytesize) / 1024) / csec; + snprintf(str, 64, (_rate < 100) ? "%.1fK/s" : "%.0fK/s", _rate); + pr_Rate->copy_label(str); + pr_Rate->redraw_label(); + } + + /* ETA */ + if (fork_done()) { + updates_done(1); // Last update + prETAt->label("Time"); + prETAt->tooltip("Download Time"); + prETAt->redraw(); + secs2timestr((int)csec, str); + prETA->copy_label(str); + if (total_bytesize == -1) { + update_prSize(curr_bytesize); + if (wget_status() == 0) + status_msg("Done"); + } + } else { + if (_rate > 0 && total_bytesize > 0 && curr_bytesize > 0) { + et = (int)((total_bytesize-curr_bytesize) / (_rate * 1024)); + secs2timestr(et, str); + prETA->copy_label(str); + } + } + prETA->redraw_label(); + + /* Update one and two secs ago times and bytesizes */ + twosec_time = onesec_time; + onesec_time = curr_time; + twosec_bytesize = onesec_bytesize; + onesec_bytesize = curr_bytesize; +} + +// SIGCHLD ------------------------------------------------------------------- + +/*! SIGCHLD handler + */ +void raw_sigchld(int) +{ + caught_sigchld = 1; +} + +/*! Establish SIGCHLD handler */ +void est_sigchld(void) +{ + struct sigaction sigact; + sigset_t set; + + (void) sigemptyset(&set); + sigact.sa_handler = raw_sigchld; + sigact.sa_mask = set; + sigact.sa_flags = SA_NOCLDSTOP; + if (sigaction(SIGCHLD, &sigact, NULL) == -1) { + perror("sigaction"); + exit(1); + } +} + +/* + * Timeout function to check wget's exit status. + */ +void cleanup_cb(void *data) +{ + DLItemList *list = (DLItemList *)data; + + sigprocmask(SIG_BLOCK, &mask_sigchld, NULL); + if (caught_sigchld) { + /* Handle SIGCHLD */ + int i, status; + for (i = 0; i < list->num(); ++i) { + if (!list->get(i)->fork_done() && + waitpid(list->get(i)->pid(), &status, WNOHANG) > 0) { + list->get(i)->child_finished(status); + list->get(i)->fork_done(1); + } + } + caught_sigchld = 0; + } + sigprocmask(SIG_UNBLOCK, &mask_sigchld, NULL); + + repeat_timeout(1.0,cleanup_cb,data); +} + +/* + * Timeout function to update the widget indicators, + * also remove widgets marked "done". + */ +void update_cb(void *data) +{ + static int cb_used = 0; + + DLItemList *list = (DLItemList *)data; + + /* Update the widgets and remove the ones marked as done */ + for (int i = 0; i < list->num(); ++i) { + if (!list->get(i)->widget_done()) { + list->get(i)->update(); + } else if (list->get(i)->fork_done()) { + // widget_done and fork_done avoid a race condition. + dl_win->del(i); --i; + } + cb_used = 1; + } + + if (cb_used && list->num() == 0) + exit(0); + + repeat_timeout(1.0,update_cb,data); +} + + +// DLWin --------------------------------------------------------------------- + +/* + * Read a single line from a socket and store it in a Dstr. + */ +static ssize_t readline(int socket, Dstr ** msg) +{ + ssize_t st; + char buf[16384]; + + /* can't use fread() */ + do + st = read(socket, buf, 16384); + while (st < 0 && errno == EINTR); + + if (st == -1) + MSG("readline, %s\n", strerror(errno)); + + dStr_truncate(*msg, 0); + if (st > 0) + dStr_append_l(*msg, buf, (int)st); + + return st; +} + +/* + * Make a new name and place it in 'dl_dest'. + */ +static void make_new_name(char **dl_dest, const char *url) +{ + Dstr *gstr = dStr_new(*dl_dest); + int idx = gstr->len; + + if (gstr->str[idx - 1] != '/'){ + dStr_append_c(gstr, '/'); + ++idx; + } + + /* Use a mangled url as name */ + dStr_append(gstr, url); + for ( ; idx < gstr->len; ++idx) + if (!isalnum(gstr->str[idx])) + gstr->str[idx] = '_'; + + /* free memory */ + dFree(*dl_dest); + *dl_dest = gstr->str; + dStr_free(gstr, FALSE); +} + +/* + * Callback function for the request socket. + * Read the request, parse and start a new download. + */ +static void read_req_cb(int req_fd, void *) +{ + Dstr *tag; + struct sockaddr_un clnt_addr; + int new_socket; + socklen_t csz; + struct stat sb; + char *cmd = NULL, *url = NULL, *dl_dest = NULL; + DLAction action = DL_ABORT; /* compiler happiness */ + + /* Initialize the value-result parameter */ + csz = sizeof(struct sockaddr_un); + /* accept the request */ + do { + new_socket = accept(req_fd, (struct sockaddr *) &clnt_addr, &csz); + } while (new_socket == -1 && errno == EINTR); + if (new_socket == -1) { + MSG("accept, %s fd=%d\n", strerror(errno), req_fd); + return; + } + + //sigprocmask(SIG_BLOCK, &blockSC, NULL); + tag = dStr_sized_new(64); + readline(new_socket, &tag); + close(new_socket); + _MSG("Received tag={%s}\n", tag->str); + + if ((cmd = a_Dpip_get_attr(tag->str, (size_t)tag->len, "cmd")) == NULL) { + MSG("Failed to parse 'cmd' in %s\n", tag->str); + goto end; + } + if (strcmp(cmd, "DpiBye") == 0) { + MSG("got DpiBye, ignoring...\n"); + goto end; + } + if (strcmp(cmd, "download") != 0) { + MSG("unknown command: '%s'. Aborting.\n", cmd); + goto end; + } + if (!(url = a_Dpip_get_attr(tag->str,(size_t)tag->len, "url"))){ + MSG("Failed to parse 'url' in %s\n", tag->str); + goto end; + } + if (!(dl_dest = a_Dpip_get_attr(tag->str,(size_t)tag->len,"destination"))){ + MSG("Failed to parse 'destination' in %s\n", tag->str); + goto end; + } + /* 'dl_dest' may be a directory */ + if (stat(dl_dest, &sb) == 0 && S_ISDIR(sb.st_mode)) { + make_new_name(&dl_dest, url); + } + action = dl_win->check_filename(&dl_dest); + if (action != DL_ABORT) { + // Start the whole thing whithin FLTK. + dl_win->add(dl_dest, url, action); + } else if (dl_win->num() == 0) { + exit(0); + } + +end: + dFree(cmd); + dFree(url); + dFree(dl_dest); + dStr_free(tag, TRUE); +} + +/* + * Callback for close window request (WM or EscapeKey press) + */ +static void dlwin_esc_cb(Widget *, void *) +{ + char *msg = "There are running downloads.\n" + "ABORT them and EXIT anyway?"; + + if (dl_win && dl_win->num_running() > 0) { + int ch = fltk::choice(msg, "Yes", "*No", "Cancel"); + if (ch != 0) + return; + } + + /* abort each download properly */ + dl_win->abort_all(); +} + +/* + * Add a new download request to the main window and + * fork a child to do the job. + */ +void DLWin::add(const char *full_filename, const char *url, DLAction action) +{ + DLItem *dl_item = new DLItem(full_filename, url, action); + mDList->add(dl_item); + //mPG->add(*dl_item->get_widget()); + mPG->insert(*dl_item->get_widget(), 0); + + _MSG("Child index = %d\n", mPG->find(dl_item->get_widget())); + + // Start the child process + pid_t f_pid = fork(); + if (f_pid == 0) { + /* child */ + dl_item->child_init(); + _exit(EXIT_FAILURE); + } else if (f_pid < 0) { + perror("fork, "); + exit(1); + } else { + /* father */ + dl_win->show(); + dl_item->pid(f_pid); + dl_item->father_init(); + } +} + +/* + * Decide what to do when the filename already exists. + * (renaming takes place here when necessary) + */ +DLAction DLWin::check_filename(char **p_fullname) +{ + struct stat ss; + Dstr *ds; + int ch; + DLAction ret = DL_ABORT; + + if (stat(*p_fullname, &ss) == -1) + return DL_NEWFILE; + + ds = dStr_sized_new(128); + dStr_sprintf(ds, + "The file:\n %s (%d Bytes)\nalready exists. What do we do?", + *p_fullname, (int)ss.st_size); + ch = fltk::choice(ds->str, "Rename", "Continue", "Abort"); + dStr_free(ds, 1); + MSG("Choice %d\n", ch); + if (ch == 0) { + const char *p; + p = fltk::file_chooser("Enter a new name:", NULL, *p_fullname); + if (p) { + dFree(*p_fullname); + *p_fullname = dStrdup(p); + ret = check_filename(p_fullname); + } + } else if (ch == 1) { + ret = DL_CONTINUE; + } + return ret; +} + +/* + * Add a new download request to the main window and + * fork a child to do the job. + */ +void DLWin::del(int n_item) +{ + DLItem *dl_item = mDList->get(n_item); + + // Remove the widget from the scroll group + mPG->remove(dl_item->get_widget()); + // Resize the scroll group + mPG->resize(mWin->w(), 1); + + mDList->del(n_item); + delete(dl_item); +} + +/* + * Return number of entries + */ +int DLWin::num() +{ + return mDList->num(); +} + +/* + * Return number of running downloads + */ +int DLWin::num_running() +{ + int i, nr; + + for (i = nr = 0; i < mDList->num(); ++i) + if (!mDList->get(i)->fork_done()) + ++nr; + return nr; +} + +/* + * Set a callback function for the request socket + */ +void DLWin::listen(int req_fd) +{ + add_fd(req_fd, 1, read_req_cb, NULL); // Read +} + +/* + * Abort each download properly, and let the main cycle exit + */ +void DLWin::abort_all() +{ + for (int i = 0; i < mDList->num(); ++i) + mDList->get(i)->abort_dl(); +} + +/* + * Create the main window and an empty list of requests. + */ +DLWin::DLWin(int ww, int wh) { + + // Init an empty list for the download requests + mDList = new DLItemList(); + + // Create the empty main window + mWin = new Window(ww, wh, "Downloads:"); + mWin->begin(); + mScroll = new ScrollGroup(0,0,ww,wh); + mScroll->begin(); + mPG = new PackedGroup(0,0,ww,wh); + mPG->end(); + //mPG->spacing(10); + mScroll->end(); + mWin->resizable(mWin); + mWin->end(); + mWin->callback(dlwin_esc_cb, NULL); + mWin->show(); + + // Set SIGCHLD handlers + sigemptyset(&mask_sigchld); + sigaddset(&mask_sigchld, SIGCHLD); + est_sigchld(); + + // Set the cleanup timeout + add_timeout(1.0, cleanup_cb, mDList); + // Set the update timeout + add_timeout(1.0, update_cb, mDList); +} + + +// --------------------------------------------------------------------------- + + + +//int main(int argc, char **argv) +int main() +{ + int ww = 420, wh = 85; + + lock(); + + // Create the download window + dl_win = new DLWin(ww, wh); + + // Start listening to the request socket + dl_win->listen(STDIN_FILENO); + + MSG("started...\n"); + + return run(); +} + diff --git a/dpi/dpiutil.c b/dpi/dpiutil.c new file mode 100644 index 00000000..c6622850 --- /dev/null +++ b/dpi/dpiutil.c @@ -0,0 +1,270 @@ +/* + * File: dpiutil.c + * + * Copyright 2004 Jorge Arellano Cid <jcid@dillo.org> + * + * 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 3 of the License, or + * (at your option) any later version. + * + */ + +#include <unistd.h> +#include <stdio.h> +#include <stdarg.h> +#include <string.h> +#include <ctype.h> +#include <errno.h> +#include <sys/socket.h> + +#include "dpiutil.h" + +/* + * Debugging macros + */ +#define _MSG(...) +#define MSG(...) printf("[dpiutil.c]: " __VA_ARGS__) + + +/* Escaping/De-escaping ---------------------------------------------------*/ + +/* + * Escape URI characters in 'esc_set' as %XX sequences. + * Return value: New escaped string. + */ +char *Escape_uri_str(const char *str, char *p_esc_set) +{ + static const char *hex = "0123456789ABCDEF"; + char *p, *esc_set; + Dstr *dstr; + int i; + + esc_set = (p_esc_set) ? p_esc_set : "%#:' "; + dstr = dStr_sized_new(64); + for (i = 0; str[i]; ++i) { + if (str[i] <= 0x1F || str[i] == 0x7F || strchr(esc_set, str[i])) { + dStr_append_c(dstr, '%'); + dStr_append_c(dstr, hex[(str[i] >> 4) & 15]); + dStr_append_c(dstr, hex[str[i] & 15]); + } else { + dStr_append_c(dstr, str[i]); + } + } + p = dstr->str; + dStr_free(dstr, FALSE); + + return p; +} + +static const char *unsafe_chars = "&<>\"'"; +static const char *unsafe_rep[] = + { "&", "<", ">", """, "'" }; +static const int unsafe_rep_len[] = { 5, 4, 4, 6, 5 }; + +/* + * Escape unsafe characters as html entities. + * Return value: New escaped string. + */ +char *Escape_html_str(const char *str) +{ + int i; + char *p; + Dstr *dstr = dStr_sized_new(64); + + for (i = 0; str[i]; ++i) { + if ((p = strchr(unsafe_chars, str[i]))) + dStr_append(dstr, unsafe_rep[p - unsafe_chars]); + else + dStr_append_c(dstr, str[i]); + } + p = dstr->str; + dStr_free(dstr, FALSE); + + return p; +} + +/* + * Unescape a few HTML entities (inverse of Escape_html_str) + * Return value: New unescaped string. + */ +char *Unescape_html_str(const char *str) +{ + int i, j, k; + char *u_str = dStrdup(str); + + if (!strchr(str, '&')) + return u_str; + + for (i = 0, j = 0; str[i]; ++i) { + if (str[i] == '&') { + for (k = 0; k < 5; ++k) { + if (!dStrncasecmp(str + i, unsafe_rep[k], unsafe_rep_len[k])) { + i += unsafe_rep_len[k] - 1; + break; + } + } + u_str[j++] = (k < 5) ? unsafe_chars[k] : str[i]; + } else { + u_str[j++] = str[i]; + } + } + u_str[j] = 0; + + return u_str; +} + +/* + * Filter '\n', '\r', "%0D" and "%0A" from the authority part of an FTP url. + * This helps to avoid a SMTP relaying hack. This filtering could be done + * only when port == 25, but if the mail server is listening on another + * port it wouldn't work. + * Note: AFAIS this should be done by wget. + */ +char *Filter_smtp_hack(char *url) +{ + int i; + char c; + + if (strlen(url) > 6) { /* ftp:// */ + for (i = 6; (c = url[i]) && c != '/'; ++i) { + if (c == '\n' || c == '\r') { + memmove(url + i, url + i + 1, strlen(url + i)); + --i; + } else if (c == '%' && url[i+1] == '0' && + (tolower(url[i+2]) == 'a' || tolower(url[i+2]) == 'd')) { + memmove(url + i, url + i + 3, strlen(url + i + 2)); + --i; + } + } + } + return url; +} + + +/* Streamed Sockets API (not mandatory) ----------------------------------*/ + +/* + * Create and initialize the SockHandler structure + */ +SockHandler *sock_handler_new(int fd_in, int fd_out, int flush_sz) +{ + SockHandler *sh = dNew(SockHandler, 1); + + /* init descriptors and streams */ + sh->fd_in = fd_in; + sh->fd_out = fd_out; + sh->out = fdopen(fd_out, "w"); + + /* init buffer */ + sh->buf_max = 8 * 1024; + sh->buf = dNew(char, sh->buf_max); + sh->buf_sz = 0; + sh->flush_sz = flush_sz; + + return sh; +} + +/* + * Streamed write to socket + * Return: 0 on success, 1 on error. + */ +int sock_handler_write(SockHandler *sh, int flush, + const char *Data, size_t DataSize) +{ + int retval = 1; + + /* append to buf */ + while (sh->buf_max < sh->buf_sz + DataSize) { + sh->buf_max <<= 1; + sh->buf = dRealloc(sh->buf, sh->buf_max); + } + memcpy(sh->buf + sh->buf_sz, Data, DataSize); + sh->buf_sz += DataSize; +/* + MSG("sh->buf=%p, sh->buf_sz=%d, sh->buf_max=%d, sh->flush_sz=%d\n", + sh->buf, sh->buf_sz, sh->buf_max, sh->flush_sz); +*/ +/**/ +#if 0 +{ + uint_t i; + /* Test dpip's stream handling by chopping data into characters */ + for (i = 0; i < sh->buf_sz; ++i) { + fputc(sh->buf[i], sh->out); + fflush(sh->out); + usleep(50); + } + if (i == sh->buf_sz) { + sh->buf_sz = 0; + retval = 0; + } +} +#else + /* flush data if necessary */ + if (flush || sh->buf_sz >= sh->flush_sz) { + if (sh->buf_sz && fwrite (sh->buf, sh->buf_sz, 1, sh->out) != 1) { + perror("[sock_handler_write]"); + } else { + fflush(sh->out); + sh->buf_sz = 0; + retval = 0; + } + + } else { + retval = 0; + } +#endif + return retval; +} + +/* + * Convenience function. + */ +int sock_handler_write_str(SockHandler *sh, int flush, const char *str) +{ + return sock_handler_write(sh, flush, str, strlen(str)); +} + +/* + * Return a newlly allocated string with the contents read from the socket. + */ +char *sock_handler_read(SockHandler *sh) +{ + ssize_t st; + char buf[16384]; + + /* can't use fread() */ + do + st = read(sh->fd_in, buf, 16384); + while (st < 0 && errno == EINTR); + + if (st == -1) + perror("[sock_handler_read]"); + + return (st > 0) ? dStrndup(buf, (uint_t)st) : NULL; +} + +/* + * Close this socket for reading and writing. + */ +void sock_handler_close(SockHandler *sh) +{ + /* flush before closing */ + sock_handler_write(sh, 1, "", 0); + + fclose(sh->out); + close(sh->fd_out); +} + +/* + * Free the SockHandler structure + */ +void sock_handler_free(SockHandler *sh) +{ + dFree(sh->buf); + dFree(sh); +} + +/* ------------------------------------------------------------------------ */ + diff --git a/dpi/dpiutil.h b/dpi/dpiutil.h new file mode 100644 index 00000000..b8efbb28 --- /dev/null +++ b/dpi/dpiutil.h @@ -0,0 +1,97 @@ +/* + * File: dpiutil.h + * + * Copyright 2004-2005 Jorge Arellano Cid <jcid@dillo.org> + * + * 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 file contains common functions used by dpi programs. + * (i.e. a convenience library). + */ + +#ifndef __DPIUTIL_H__ +#define __DPIUTIL_H__ + +#include <stdio.h> +#include "d_size.h" +#include "../dlib/dlib.h" + + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +#define BUFLEN 256 +#define TOUT 300 + + +/* Streamed Sockets API (not mandatory) ----------------------------------*/ + +typedef struct _SockHandler SockHandler; +struct _SockHandler { + int fd_in; + int fd_out; + /* FILE *in; --Unused. The stream functions block when reading. */ + FILE *out; + + char *buf; /* internal buffer */ + uint_t buf_sz; /* data size */ + uint_t buf_max; /* allocated size */ + uint_t flush_sz; /* max size before flush */ +}; + +SockHandler *sock_handler_new(int fd_in, int fd_out, int flush_sz); +int sock_handler_write(SockHandler *sh, int flush, + const char *Data,size_t DataSize); +int sock_handler_write_str(SockHandler *sh, int flush, const char *str); +char *sock_handler_read(SockHandler *sh); +void sock_handler_close(SockHandler *sh); +void sock_handler_free(SockHandler *sh); + +#define sock_handler_printf(sh, flush, ...) \ + D_STMT_START { \ + Dstr *dstr = dStr_sized_new(128); \ + dStr_sprintf(dstr, __VA_ARGS__); \ + sock_handler_write(sh, flush, dstr->str, dstr->len); \ + dStr_free(dstr, 1); \ + } D_STMT_END + +/* ----------------------------------------------------------------------- */ + +/* + * Escape URI characters in 'esc_set' as %XX sequences. + * Return value: New escaped string. + */ +char *Escape_uri_str(const char *str, char *p_esc_set); + +/* + * Escape unsafe characters as html entities. + * Return value: New escaped string. + */ +char *Escape_html_str(const char *str); + +/* + * Unescape a few HTML entities (inverse of Escape_html_str) + * Return value: New unescaped string. + */ +char *Unescape_html_str(const char *str); + +/* + * Filter an SMTP hack with a FTP URI + */ +char *Filter_smtp_hack(char *url); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __DPIUTIL_H__ */ + diff --git a/dpi/file.c b/dpi/file.c new file mode 100644 index 00000000..e1547c66 --- /dev/null +++ b/dpi/file.c @@ -0,0 +1,975 @@ +/* + * File: file.c :) + * + * Copyright (C) 2000 - 2004 Jorge Arellano Cid <jcid@dillo.org> + * + * 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 3 of the License, or + * (at your option) any later version. + */ + +/* + * Directory scanning is no longer streamed, but it gets sorted instead! + * Directory entries on top, files next. + * With new HTML layout. + */ + +#include <pthread.h> + +#include <ctype.h> /* for tolower */ +#include <errno.h> /* for errno */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/time.h> +#include <sys/un.h> +#include <dirent.h> +#include <fcntl.h> +#include <time.h> +#include <signal.h> + +#include "../dpip/dpip.h" +#include "dpiutil.h" +#include "d_size.h" + +/* + * Debugging macros + */ +#define _MSG(...) +#define MSG(...) printf("[file dpi]: " __VA_ARGS__) + + +#define MAXNAMESIZE 30 +#define HIDE_DOTFILES TRUE + +enum { + FILE_OK, + FILE_NOT_FOUND, + FILE_NO_ACCESS +}; + +typedef struct { + char *full_path; + const char *filename; + off_t size; + mode_t mode; + time_t mtime; +} FileInfo; + +typedef struct { + char *dirname; + Dlist *flist; /* List of files and subdirectories (for sorting) */ +} DilloDir; + +typedef struct { + SockHandler *sh; + int status; + int old_style; + pthread_t thrID; + int done; +} ClientInfo; + +/* + * Forward references + */ +static const char *File_content_type(const char *filename); +static int File_get_file(ClientInfo *Client, + const char *filename, + struct stat *sb, + const char *orig_url); +static int File_get_dir(ClientInfo *Client, + const char *DirName, + const char *orig_url); + +/* + * Global variables + */ +static volatile int DPIBYE = 0; +static volatile int ThreadRunning = 0; +static int OLD_STYLE = 0; +/* A list for the clients we are serving */ +static Dlist *Clients; +/* a mutex for operations on clients */ +static pthread_mutex_t ClMut; + +/* + * Close a file descriptor, but handling EINTR + */ +static void File_close(int fd) +{ + while (close(fd) < 0 && errno == EINTR) + ; +} + +/* + * Detects 'Content-Type' when the server does not supply one. + * It uses the magic(5) logic from file(1). Currently, it + * only checks the few mime types that Dillo supports. + * + * 'Data' is a pointer to the first bytes of the raw data. + * (this is based on a_Misc_get_content_type_from_data()) + */ +static const char *File_get_content_type_from_data(void *Data, size_t Size) +{ + static const char *Types[] = { + "application/octet-stream", + "text/html", "text/plain", + "image/gif", "image/png", "image/jpeg", + }; + int Type = 0; + char *p = Data; + size_t i, non_ascci; + + _MSG("File_get_content_type_from_data:: Size = %d\n", Size); + + /* HTML try */ + for (i = 0; i < Size && isspace(p[i]); ++i); + if ((Size - i >= 5 && !dStrncasecmp(p+i, "<html", 5)) || + (Size - i >= 5 && !dStrncasecmp(p+i, "<head", 5)) || + (Size - i >= 6 && !dStrncasecmp(p+i, "<title", 6)) || + (Size - i >= 14 && !dStrncasecmp(p+i, "<!doctype html", 14)) || + /* this line is workaround for FTP through the Squid proxy */ + (Size - i >= 17 && !dStrncasecmp(p+i, "<!-- HTML listing", 17))) { + + Type = 1; + + /* Images */ + } else if (Size >= 4 && !dStrncasecmp(p, "GIF8", 4)) { + Type = 3; + } else if (Size >= 4 && !dStrncasecmp(p, "\x89PNG", 4)) { + Type = 4; + } else if (Size >= 2 && !dStrncasecmp(p, "\xff\xd8", 2)) { + /* JPEG has the first 2 bytes set to 0xffd8 in BigEndian - looking + * at the character representation should be machine independent. */ + Type = 5; + + /* Text */ + } else { + /* We'll assume "text/plain" if the set of chars above 127 is <= 10 + * in a 256-bytes sample. Better heuristics are welcomed! :-) */ + non_ascci = 0; + Size = MIN (Size, 256); + for (i = 0; i < Size; i++) + if ((uchar_t) p[i] > 127) + ++non_ascci; + if (Size == 256) { + Type = (non_ascci > 10) ? 0 : 2; + } else { + Type = (non_ascci > 0) ? 0 : 2; + } + } + + return (Types[Type]); +} + +/* + * Compare two FileInfo pointers + * This function is used for sorting directories + */ +static int File_comp(const FileInfo *f1, const FileInfo *f2) +{ + if (S_ISDIR(f1->mode)) { + if (S_ISDIR(f2->mode)) { + return strcmp(f1->filename, f2->filename); + } else { + return -1; + } + } else { + if (S_ISDIR(f2->mode)) { + return 1; + } else { + return strcmp(f1->filename, f2->filename); + } + } +} + +/* + * Allocate a DilloDir structure, set safe values in it and sort the entries. + */ +static DilloDir *File_dillodir_new(char *dirname) +{ + struct stat sb; + struct dirent *de; + DIR *dir; + DilloDir *Ddir; + FileInfo *finfo; + char *fname; + int dirname_len; + + if (!(dir = opendir(dirname))) + return NULL; + + Ddir = dNew(DilloDir, 1); + Ddir->dirname = dStrdup(dirname); + Ddir->flist = dList_new(512); + + dirname_len = strlen(Ddir->dirname); + + /* Scan every name and sort them */ + while ((de = readdir(dir)) != 0) { + if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) + continue; /* skip "." and ".." */ + + if (HIDE_DOTFILES) { + /* Don't add hidden files or backup files to the list */ + if (de->d_name[0] == '.' || + de->d_name[0] == '#' || + (de->d_name[0] != '\0' && + de->d_name[strlen(de->d_name) - 1] == '~')) + continue; + } + + fname = dStrconcat(Ddir->dirname, de->d_name, NULL); + if (stat(fname, &sb) == -1) { + dFree(fname); + continue; /* ignore files we can't stat */ + } + + finfo = dNew(FileInfo, 1); + finfo->full_path = fname; + finfo->filename = fname + dirname_len; + finfo->size = sb.st_size; + finfo->mode = sb.st_mode; + finfo->mtime = sb.st_mtime; + + dList_append(Ddir->flist, finfo); + } + + closedir(dir); + + /* sort the entries */ + dList_sort(Ddir->flist, (dCompareFunc)File_comp); + + return Ddir; +} + +/* + * Deallocate a DilloDir structure. + */ +static void File_dillodir_free(DilloDir *Ddir) +{ + int i; + FileInfo *finfo; + + for (i = 0; i < dList_length(Ddir->flist); ++i) { + finfo = dList_nth_data(Ddir->flist, i); + dFree(finfo->full_path); + dFree(finfo); + } + + dList_free(Ddir->flist); + dFree(Ddir->dirname); + dFree(Ddir); +} + +/* + * Output the string for parent directory + */ +static void File_print_parent_dir(ClientInfo *Client, const char *dirname) +{ + if (strcmp(dirname, "/") != 0) { /* Not the root dir */ + char *p, *parent, *HUparent, *Uparent; + + parent = dStrdup(dirname); + /* cut trailing slash */ + parent[strlen(parent) - 1] = '\0'; + /* make 'parent' have the parent dir path */ + if ((p = strrchr(parent, '/'))) + *(p + 1) = '\0'; + + Uparent = Escape_uri_str(parent, NULL); + HUparent = Escape_html_str(Uparent); + sock_handler_printf(Client->sh, 0, + "<a href='file:%s'>Parent directory</a>", HUparent); + dFree(HUparent); + dFree(Uparent); + dFree(parent); + } +} + +/* + * Given a timestamp, output an HTML-formatted date string. + */ +static void File_print_mtime(ClientInfo *Client, time_t mtime) +{ + char *ds = ctime(&mtime); + + /* Month, day and {hour or year} */ + if (Client->old_style) { + sock_handler_printf(Client->sh, 0, " %.3s %.2s", ds + 4, ds + 8); + if (time(NULL) - mtime > 15811200) { + sock_handler_printf(Client->sh, 0, " %.4s", ds + 20); + } else { + sock_handler_printf(Client->sh, 0, " %.5s", ds + 11); + } + } else { + sock_handler_printf(Client->sh, 0, + "<td>%.3s %.2s %.5s", ds + 4, ds + 8, + /* (more than 6 months old) ? year : hour; */ + (time(NULL) - mtime > 15811200) ? ds + 20 : ds + 11); + } +} + +/* + * Return a HTML-line from file info. + */ +static void File_info2html(ClientInfo *Client, + FileInfo *finfo, const char *dirname, int n) +{ + int size; + char *sizeunits; + char namebuf[MAXNAMESIZE + 1]; + char *Uref, *HUref, *Hname; + const char *ref, *filecont, *name = finfo->filename; + + if (finfo->size <= 9999) { + size = finfo->size; + sizeunits = "bytes"; + } else if (finfo->size / 1024 <= 9999) { + size = finfo->size / 1024 + (finfo->size % 1024 >= 1024 / 2); + sizeunits = "Kb"; + } else { + size = finfo->size / 1048576 + (finfo->size % 1048576 >= 1048576 / 2); + sizeunits = "Mb"; + } + + /* we could note if it's a symlink... */ + if S_ISDIR (finfo->mode) { + filecont = "Directory"; + } else if (finfo->mode & (S_IXUSR | S_IXGRP | S_IXOTH)) { + filecont = "Executable"; + } else { + filecont = File_content_type(finfo->full_path); + if (!filecont || !strcmp(filecont, "application/octet-stream")) + filecont = "unknown"; + } + + ref = name; + + if (strlen(name) > MAXNAMESIZE) { + memcpy(namebuf, name, MAXNAMESIZE - 3); + strcpy(namebuf + (MAXNAMESIZE - 3), "..."); + name = namebuf; + } + + /* escape problematic filenames */ + Uref = Escape_uri_str(ref, NULL); + HUref = Escape_html_str(Uref); + Hname = Escape_html_str(name); + + if (Client->old_style) { + char *dots = ".. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .."; + int ndots = MAXNAMESIZE - strlen(name); + sock_handler_printf(Client->sh, 0, + "%s<a href='%s'>%s</a>" + " %s" + " %-11s%4d %-5s", + S_ISDIR (finfo->mode) ? ">" : " ", HUref, Hname, + dots + 50 - (ndots > 0 ? ndots : 0), + filecont, size, sizeunits); + + } else { + sock_handler_printf(Client->sh, 0, + "<tr align=center %s><td>%s<td align=left><a href='%s'>%s</a>" + "<td>%s<td>%d %s", + (n & 1) ? "bgcolor=#dcdcdc" : "", + S_ISDIR (finfo->mode) ? ">" : " ", HUref, Hname, + filecont, size, sizeunits); + } + File_print_mtime(Client, finfo->mtime); + sock_handler_write_str(Client->sh, 0, "\n"); + + dFree(Hname); + dFree(HUref); + dFree(Uref); +} + +/* + * Read a local directory and translate it to html. + */ +static void File_transfer_dir(ClientInfo *Client, + DilloDir *Ddir, const char *orig_url) +{ + int n; + char *d_cmd, *Hdirname, *Udirname, *HUdirname; + + /* Send DPI header */ + d_cmd = a_Dpip_build_cmd("cmd=%s url=%s", "start_send_page", orig_url); + sock_handler_write_str(Client->sh, 1, d_cmd); + dFree(d_cmd); + + /* Send page title */ + Udirname = Escape_uri_str(Ddir->dirname, NULL); + HUdirname = Escape_html_str(Udirname); + Hdirname = Escape_html_str(Ddir->dirname); + + sock_handler_printf(Client->sh, 0, + "Content-Type: text/html\n\n" + "<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN'>\n" + "<HTML>\n<HEAD>\n <BASE href='file:%s'>\n" + " <TITLE>file:%s</TITLE>\n</HEAD>\n" + "<BODY><H1>Directory listing of %s</H1>\n", + HUdirname, Hdirname, Hdirname); + dFree(Hdirname); + dFree(HUdirname); + dFree(Udirname); + + if (Client->old_style) { + sock_handler_write_str(Client->sh, 0, "<pre>\n"); + } + + /* Output the parent directory */ + File_print_parent_dir(Client, Ddir->dirname); + + /* HTML style toggle */ + sock_handler_write_str(Client->sh, 0, + " <a href='dpi:/file/toggle'>%</a>\n"); + + if (dList_length(Ddir->flist)) { + if (Client->old_style) { + sock_handler_write_str(Client->sh, 0, "\n\n"); + } else { + sock_handler_write_str(Client->sh, 0, + "<br><br>\n" + "<table border=0 cellpadding=1 cellspacing=0" + " bgcolor=#E0E0E0 width=100%>\n" + "<tr align=center>\n" + "<td>\n" + "<td width=60%><b>Filename</b>" + "<td><b>Type</b>" + "<td><b>Size</b>" + "<td><b>Modified at</b>\n"); + } + } else { + sock_handler_write_str(Client->sh, 0, "<br><br>Directory is empty..."); + } + + /* Output entries */ + for (n = 0; n < dList_length(Ddir->flist); ++n) { + File_info2html(Client, dList_nth_data(Ddir->flist,n),Ddir->dirname,n+1); + } + + if (dList_length(Ddir->flist)) { + if (Client->old_style) { + sock_handler_write_str(Client->sh, 0, "</pre>\n"); + } else { + sock_handler_write_str(Client->sh, 0, "</table>\n"); + } + } + + sock_handler_write_str(Client->sh, 0, "</BODY></HTML>\n"); +} + +/* + * Return a content type based on the extension of the filename. + */ +static const char *File_ext(const char *filename) +{ + char *e; + + if (!(e = strrchr(filename, '.'))) + return NULL; + + e++; + + if (!dStrcasecmp(e, "gif")) { + return "image/gif"; + } else if (!dStrcasecmp(e, "jpg") || + !dStrcasecmp(e, "jpeg")) { + return "image/jpeg"; + } else if (!dStrcasecmp(e, "png")) { + return "image/png"; + } else if (!dStrcasecmp(e, "html") || + !dStrcasecmp(e, "htm") || + !dStrcasecmp(e, "shtml")) { + return "text/html"; + } else { + return NULL; + } +} + +/* + * Based on the extension, return the content_type for the file. + * (if there's no extension, analyze the data and try to figure it out) + */ +static const char *File_content_type(const char *filename) +{ + int fd; + struct stat sb; + const char *ct; + char buf[256]; + ssize_t buf_size; + + if (!(ct = File_ext(filename))) { + /* everything failed, let's analyze the data... */ + if ((fd = open(filename, O_RDONLY | O_NONBLOCK)) != -1) { + if ((buf_size = read(fd, buf, 256)) == 256 ) { + ct = File_get_content_type_from_data(buf, (size_t)buf_size); + + } else if (stat(filename, &sb) != -1 && + buf_size > 0 && buf_size == sb.st_size) { + ct = File_get_content_type_from_data(buf, (size_t)buf_size); + } + File_close(fd); + } + } + + return ct; +} + +/* + * Try to stat the file and determine if it's readable. + */ +static void File_get(ClientInfo *Client, const char *filename, + const char *orig_url) +{ + int res; + struct stat sb; + char *d_cmd; + Dstr *ds = NULL; + + if (stat(filename, &sb) != 0) { + /* stat failed, prepare a file-not-found error. */ + res = FILE_NOT_FOUND; + } else if (S_ISDIR(sb.st_mode)) { + /* set up for reading directory */ + res = File_get_dir(Client, filename, orig_url); + } else { + /* set up for reading a file */ + res = File_get_file(Client, filename, &sb, orig_url); + } + + if (res == FILE_NOT_FOUND) { + ds = dStr_sized_new(128); + dStr_sprintf(ds, "%s Not Found: %s", + S_ISDIR(sb.st_mode) ? "Directory" : "File", filename); + } else if (res == FILE_NO_ACCESS) { + ds = dStr_sized_new(128); + dStr_sprintf(ds, "Access denied to %s: %s", + S_ISDIR(sb.st_mode) ? "Directory" : "File", filename); + } + if (ds) { + d_cmd = a_Dpip_build_cmd("cmd=%s msg=%s","send_status_message",ds->str); + sock_handler_write_str(Client->sh, 1, d_cmd); + dFree(d_cmd); + dStr_free(ds, 1); + } +} + +/* + * + */ +static int File_get_dir(ClientInfo *Client, + const char *DirName, const char *orig_url) +{ + Dstr *ds_dirname; + DilloDir *Ddir; + + /* Let's make sure this directory url has a trailing slash */ + ds_dirname = dStr_new(DirName); + if (ds_dirname->str[ds_dirname->len - 1] != '/') + dStr_append(ds_dirname, "/"); + + /* Let's get a structure ready for transfer */ + Ddir = File_dillodir_new(ds_dirname->str); + dStr_free(ds_dirname, TRUE); + if (Ddir) { + File_transfer_dir(Client, Ddir, orig_url); + File_dillodir_free(Ddir); + return FILE_OK; + } else + return FILE_NO_ACCESS; +} + +/* + * Send the MIME content/type and then send the file itself. + */ +static int File_get_file(ClientInfo *Client, + const char *filename, + struct stat *sb, + const char *orig_url) +{ +#define LBUF 16*1024 + + const char *ct; + char buf[LBUF], *d_cmd; + int fd, st; + + if ((fd = open(filename, O_RDONLY | O_NONBLOCK)) < 0) + return FILE_NO_ACCESS; + + /* Content-Type info is based on filename extension. If there's no + * known extension, then we do data sniffing. If this doesn't lead + * to a conclusion, "application/octet-stream" is sent. + */ + if (!(ct = File_content_type(filename))) + ct = "application/octet-stream"; + + /* Send DPI command */ + d_cmd = a_Dpip_build_cmd("cmd=%s url=%s", "start_send_page", orig_url); + sock_handler_write_str(Client->sh, 1, d_cmd); + dFree(d_cmd); + + /* Send HTTP stream */ + sock_handler_printf(Client->sh, 0, + "Content-Type: %s\n" + "Content-length: %ld\n\n", + ct, sb->st_size); + + /* Send raw file contents */ + do { + if ((st = read(fd, buf, LBUF)) > 0) { + if (sock_handler_write(Client->sh, 0, buf, (size_t)st) != 0) + break; + } else if (st < 0) { + perror("[read]"); + if (errno == EINTR || errno == EAGAIN) + continue; + } + } while (st > 0); + + /* todo: It may be better to send an error report to dillo instead of + * calling exit() */ + if (st == -1) { + MSG("ERROR while reading from file \"%s\", error was \"%s\"\n", + filename, strerror(errno)); + exit(1); + } + + File_close(fd); + return FILE_OK; +} + +/* + * Given an hex octet (e3, 2F, 20), return the corresponding + * character if the octet is valid, and -1 otherwise + */ +static int File_parse_hex_octet(const char *s) +{ + int hex_value; + char *tail, hex[3]; + + if ((hex[0] = s[0]) && (hex[1] = s[1])) { + hex[2] = 0; + hex_value = strtol(hex, &tail, 16); + if (tail - hex == 2) + return hex_value; + } + + return -1; +} + +/* + * Make a file URL into a human (and machine) readable path. + * The idea is to always have a path that starts with only one slash. + * Embedded slashes are ignored. + */ +static char *File_normalize_path(const char *orig) +{ + char *str = (char *) orig, *basename = NULL, *ret = NULL, *p; + + /* Make sure the string starts with "file:/" */ + if (strncmp(str, "file:/", 5) != 0) + return ret; + str += 5; + + /* Skip "localhost" */ + if (dStrncasecmp(str, "//localhost/", 12) == 0) + str += 11; + + /* Skip packed slashes, and leave just one */ + while (str[0] == '/' && str[1] == '/') + str++; + + { + int i, val; + Dstr *ds = dStr_sized_new(32); + dStr_sprintf(ds, "%s%s%s", + basename ? basename : "", + basename ? "/" : "", + str); + dFree(basename); + + /* Parse possible hexadecimal octets in the URI path */ + for (i = 0; ds->str[i]; ++i) { + if (ds->str[i] == '%' && + (val = File_parse_hex_octet(ds->str + i+1)) != -1) { + ds->str[i] = val; + dStr_erase(ds, i+1, 2); + } + } + /* Remove the fragment if not a part of the filename */ + if ((p = strrchr(ds->str, '#')) != NULL && access(ds->str, F_OK) != 0) + dStr_truncate(ds, p - ds->str); + ret = ds->str; + dStr_free(ds, 0); + } + + return ret; +} + +/* + * Set the style flag and ask for a reload, so it shows inmediatly. + */ +static void File_toggle_html_style(ClientInfo *Client) +{ + char *d_cmd; + + OLD_STYLE = !OLD_STYLE; + d_cmd = a_Dpip_build_cmd("cmd=%s", "reload_request"); + sock_handler_write_str(Client->sh, 1, d_cmd); + dFree(d_cmd); +} + +/* + * Perform any necessary cleanups upon abnormal termination + */ +static void termination_handler(int signum) +{ + exit(signum); +} + + +/* Client handling ----------------------------------------------------------*/ + +/* + * Add a new client to the list. + */ +static ClientInfo *File_add_client(int sock_fd) +{ + ClientInfo *NewClient; + + NewClient = dNew(ClientInfo, 1); + NewClient->sh = sock_handler_new(sock_fd, sock_fd, 8*1024); + NewClient->status = 0; + NewClient->done = 0; + NewClient->old_style = OLD_STYLE; + pthread_mutex_lock(&ClMut); + dList_append(Clients, NewClient); + pthread_mutex_unlock(&ClMut); + return NewClient; +} + +/* + * Get client record by number + */ +static void *File_get_client_n(uint_t n) +{ + void *client; + + pthread_mutex_lock(&ClMut); + client = dList_nth_data(Clients, n); + pthread_mutex_unlock(&ClMut); + + return client; +} + +/* + * Remove a client from the list. + */ +static void File_remove_client_n(uint_t n) +{ + ClientInfo *Client; + + pthread_mutex_lock(&ClMut); + Client = dList_nth_data(Clients, n); + dList_remove(Clients, (void *)Client); + pthread_mutex_unlock(&ClMut); + + _MSG("Closing Socket Handler\n"); + sock_handler_close(Client->sh); + sock_handler_free(Client->sh); + dFree(Client); +} + +/* + * Return the number of clients. + */ +static int File_num_clients(void) +{ + uint_t n; + + pthread_mutex_lock(&ClMut); + n = dList_length(Clients); + pthread_mutex_unlock(&ClMut); + + return n; +} + +/* + * Serve this client. + * (this function runs on its own thread) + */ +static void *File_serve_client(void *data) +{ + char *dpip_tag, *cmd = NULL, *url = NULL, *path; + ClientInfo *Client = data; + + /* Read the dpi command */ + dpip_tag = sock_handler_read(Client->sh); + MSG("dpip_tag={%s}\n", dpip_tag); + + if (dpip_tag) { + cmd = a_Dpip_get_attr(dpip_tag, strlen(dpip_tag), "cmd"); + if (cmd) { + if (strcmp(cmd, "DpiBye") == 0) { + DPIBYE = 1; + } else { + url = a_Dpip_get_attr(dpip_tag, strlen(dpip_tag), "url"); + if (!url) + MSG("file.dpi:: Failed to parse 'url'\n"); + } + } + } + dFree(cmd); + dFree(dpip_tag); + + if (!DPIBYE && url) { + _MSG("url = '%s'\n", url); + + path = File_normalize_path(url); + if (path) { + _MSG("path = '%s'\n", path); + File_get(Client, path, url); + } else if (strcmp(url, "dpi:/file/toggle") == 0) { + File_toggle_html_style(Client); + } else { + MSG("ERROR: URL path was %s\n", url); + } + dFree(path); + } + dFree(url); + + /* flag the the transfer finished */ + Client->done = 1; + + return NULL; +} + +/* + * Serve the client queue. + * (this function runs on its own thread) + */ +static void *File_serve_clients(void *client) +{ + /* switch to detached state */ + pthread_detach(pthread_self()); + + while (File_num_clients()) { + client = File_get_client_n((uint_t)0); + File_serve_client(client); + File_remove_client_n((uint_t)0); + } + ThreadRunning = 0; + + return NULL; +} + +/* --------------------------------------------------------------------------*/ + +/* + * Check a fd for activity, with a max timeout. + * return value: 0 if timeout, 1 if input available, -1 if error. + */ +int File_check_fd(int filedes, unsigned int seconds) +{ + int st; + fd_set set; + struct timeval timeout; + + /* Initialize the file descriptor set. */ + FD_ZERO (&set); + FD_SET (filedes, &set); + + /* Initialize the timeout data structure. */ + timeout.tv_sec = seconds; + timeout.tv_usec = 0; + + do { + st = select(FD_SETSIZE, &set, NULL, NULL, &timeout); + } while (st == -1 && errno == EINTR); + + return st; +} + + +int main(void) +{ + ClientInfo *NewClient; + struct sockaddr_un spun; + int temp_sock_descriptor; + socklen_t address_size; + int c_st, st = 1; + uint_t i; + + /* Arrange the cleanup function for abnormal terminations */ + if (signal (SIGINT, termination_handler) == SIG_IGN) + signal (SIGINT, SIG_IGN); + if (signal (SIGHUP, termination_handler) == SIG_IGN) + signal (SIGHUP, SIG_IGN); + if (signal (SIGTERM, termination_handler) == SIG_IGN) + signal (SIGTERM, SIG_IGN); + + MSG("(v.1) accepting connections...\n"); + + /* initialize mutex */ + pthread_mutex_init(&ClMut, NULL); + + /* initialize Clients list */ + Clients = dList_new(512); + + /* some OSes may need this... */ + address_size = sizeof(struct sockaddr_un); + + /* start the service loop */ + while (!DPIBYE) { + /* wait for a connection */ + do { + c_st = File_check_fd(STDIN_FILENO, 1); + } while (c_st == 0 && !DPIBYE); + if (c_st < 0) { + perror("[select]"); + break; + } + if (DPIBYE) + break; + + temp_sock_descriptor = + accept(STDIN_FILENO, (struct sockaddr *)&spun, &address_size); + + if (temp_sock_descriptor == -1) { + perror("[accept]"); + break; + } + + /* Create and initialize a new client */ + NewClient = File_add_client(temp_sock_descriptor); + + if (!ThreadRunning) { + ThreadRunning = 1; + /* Serve the client from a thread (avoids deadlocks) */ + if (pthread_create(&NewClient->thrID, NULL, + File_serve_clients, NewClient) != 0) { + perror("[pthread_create]"); + ThreadRunning = 0; + break; + } + } + } + + /* todo: handle a running thread better. */ + for (i = 0; i < 5 && ThreadRunning; ++i) { + MSG("sleep i=%u", i); + sleep(i); + } + + if (DPIBYE) + st = 0; + return st; +} + diff --git a/dpi/ftp.c b/dpi/ftp.c new file mode 100644 index 00000000..36fe7498 --- /dev/null +++ b/dpi/ftp.c @@ -0,0 +1,301 @@ +/* + * Dpi for FTP. + * + * This server checks the ftp-URL to be a directory (requires wget). + * If true, it sends back an html representation of it, and if not + * a dpip message (which is catched by dillo who redirects the ftp URL + * to the downloads server). + * + * Feel free to polish! + * + * Copyright 2003-2005 Jorge Arellano Cid <jcid@dillo.org> + * + * 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 3 of the License, or + * (at your option) any later version. + * + */ + +/* + * TODO: + * - Send feedback about the FTP login process from wget's stderr. + * i.e. capture our child's stderr, process it, and report back. + * - Handle simultaneous connections. + * If ftp.dpi is implemented with a low level ftp library, it becomes + * possible to keep the connection alive, and thus make browsing of ftp + * directories faster (this avoids one login per page, and forks). Perhaps + * it's not worth, but can be done. + */ + +#include <unistd.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <signal.h> +#include <sys/wait.h> +#include <errno.h> +#include <sys/time.h> +#include <ctype.h> + +#include "../dpip/dpip.h" +#include "dpiutil.h" +#include "d_size.h" + +/* + * Debugging macros + */ +#define _MSG(...) +#define MSG(...) printf("[ftp dpi]: " __VA_ARGS__) + +/* + * Global variables + */ +static SockHandler *sh = NULL; +char **dl_argv = NULL; + +/*---------------------------------------------------------------------------*/ + +/* TODO: could use dStr ADT! */ +typedef struct ContentType_ { + const char *str; + int len; +} ContentType_t; + +static const ContentType_t MimeTypes[] = { + { "application/octet-stream", 24 }, + { "text/html", 9 }, + { "text/plain", 10 }, + { "image/gif", 9 }, + { "image/png", 9 }, + { "image/jpeg", 10 }, + { NULL, 0 } +}; + +/* + * Detects 'Content-Type' from a data stream sample. + * + * It uses the magic(5) logic from file(1). Currently, it + * only checks the few mime types that Dillo supports. + * + * 'Data' is a pointer to the first bytes of the raw data. + * + * Return value: (0 on success, 1 on doubt, 2 on lack of data). + */ +int a_Misc_get_content_type_from_data(void *Data, size_t Size, + const char **PT) +{ + int st = 1; /* default to "doubt' */ + int Type = 0; /* default to "application/octet-stream" */ + char *p = Data; + size_t i, non_ascci; + + /* HTML try */ + for (i = 0; i < Size && isspace(p[i]); ++i); + if ((Size - i >= 5 && !dStrncasecmp(p+i, "<html", 5)) || + (Size - i >= 5 && !dStrncasecmp(p+i, "<head", 5)) || + (Size - i >= 6 && !dStrncasecmp(p+i, "<title", 6)) || + (Size - i >= 14 && !dStrncasecmp(p+i, "<!doctype html", 14)) || + /* this line is workaround for FTP through the Squid proxy */ + (Size - i >= 17 && !dStrncasecmp(p+i, "<!-- HTML listing", 17))) { + + Type = 1; + st = 0; + /* Images */ + } else if (Size >= 4 && !dStrncasecmp(p, "GIF8", 4)) { + Type = 3; + st = 0; + } else if (Size >= 4 && !dStrncasecmp(p, "\x89PNG", 4)) { + Type = 4; + st = 0; + } else if (Size >= 2 && !dStrncasecmp(p, "\xff\xd8", 2)) { + /* JPEG has the first 2 bytes set to 0xffd8 in BigEndian - looking + * at the character representation should be machine independent. */ + Type = 5; + st = 0; + + /* Text */ + } else { + /* We'll assume "text/plain" if the set of chars above 127 is <= 10 + * in a 256-bytes sample. Better heuristics are welcomed! :-) */ + non_ascci = 0; + Size = MIN (Size, 256); + for (i = 0; i < Size; i++) + if ((uchar_t) p[i] > 127) + ++non_ascci; + if (Size == 256) { + Type = (non_ascci > 10) ? 0 : 2; + st = 0; + } else { + Type = (non_ascci > 0) ? 0 : 2; + } + } + + *PT = MimeTypes[Type].str; + return st; +} + +/*---------------------------------------------------------------------------*/ + +/* + * Build a shell command using wget for this URL. + */ +static void make_wget_argv(char *url) +{ + char *esc_url; + + if (dl_argv) { + dFree(dl_argv[2]); + dFree(dl_argv); + } + dl_argv = dNew(char*, 10); + + esc_url = Escape_uri_str(url, "'"); + /* avoid malicious SMTP relaying with FTP urls */ + Filter_smtp_hack(esc_url); + + dl_argv[0] = "wget"; + dl_argv[1] = "-O-"; + dl_argv[2] = esc_url; + dl_argv[3] = NULL; +} + +/* + * Fork, exec command, get its output and send via stdout. + * Return: Number of bytes transfered. + */ +static int try_ftp_transfer(char *url) +{ +#define MinSZ 256 + + ssize_t n; + int nb, minibuf_sz; + const char *mime_type; + char buf[4096], minibuf[MinSZ], *d_cmd; + pid_t ch_pid; + int aborted = 0; + int DataPipe[2]; + + if (pipe(DataPipe) < 0) { + MSG("pipe, %s\n", strerror(errno)); + return 0; + } + + /* Prepare args for execvp() */ + make_wget_argv(url); + + /* Start the child process */ + if ((ch_pid = fork()) == 0) { + /* child */ + /* start wget */ + close(DataPipe[0]); + dup2(DataPipe[1], 1); /* stdout */ + execvp(dl_argv[0], dl_argv); + _exit(1); + } else if (ch_pid < 0) { + perror("fork, "); + exit(1); + } else { + /* father continues below */ + close(DataPipe[1]); + } + + /* Read/Write the real data */ + minibuf_sz = 0; + for (nb = 0; 1; nb += n) { + while ((n = read(DataPipe[0], buf, 4096)) < 0 && errno == EINTR); + if (n <= 0) + break; + + if (minibuf_sz < MinSZ) { + memcpy(minibuf + minibuf_sz, buf, MIN(n, MinSZ - minibuf_sz)); + minibuf_sz += MIN(n, MinSZ - minibuf_sz); + if (minibuf_sz < MinSZ) + continue; + a_Misc_get_content_type_from_data(minibuf, minibuf_sz, &mime_type); + if (strcmp(mime_type, "application/octet-stream") == 0) { + /* abort transfer */ + kill(ch_pid, SIGTERM); + /* The "application/octet-stream" MIME type will be sent and + * Dillo will offer a download dialog */ + aborted = 1; + } + } + + if (nb == 0) { + /* Send dpip tag */ + d_cmd = a_Dpip_build_cmd("cmd=%s url=%s", "start_send_page", url); + sock_handler_write_str(sh, 1, d_cmd); + dFree(d_cmd); + + /* Send HTTP header. */ + sock_handler_write_str(sh, 0, "Content-type: "); + sock_handler_write_str(sh, 0, mime_type); + sock_handler_write_str(sh, 1, "\n\n"); + } + + if (!aborted) + sock_handler_write(sh, 0, buf, n); + } + + return nb; +} + +/* + * + */ +int main(void) +{ + char *dpip_tag = NULL, *cmd = NULL, *url = NULL, *url2 = NULL; + int nb; + char *p, *d_cmd; + + /* Initialize the SockHandler */ + sh = sock_handler_new(STDIN_FILENO, STDOUT_FILENO, 8*1024); + + /* wget may need to write a temporary file... */ + chdir("/tmp"); + + /* Read the dpi command from STDIN */ + dpip_tag = sock_handler_read(sh); + MSG("tag=[%s]\n", dpip_tag); + + cmd = a_Dpip_get_attr(dpip_tag, strlen(dpip_tag), "cmd"); + url = a_Dpip_get_attr(dpip_tag, strlen(dpip_tag), "url"); + if (!cmd || !url) { + MSG("ERROR, cmd=%s, url=%s\n", cmd, url); + exit (EXIT_FAILURE); + } + + if ((nb = try_ftp_transfer(url)) == 0) { + /* Transfer failed, the requested file may not exist or be a symlink + * to a directory. Try again... */ + if ((p = strrchr(url, '/')) && p[1]) { + url2 = dStrconcat(url, "/", NULL); + nb = try_ftp_transfer(url2); + } + } + + if (nb == 0) { + /* The transfer failed, let dillo know... */ + d_cmd = a_Dpip_build_cmd("cmd=%s to_cmd=%s msg=%s", + "answer", "open_url", "not a directory"); + sock_handler_write_str(sh, 1, d_cmd); + dFree(d_cmd); + } + + dFree(cmd); + dFree(url); + dFree(url2); + dFree(dpip_tag); + + /* Finish the SockHandler */ + sock_handler_close(sh); + sock_handler_free(sh); + + return 0; +} + diff --git a/dpi/hello.c b/dpi/hello.c new file mode 100644 index 00000000..bc316f5e --- /dev/null +++ b/dpi/hello.c @@ -0,0 +1,161 @@ +/* + * Dpi for "Hello World". + * + * This server is an example. Play with it and modify to your taste. + * + * Copyright 2003 Jorge Arellano Cid <jcid@dillo.org> + * + * 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 3 of the License, or + * (at your option) any later version. + * + */ + +#include <unistd.h> +#include <sys/types.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include "../dpip/dpip.h" +#include "dpiutil.h" + +/* + * Debugging macros + */ +#define _MSG(...) +#define MSG(...) printf("[hello dpi]: " __VA_ARGS__) + +/*---------------------------------------------------------------------------*/ + + +/* + * + */ +int main(void) +{ + FILE *in_stream; + SockHandler *sh; + char *dpip_tag, *cmd = NULL, *url = NULL, *child_cmd = NULL; + char *esc_tag, *d_cmd; + size_t n; + int ret; + char buf[4096]; + char *choice[] = {"Window was closed", "Yes", "No", + "Could be", "It's OK", "Cancel"}; + /* "Could>be", ">It's OK", "Can'>cel"}; --for testing */ + int choice_num; + + MSG("starting...\n"); + + /* Initialize the SockHandler */ + sh = sock_handler_new(STDIN_FILENO, STDOUT_FILENO, 2*1024); + + /* Read the dpi command from STDIN */ + dpip_tag = sock_handler_read(sh); + MSG("tag = [%s]\n", dpip_tag); + + cmd = a_Dpip_get_attr(dpip_tag, strlen(dpip_tag), "cmd"); + url = a_Dpip_get_attr(dpip_tag, strlen(dpip_tag), "url"); + +/*-- Dialog part */ +{ + char *dpip_tag2, *dialog_msg; + + /* Let's confirm the request */ + /* NOTE: you can send less alternatives (e.g. only alt1 and alt2) */ + d_cmd = a_Dpip_build_cmd( + "cmd=%s msg=%s alt1=%s alt2=%s alt3=%s alt4=%s alt5=%s", + "dialog", "Do you want to see the hello page?", + choice[1], choice[2], choice[3], choice[4], choice[5]); + sock_handler_write_str(sh, 1, d_cmd); + dFree(d_cmd); + + /* Get the answer */ + dpip_tag2 = sock_handler_read(sh); + MSG("tag = [%s]\n", dpip_tag2); + + /* Get "msg" value */ + dialog_msg = a_Dpip_get_attr(dpip_tag2, strlen(dpip_tag2), "msg"); + choice_num = 0; + if (dialog_msg) + choice_num = *dialog_msg - '0'; + + dFree(dialog_msg); + dFree(dpip_tag2); +} +/*-- EOD part */ + + /* Start sending our answer */ + d_cmd = a_Dpip_build_cmd("cmd=%s url=%s", "start_send_page", url); + sock_handler_write_str(sh, 0, d_cmd); + dFree(d_cmd); + + sock_handler_printf(sh, 0, + "Content-type: text/html\n\n" + "<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN'>\n" + "<html>\n" + "<head><title>Simple dpi test page (hello.dpi)</title></head>\n" + "<body><hr><h1>Hello world!</h1><hr>\n<br><br>\n"); + + /* Show the choice received with the dialog */ + sock_handler_printf(sh, 0, + "<hr>\n" + "<table width='100%%' border='1' bgcolor='burlywood'><tr><td>\n" + "<big><em>Dialog question:</em> Do you want to see the hello page?<br>\n" + "<em>Answer received:</em> <b>%s</b></big> </table>\n" + "<hr>\n", + choice[choice_num]); + + /* Show the dpip tag we received */ + esc_tag = Escape_html_str(dpip_tag); + sock_handler_printf(sh, 0, + "<h3>dpip tag received:</h3>\n" + "<pre>\n%s</pre>\n" + "<br><small>(<b>dpip:</b> dpi protocol)</small><br><br><br>\n", + esc_tag); + dFree(esc_tag); + + + /* Now something more interesting, + * fork a command and show its feedback */ + if (cmd && url) { + child_cmd = dStrdup("date -R"); + MSG("[%s]\n", child_cmd); + + /* Fork, exec command, get its output and answer */ + if ((in_stream = popen(child_cmd, "r")) == NULL) { + perror("popen"); + return EXIT_FAILURE; + } + + sock_handler_printf(sh, 0, "<h3>date:</h3>\n"); + sock_handler_printf(sh, 0, "<pre>\n"); + + /* Read/Write */ + while ((n = fread (buf, 1, 4096, in_stream)) > 0) { + sock_handler_write(sh, 0, buf, n); + } + + sock_handler_printf(sh, 0, "</pre>\n"); + + if ((ret = pclose(in_stream)) != 0) + MSG("popen: [%d]\n", ret); + + dFree(child_cmd); + } + + sock_handler_printf(sh, 1, "</body></html>\n"); + + dFree(cmd); + dFree(url); + dFree(dpip_tag); + + /* Finish the SockHandler */ + sock_handler_close(sh); + sock_handler_free(sh); + + return 0; +} + diff --git a/dpi/https.c b/dpi/https.c new file mode 100644 index 00000000..bbfc7ae4 --- /dev/null +++ b/dpi/https.c @@ -0,0 +1,713 @@ +/* + * Dpi for HTTPS. + * + * + * + * W A R N I N G + * + * One of the important things to have in mind is about whether + * unix domain sockets (UDS) are secure for https. I mean, root can always + * snoop on sockets (regardless of permissions) so he'd be able to "see" all + * the traffic. OTOH, if someone has root access on a machine he can do + * anything, and that includes modifying the binaries, peeking-up in + * memory space, installing a key-grabber, ... + * + * + * Copyright 2003, 2004 Jorge Arellano Cid <jcid@dillo.org> + * Copyright 2004 Garrett Kajmowicz <gkajmowi@tbaytel.net> + * + * 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 3 of the License, or + * (at your option) any later version. + * + * As a special exception permission is granted to link the code of + * the https dillo plugin with the OpenSSL project's "OpenSSL" + * library, and distribute the linked executables, without including + * the source code for OpenSSL in the source distribution. You must + * obey the GNU General Public License, version 2, in all respects + * for all of the code used other than "OpenSSL". + * + */ + +/* + * TODO: a lot of things, this is just a bare bones example. + * + * For instance: + * - Handle cookies (now that they arrive with the dpip tag, it needs + * testing). + * - Certificate authentication (asking the user in case it can't be verified) + * - Certificate management. + * - Session caching ... + * + */ + +#include <config.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> +#include <sys/un.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <signal.h> +#include <sys/wait.h> +#include <errno.h> +#include <sys/time.h> +#include <sys/stat.h> + +#include "../dpip/dpip.h" +#include "dpiutil.h" + +/* + * Debugging macros + */ +#define _MSG(...) +#define MSG(...) printf("[https dpi]: " __VA_ARGS__) + + + +#define ENABLE_SSL +/* #undef ENABLE_SSL */ +#ifdef ENABLE_SSL + +#include <openssl/ssl.h> +#include <openssl/rand.h> + +static int get_network_connection(char * url); +static int handle_certificate_problem(SSL * ssl_connection); +static int save_certificate_home(X509 * cert); + +#endif + + + +/*---------------------------------------------------------------------------*/ +/* + * Global variables + */ +static char *root_url = NULL; /*Holds the URL we are connecting to*/ +static SockHandler *sh; + + +#ifdef ENABLE_SSL + +/* + * Read the answer dpip tag for a dialog and return the number for + * the user-selected alternative. + * Return: (-1: parse error, 0: window closed, 1-5 alt. number) + */ +static int dialog_get_answer_number(void) +{ + int response_number = -1; + char *dpip_tag, *response; + + /* Read the dpi command from STDIN */ + dpip_tag = sock_handler_read(sh); + response = a_Dpip_get_attr(dpip_tag, strlen(dpip_tag), "msg"); + response_number = (response) ? strtol (response, NULL, 10) : -1; + dFree(dpip_tag); + dFree(response); + + return response_number; +} + + +/* + * This function does all of the work with SSL + */ +static void yes_ssl_support(void) +{ + /* The following variable will be set to 1 in the event of + * an error and skip any further processing + */ + int exit_error = 0; + SSL_CTX * ssl_context = NULL; + SSL * ssl_connection = NULL; + + char *dpip_tag = NULL, *cmd = NULL, *url = NULL, *http_query = NULL; + char buf[4096]; + int retval = 0; + int network_socket = -1; + + + MSG("{In https.filter.dpi}\n"); + + /*Initialize library*/ + SSL_load_error_strings(); + SSL_library_init(); + if (RAND_status() != 1){ + /*Insufficient entropy. Deal with it?*/ + MSG("Insufficient random entropy\n"); + } + + /*Create context and SSL object*/ + if (exit_error == 0){ + ssl_context = SSL_CTX_new(SSLv23_client_method()); + if (ssl_context == NULL){ + MSG("Error creating SSL context\n"); + exit_error = 1; + } + } + + /*Set directory to load certificates from*/ + /*FIXME - provide for sysconfdir variables and such*/ + if (exit_error == 0){ + if (SSL_CTX_load_verify_locations( + ssl_context, NULL, "/etc/ssl/certs/" ) == 0){ + MSG("Error opening system x509 certificate location\n"); + exit_error = 1; + } + } + + if (exit_error == 0){ + snprintf(buf, 4095, "%s/.dillo/certs/", dGethomedir()); + if (SSL_CTX_load_verify_locations(ssl_context, NULL, buf )==0){ + MSG("Error opening user x509 certificate location\n"); + exit_error = 1; + } + } + + if (exit_error == 0){ + ssl_connection = SSL_new(ssl_context); + if (ssl_connection == NULL){ + MSG("Error creating SSL connection\n"); + exit_error = 1; + } + } + + if (exit_error == 0){ + /* Need to do the following if we want to deal with all + * possible ciphers + */ + SSL_set_cipher_list(ssl_connection, "ALL"); + + /* Need to do this if we want to have the option of dealing + * with self-signed certs + */ + SSL_set_verify(ssl_connection, SSL_VERIFY_NONE, 0); + + /*Get the network address and command to be used*/ + dpip_tag = sock_handler_read(sh); + cmd = a_Dpip_get_attr(dpip_tag, strlen(dpip_tag), "cmd"); + url = a_Dpip_get_attr(dpip_tag, strlen(dpip_tag), "url"); + http_query = a_Dpip_get_attr(dpip_tag, strlen(dpip_tag), "query"); + + if (cmd == NULL || url == NULL || http_query == NULL){ + MSG("***Value of cmd, url or http_query is NULL" + " - cannot continue\n"); + exit_error = 1; + } + } + + if (exit_error == 0){ + network_socket = get_network_connection(url); + if (network_socket<0){ + MSG("Network socket create error\n"); + exit_error = 1; + } + } + + + if (exit_error == 0){ + /* Configure SSL to use network file descriptor */ + if (SSL_set_fd(ssl_connection, network_socket) == 0){ + MSG("Error connecting network socket to SSL\n"); + exit_error = 1; + } + } + + if (exit_error == 0){ + /*Actually do SSL connection handshake*/ + if (SSL_connect(ssl_connection) != 1){ + MSG("SSL_connect failed\n"); + exit_error = 1; + } + } + + /*Use handle error function to decide what to do*/ + if (exit_error == 0){ + if (handle_certificate_problem(ssl_connection) < 0){ + MSG("Certificate verification error\n"); + exit_error = 1; + } + } + + if (exit_error == 0) { + char *d_cmd; + + /*Send query we want*/ + SSL_write(ssl_connection, http_query, (int)strlen(http_query)); + + /*Analyse response from server*/ + + /*Send dpi command*/ + d_cmd = a_Dpip_build_cmd("cmd=%s url=%s", "start_send_page", url); + sock_handler_write_str(sh, 1, d_cmd); + dFree(d_cmd); + + /*Send remaining data*/ + + while ((retval = SSL_read(ssl_connection, buf, 4096)) > 0 ){ + sock_handler_write(sh, 0, buf, (size_t)retval); + } + } + + /*Begin cleanup of all resources used*/ + dFree(dpip_tag); + dFree(cmd); + dFree(url); + dFree(http_query); + + if (network_socket != -1){ + close(network_socket); + network_socket = -1; + } + if (ssl_connection != NULL){ + SSL_free(ssl_connection); + ssl_connection = NULL; + } + if (ssl_context != NULL){ + SSL_CTX_free(ssl_context); + ssl_context = NULL; + } +} + +/* + * The following function attempts to open up a connection to the + * remote server and return the file descriptor number of the + * socket. Returns -1 in the event of an error + */ +static int get_network_connection(char * url) +{ + struct sockaddr_in address; + struct hostent *hp; + + int s; + int url_offset = 0; + int portnum = 443; + unsigned int url_look_up_length = 0; + char * url_look_up = NULL; + + /*Determine how much of url we chop off as unneeded*/ + if (dStrncasecmp(url, "https://", 8) == 0){ + url_offset = 8; + } + + /*Find end of URL*/ + + if (strpbrk(url+url_offset, ":/") != NULL){ + url_look_up_length = strpbrk(url+url_offset, ":/") - (url+url_offset); + url_look_up = dStrndup(url+url_offset, url_look_up_length); + + /*Check for port number*/ + if (strchr(url+url_offset, ':') == + (url + url_offset + url_look_up_length)){ + portnum = atoi(url + url_offset + url_look_up_length + 1); + } + } else { + url_look_up = url + url_offset; + } + + root_url = dStrdup(url_look_up); + hp=gethostbyname(url_look_up); + + /*url_look_uip no longer needed, so free if neccessary*/ + if (url_look_up_length != 0){ + dFree(url_look_up); + } + + if (hp == NULL){ + MSG("gethostbyname() failed\n"); + return -1; + } + + memset(&address,0,sizeof(address)); + memcpy((char *)&address.sin_addr, hp->h_addr, (size_t)hp->h_length); + address.sin_family=hp->h_addrtype; + address.sin_port= htons((u_short)portnum); + + s = socket(hp->h_addrtype, SOCK_STREAM, 0); + if (connect(s, (struct sockaddr *)&address, sizeof(address)) != 0){ + close(s); + s = -1; + MSG("errno: %i\n", errno); + } + return s; +} + + +/* This function is run only when the certificate cannot + * be completely trusted. This will notify the user and + * allow the user to decide what to do. It may save the + * certificate to the user's .dillo directory if it is + * trusted. + * Return value: -1 on abort, 0 or higher on continue + */ +static int handle_certificate_problem(SSL * ssl_connection) +{ + int response_number; + int retval = -1; + long st; + char *cn, *cn_end; + char buf[4096], *d_cmd, *msg; + + X509 * remote_cert; + + remote_cert = SSL_get_peer_certificate(ssl_connection); + if (remote_cert == NULL){ + /*Inform user that remote system cannot be trusted*/ + d_cmd = a_Dpip_build_cmd( + "cmd=%s msg=%s alt1=%s alt2=%s", + "dialog", + "The remote system is NOT presenting a certificate.\n" + "This site CAN NOT be trusted. Sending data is NOT SAFE.\n" + "What do I do?", + "Continue", "Cancel"); + sock_handler_write_str(sh, 1, d_cmd); + dFree(d_cmd); + + /*Read the user's response*/ + response_number = dialog_get_answer_number(); + + /*Abort on anything but "Continue"*/ + if (response_number == 1){ + retval = 0; + } + + } else { + /*Figure out if (and why) the remote system can't be trusted*/ + st = SSL_get_verify_result(ssl_connection); + switch (st) { + case X509_V_OK: /*Everything is Kosher*/ + retval = 0; + break; + case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: + /*Either self signed and untrusted*/ + /*Extract CN from certificate name information*/ + cn = strstr(remote_cert->name, "/CN=") + 4; + if (cn == NULL) + break; + + if ((cn_end = strstr(cn, "/")) == NULL ) + cn_end = cn + strlen(cn); + + strncpy(buf, cn, (size_t) (cn_end - cn)); + + /*Add terminating NULL*/ + buf[cn_end - cn] = 0; + + msg = dStrconcat("The remote certificate is self-signed and " + "untrusted.\nFor address: ", buf, NULL); + d_cmd = a_Dpip_build_cmd( + "cmd=%s msg=%s alt1=%s alt2=%s alt3=%s", + "dialog", msg, "Continue", "Cancel", "Trust Certificate"); + sock_handler_write_str(sh, 1, d_cmd); + dFree(d_cmd); + dFree(msg); + + response_number = dialog_get_answer_number(); + switch (response_number){ + case 1: + retval = 0; + break; + case 2: + break; + case 3: + /*Save certificate to a file here and recheck the chain*/ + /*Potential security problems because we are writing + *to the filesystem*/ + save_certificate_home(remote_cert); + retval = 1; + break; + default: + break; + } + break; + case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT: + case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY: + d_cmd = a_Dpip_build_cmd( + "cmd=%s msg=%s alt1=%s alt2=%s", + "dialog", + "The issuer for the remote certificate cannot be found\n" + "The authenticity of the remote certificate cannot be trusted", + "Continue", "Cancel"); + sock_handler_write_str(sh, 1, d_cmd); + dFree(d_cmd); + + response_number = dialog_get_answer_number(); + if (response_number == 1) { + retval = 0; + } + break; + + case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE: + case X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE: + case X509_V_ERR_CERT_SIGNATURE_FAILURE: + case X509_V_ERR_CRL_SIGNATURE_FAILURE: + d_cmd = a_Dpip_build_cmd( + "cmd=%s msg=%s alt1=%s alt2=%s", + "dialog", + "The remote certificate signature could not be read\n" + "or is invalid and should not be trusted", + "Continue", "Cancel"); + sock_handler_write_str(sh, 1, d_cmd); + dFree(d_cmd); + + response_number = dialog_get_answer_number(); + if (response_number == 1) { + retval = 0; + } + break; + case X509_V_ERR_CERT_NOT_YET_VALID: + case X509_V_ERR_CRL_NOT_YET_VALID: + d_cmd = a_Dpip_build_cmd( + "cmd=%s msg=%s alt1=%s alt2=%s", + "dialog", + "Part of the remote certificate is not yet valid\n" + "Certificates usually have a range of dates over which\n" + "they are to be considered valid, and the certificate\n" + "presented has a starting validity after today's date\n" + "You should be cautious about using this site", + "Continue", "Cancel"); + sock_handler_write_str(sh, 1, d_cmd); + dFree(d_cmd); + + response_number = dialog_get_answer_number(); + if (response_number == 1) { + retval = 0; + } + break; + case X509_V_ERR_CERT_HAS_EXPIRED: + case X509_V_ERR_CRL_HAS_EXPIRED: + d_cmd = a_Dpip_build_cmd( + "cmd=%s msg=%s alt1=%s alt2=%s", + "dialog", + "The remote certificate has expired. The certificate\n" + "wasn't designed to last this long. You should avoid \n" + "this site.", + "Continue", "Cancel"); + sock_handler_write_str(sh, 1, d_cmd); + dFree(d_cmd); + response_number = dialog_get_answer_number(); + if (response_number == 1) { + retval = 0; + } + break; + case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD: + case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD: + case X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD: + case X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD: + d_cmd = a_Dpip_build_cmd( + "cmd=%s msg=%s alt1=%s alt2=%s", + "dialog", + "There was an error in the certificate presented.\n" + "Some of the certificate data was improperly formatted\n" + "making it impossible to determine if the certificate\n" + "is valid. You should not trust this certificate.", + "Continue", "Cancel"); + sock_handler_write_str(sh, 1, d_cmd); + dFree(d_cmd); + response_number = dialog_get_answer_number(); + if (response_number == 1) { + retval = 0; + } + break; + case X509_V_ERR_INVALID_CA: + case X509_V_ERR_INVALID_PURPOSE: + case X509_V_ERR_CERT_UNTRUSTED: + case X509_V_ERR_CERT_REJECTED: + case X509_V_ERR_KEYUSAGE_NO_CERTSIGN: + d_cmd = a_Dpip_build_cmd( + "cmd=%s msg=%s alt1=%s alt2=%s", + "dialog", + "One of the certificates in the chain is being used\n" + "incorrectly (possibly due to configuration problems\n" + "with the remote system. The connection should not\n" + "be trusted", + "Continue", "Cancel"); + sock_handler_write_str(sh, 1, d_cmd); + dFree(d_cmd); + response_number = dialog_get_answer_number(); + if (response_number == 1) { + retval = 0; + } + break; + case X509_V_ERR_SUBJECT_ISSUER_MISMATCH: + case X509_V_ERR_AKID_SKID_MISMATCH: + case X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH: + d_cmd = a_Dpip_build_cmd( + "cmd=%s msg=%s alt1=%s alt2=%s", + "dialog", + "Some of the information presented by the remote system\n" + "does not match other information presented\n" + "This may be an attempt to evesdrop on communications", + "Continue", "Cancel"); + sock_handler_write_str(sh, 1, d_cmd); + dFree(d_cmd); + default: /*Need to add more options later*/ + snprintf(buf, 80, + "The remote certificate cannot be verified (code %ld)", st); + d_cmd = a_Dpip_build_cmd( + "cmd=%s msg=%s alt1=%s alt2=%s", + "dialog", buf, "Continue", "Cancel"); + sock_handler_write_str(sh, 1, d_cmd); + dFree(d_cmd); + response_number = dialog_get_answer_number(); + /*abort on anything but "Continue"*/ + if (response_number == 1){ + retval = 0; + } + } + X509_free(remote_cert); + remote_cert = 0; + } + + return retval; +} + +/* + * Save certificate with a hashed filename. + * Return: 0 on success, 1 on failure. + */ +static int save_certificate_home(X509 * cert) +{ + char buf[4096]; + + FILE * fp = NULL; + unsigned int i = 0; + int retval = 1; + + /*Attempt to create .dillo/certs blindly - check later*/ + snprintf(buf,4096,"%s/.dillo/", dGethomedir()); + mkdir(buf, 01777); + snprintf(buf,4096,"%s/.dillo/certs/", dGethomedir()); + mkdir(buf, 01777); + + do{ + snprintf(buf, 4096, "%s/.dillo/certs/%lx.%u", + dGethomedir(), X509_subject_name_hash(cert), i); + + fp=fopen(buf, "r"); + if (fp == NULL){ + /*File name doesn't exist so we can use it safely*/ + fp=fopen(buf, "w"); + if (fp == NULL){ + MSG("Unable to open cert save file in home dir\n"); + break; + } else { + PEM_write_X509(fp, cert); + fclose(fp); + MSG("Wrote certificate\n"); + retval = 0; + break; + } + } else { + fclose(fp); + } + i++; + /*Don't loop too many times - just give up*/ + } while( i < 1024 ); + + return retval; +} + + + +#else + + +/* + * Call this function to display an error message if SSL support + * isn't available for some reason + */ +static void no_ssl_support(void) +{ + char *dpip_tag = NULL, *cmd = NULL, *url = NULL, *http_query = NULL; + char *d_cmd; + + /* Read the dpi command from STDIN */ + dpip_tag = sock_handler_read(sh); + + MSG("{In https.filter.dpi}\n"); + MSG("no_ssl_support version\n"); + + cmd = a_Dpip_get_attr(dpip_tag, strlen(dpip_tag), "cmd"); + url = a_Dpip_get_attr(dpip_tag, strlen(dpip_tag), "url"); + http_query = a_Dpip_get_attr(dpip_tag, strlen(dpip_tag), "query"); + + MSG("{ cmd: %s}\n", cmd); + MSG("{ url: %s}\n", url); + MSG("{ http_query:\n%s}\n", http_query); + + MSG("{ sending dpip cmd...}\n"); + + d_cmd = a_Dpip_build_cmd("cmd=%s url=%s", "start_send_page", url); + sock_handler_write_str(sh, 1, d_cmd); + dFree(d_cmd); + + MSG("{ dpip cmd sent.}\n"); + + MSG("{ sending HTML...}\n"); + + sock_handler_printf(sh, 1, + "Content-type: text/html\n\n" + "<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN'>\n" + "<html><body><pre>\n" + "<b>Hi!\n\n" + " This is the https dpi that just got a request to send\n" + " the following HTTP query:\n{</b>\n" + "<code>%s</code>\n" + "<b>}</b>\n\n" + " <b>*** Dillo's prototype plugin for https support" + " is disabled now ***</b>\n\n" + " If you want to test this <b>alpha</b> support code, just remove\n" + " line 65 from https.c, recompile and reinstall.\n\n" + " (beware that this https support is very limited now)\n\n" + " To use https and SSL, you must have \n" + " the OpenSSL development libraries installed. Check your\n" + " O/S distribution provider, or check out\n" + " <a href=\"http://www.openssl.org\">www.openssl.org</a>\n\n" + " --\n" + "</pre></body></html>\n", + http_query + ); + MSG("{ HTML content sent.}\n"); + + dFree(cmd); + dFree(url); + dFree(http_query); + dFree(dpip_tag); + + MSG("{ exiting https.dpi}\n"); + +} + +#endif + + +/*---------------------------------------------------------------------------*/ +int main(void) +{ + /* Initialize the SockHandler for this filter dpi */ + sh = sock_handler_new(STDIN_FILENO, STDOUT_FILENO, 8*1024); + +#ifdef ENABLE_SSL + yes_ssl_support(); +#else + no_ssl_support(); +#endif + + /* Finish the SockHandler */ + sock_handler_close(sh); + sock_handler_free(sh); + + dFree(root_url); + + MSG("{ exiting https.dpi}\n"); + + return 0; +} + diff --git a/dpid/Makefile.am b/dpid/Makefile.am new file mode 100644 index 00000000..cd15afd4 --- /dev/null +++ b/dpid/Makefile.am @@ -0,0 +1,26 @@ +AM_CPPFLAGS=-DDPIDRC_SYS='"$(sysconfdir)/dpidrc"' + +bin_PROGRAMS = dpid +dpid_LDADD = ../dpip/libDpip.a ../dlib/libDlib.a + +EXTRA_DIST = dpidc +bin_SCRIPTS = dpidc + +dpid_SOURCES = \ + dpi.h \ + dpi_service.h \ + dpi_socket_dir.h \ + dpid.h \ + dpid_common.h \ + misc_new.h \ + dpi.c \ + dpi_service.c \ + dpi_socket_dir.c \ + dpid.c \ + dpid_common.c \ + main.c \ + misc_new.c + +install-data-local : + $(mkinstalldirs) $(DESTDIR)$(sysconfdir) + echo dpi_dir=$(libdir)/dillo/dpi > $(DESTDIR)$(sysconfdir)/dpidrc diff --git a/dpid/TODO b/dpid/TODO new file mode 100644 index 00000000..c9967535 --- /dev/null +++ b/dpid/TODO @@ -0,0 +1,32 @@ +Todo List + + File dpi_service.c + This module should be removed because its original functions + have been removed or modified. Put these functions in dpid.c + + File dpi_service.h + This module should be removed because its original functions + have been removed or modified. Put these functions in dpid.c + + Add other file types, but first we need to add files associated + with a dpi to the design. + + Global SRS_NAME + Should read this from dillorc + + File dpid_common.h + The dpid error codes will be used in the next patch + + Global main() + + add new plugins when they become available + __________________________________________________________ + +Remove global variables. + +Use a singly linked list for dpi_attr_list + +Allow dpis to be registered one at a time + +Only run dpis listed in users dillorc + +MAYBE Have dpid accept all connections to dpis (fixes inf loop if dpi crashes before accept) diff --git a/dpid/dpi.c b/dpid/dpi.c new file mode 100644 index 00000000..45e5b49d --- /dev/null +++ b/dpid/dpi.c @@ -0,0 +1,97 @@ +/* + Copyright (C) 2003 Ferdi Franceschini <ferdif@optusnet.com.au> + + 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 3 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 + */ + +/*! \file + * Access functions for ~/.dillo/dpi_socket_dir. + * The most useful function for dillo is a_Dpi_srs, it returns + * the full path to the dpid service request socket. + */ + +#include <errno.h> +#include "dpid_common.h" +#include "dpi.h" +#include "misc_new.h" + +/*! \Return + * Returns path to the dpi_socket_dir file + * Use dFree to free memory + */ +char *a_Dpi_sockdir_file(void) +{ + char *dpi_socket_dir, *dirfile_path = "/.dillo/dpi_socket_dir"; + + dpi_socket_dir = dStrconcat(dGethomedir(), dirfile_path, NULL); + return dpi_socket_dir; +} + +/*! Read socket directory path from ~/.dillo/dpi_socket_dir + * \Return + * socket directory path or NULL if the dpi_socket_dir file does not exist. + * \Note + * This function exits if ~/.dillo does not exist or + * if the dpi_socket_dir file cannot be opened for a + * reason other than it does not exist. + */ + +char *a_Dpi_rd_dpi_socket_dir(char *dirname) +{ + FILE *dir; + char *sockdir = NULL, *rcpath; + + rcpath = dStrconcat(dGethomedir(), "/.dillo", NULL); + + /* If .dillo does not exist it is an unrecoverable error */ + if (access(rcpath, F_OK) == -1) { + ERRMSG("a_Dpi_rd_dpi_socket_dir", "access", errno); + MSG_ERR(" - %s\n", rcpath); + exit(1); + } + dFree(rcpath); + + if ((dir = fopen(dirname, "r")) != NULL) { + sockdir = dGetline(dir); + fclose(dir); + } else if (errno == ENOENT) { + ERRMSG("a_Dpi_rd_dpi_socket_dir", "fopen", errno); + MSG_ERR(" - %s\n", dirname); + } else if (errno != ENOENT) { + ERRMSG("a_Dpi_rd_dpi_socket_dir", "fopen", errno); + MSG_ERR(" - %s\n", dirname); + exit(1); + } + + return sockdir; +} + +/*! + * \Modifies + * srs_name + * \Return + * The service request socket name with its complete path. + */ +char *a_Dpi_srs(void) +{ + char *dirfile_path, *sockdir, *srs_name; + + dirfile_path = a_Dpi_sockdir_file(); + sockdir = dStrstrip(a_Dpi_rd_dpi_socket_dir(dirfile_path)); + srs_name = dStrconcat(sockdir, "/", "dpid.srs", NULL); + dFree(sockdir); + dFree(dirfile_path); + return (srs_name); +} diff --git a/dpid/dpi.h b/dpid/dpi.h new file mode 100644 index 00000000..d97fdc6d --- /dev/null +++ b/dpid/dpi.h @@ -0,0 +1,54 @@ +/*! \file + * Access functions for ~/.dillo/dpi_socket_dir. + * The most useful function for dillo is a_Dpi_srs, it returns + * the full path to the dpid service request socket. + */ + +#ifndef DPI_H +#define DPI_H + +#include <config.h> +#include <stdio.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> + +/* Check the Unix98 goodie */ +#ifndef socklen_t + #define socklen_t uint32_t +#endif + +/* Some systems may not have this one... */ +#ifndef AF_LOCAL + #define AF_LOCAL AF_UNIX +#endif + +/* This one is tricky, some sources state it should include the byte + * for the terminating NULL, and others say it shouldn't. + * The other way is to only use this one when a native SUN_LEN is not present, + * but as dillo has used this for a long time successfully, here it goes. + */ +# define D_SUN_LEN(ptr) ((size_t) (((struct sockaddr_un *) 0)->sun_path) \ + + strlen ((ptr)->sun_path)) + +/*! + * dpi commands + */ +enum { + UNKNOWN_CMD, + BYE_CMD, /* "DpiBye" */ + CHECK_SERVER_CMD, /* "check_server" */ + REGISTER_ALL_CMD, /* "register_all" */ + REGISTER_SERVICE_CMD /* "register_service" */ +}; + + +char *a_Dpi_sockdir_file(void); + +char *a_Dpi_rd_dpi_socket_dir(char *dirname); + +char *a_Dpi_srs(void); + +#endif diff --git a/dpid/dpi_service.c b/dpid/dpi_service.c new file mode 100644 index 00000000..07cdad8e --- /dev/null +++ b/dpid/dpi_service.c @@ -0,0 +1,114 @@ +/* + Copyright (C) 2003 Ferdi Franceschini <ferdif@optusnet.com.au> + + 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 3 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 + */ + +/*! \file + * \todo + * This module should be removed because its original functions + * have been removed or modified. + * Put these functions in dpid.c + */ +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include "dpid_common.h" +#include "dpid.h" +#include "../dpip/dpip.h" + +#ifdef TEST +#include "testdat.h" +#endif + +/* exported functions */ +char *get_dpi_dir(char *dpidrc); + + +/*! Get dpi directory path from dpidrc + * \Return + * dpi directory on success, NULL on failure + * \Important + * The dpi_dir definition in dpidrc must have no leading white space. + */ +char *get_dpi_dir(char *dpidrc) +{ + FILE *In; + int len; + char *rcline = NULL, *value = NULL, *p; + + if ((In = fopen(dpidrc, "r")) == NULL) { + ERRMSG("dpi_dir", "fopen", errno); + MSG_ERR(" - %s\n", dpidrc); + return (NULL); + } + + while ((rcline = dGetline(In)) != NULL) { + if (strncmp(rcline, "dpi_dir", 7) == 0) + break; + dFree(rcline); + } + fclose(In); + + if (!rcline) { + ERRMSG("dpi_dir", "Failed to find a dpi_dir entry in dpidrc", 0); + MSG_ERR("Put your dillo plugins path in %s\n", dpidrc); + MSG_ERR("eg. dpi_dir=/usr/local/lib/dillo/dpi "); + MSG_ERR("with no leading spaces.\n"); + value = NULL; + } else { + len = (int) strlen(rcline); + if (len && rcline[len - 1] == '\n') + rcline[len - 1] = 0; + + if ((p = strchr(rcline, '='))) { + while (*++p == ' '); + value = dStrdup(p); + } else { + ERRMSG("dpi_dir", "strchr", 0); + MSG_ERR(" - '=' not found in %s\n", rcline); + value = NULL; + } + } + + dFree(rcline); + return (value); +} + +/*! Send the list of available dpi IDs to a client + * \Return + * 1 on success, -1 on failure. + * +static int send_service_list(int sock, struct dp *dpi_attr_list, int srv_num) +{ + int i; + char *buf; + ssize_t wlen = 0; + + for (i = 0; i < srv_num && wlen != -1; i++) { + d_cmd = a_Dpip_build_cmd("cmd=%s msg=%s", + "send_data", dpi_attr_list[i].id); + wlen = write(sock, d_cmd, strlen(d_cmd)); + dFree(d_cmd); + } + if (wlen == -1) { + ERRMSG("send_service_list", "write", errno); + return (-1); + } + return (1); +} + */ diff --git a/dpid/dpi_service.h b/dpid/dpi_service.h new file mode 100644 index 00000000..af7679b3 --- /dev/null +++ b/dpid/dpi_service.h @@ -0,0 +1,20 @@ +/*! \file + * \todo + * This module should be removed because its original functions + * have been removed or modified. + * Put these functions in dpid.c + */ + +#ifndef DPI_SERVICE_H +#define DPI_SERVICE_H + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include "dpid.h" + +char *get_dpi_dir(char *dpidrc); + +int send_service_list(int sock, struct dp *dpi_attr_list, int srv_num); + +#endif diff --git a/dpid/dpi_socket_dir.c b/dpid/dpi_socket_dir.c new file mode 100644 index 00000000..c18faf0d --- /dev/null +++ b/dpid/dpi_socket_dir.c @@ -0,0 +1,128 @@ +/* + Copyright (C) 2003 Ferdi Franceschini <ferdif@optusnet.com.au> + + 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 3 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 + */ + +/*! \file + * Create a per user temporary directory for dpi sockets + */ + +#include <errno.h> +#include "dpid_common.h" +#include "dpi.h" +#include "misc_new.h" +#include "dpi_socket_dir.h" /* for function prototypes */ + +/*! Save socket directory name in ~/.dillo/dpi_socket_dir + * \Return + * \li 1 on success + * \li -1 on failure + */ +int w_dpi_socket_dir(char *dirname, char *sockdir) +{ + FILE *dir; + + if ((dir = fopen(dirname, "w")) == NULL) { + ERRMSG("w_dpi_socket_dir", "fopen", errno); + return (-1); + } + fprintf(dir, "%s", sockdir); + fclose(dir); + return (1); +} + +/*! Test that socket directory exists and is a directory + * \Return + * \li 1 on success + * \li 0 on failure + * \bug Does not check permissions or that it's a symbolic link + * to another directory. + */ +int tst_dir(char *dir) +{ + char *dirtest; + int ret = 0; + + /* test for a directory */ + dirtest = dStrconcat(dir, "/", NULL); + if (access(dirtest, F_OK) == -1) { + ERRMSG("tst_dir", "access", errno); + MSG_ERR(" - %s\n", dirtest); + } else { + ret = 1; + } + dFree(dirtest); + + return ret; +} + +/*! Create socket directory + * \Return + * \li Socket directory path on success + * \li NULL on failure + */ +char *mk_sockdir(void) +{ + char *template; + + template = dStrconcat("/tmp/", getlogin(), "-", "XXXXXX", NULL); + if (a_Misc_mkdtemp(template) == NULL) { + ERRMSG("mk_sockdir", "a_Misc_mkdtemp", 0); + MSG_ERR(" - %s\n", template); + dFree(template); + return (NULL); + } + return template; +} + +/*! Create socket directory if it does not exist and save its name in + * ~/.dillo/dpi_socket_dir. + * \Return + * \li Socket directory name on success + * \li NULL on failure. + */ +char *init_sockdir(char *dpi_socket_dir) +{ + char *sockdir = NULL; + int dir_ok = 0; + + if ((sockdir = a_Dpi_rd_dpi_socket_dir(dpi_socket_dir)) == NULL) { + MSG_ERR("init_sockdir: The dpi_socket_dir file %s does not exist\n", + sockdir); + } else { + if ((dir_ok = tst_dir(sockdir)) == 1) { + MSG_ERR("init_sockdir: The socket directory %s exists and is OK\n", + sockdir); + } else { + MSG_ERR("init_sockdir: The socket directory %s does not exist " + "or is not a directory\n", sockdir); + dFree(sockdir); + } + } + if (!dir_ok) { + sockdir = mk_sockdir(); + if (sockdir == NULL) { + ERRMSG("init_sockdir", "mk_sockdir", 0); + MSG_ERR(" - Failed to create dpi socket directory\n"); + } else if ((w_dpi_socket_dir(dpi_socket_dir, sockdir)) == -1) { + ERRMSG("init_sockdir", "w_dpi_socket_dir", 0); + MSG_ERR(" - failed to save %s\n", sockdir); + dFree(sockdir); + sockdir = NULL; + } + } + return (sockdir); +} diff --git a/dpid/dpi_socket_dir.h b/dpid/dpi_socket_dir.h new file mode 100644 index 00000000..87719212 --- /dev/null +++ b/dpid/dpi_socket_dir.h @@ -0,0 +1,17 @@ +/*! \file + * Create a per user temporary directory for dpi sockets + */ + +#ifndef DPI_SOCKET_DIR_H +#define DPI_SOCKET_DIR_H + + +int w_dpi_socket_dir(char *dirname, char *sockdir); + +int tst_dir(char *dir); + +char *mk_sockdir(void); + +char *init_sockdir(char *dpi_socket_dir); + +#endif diff --git a/dpid/dpid.c b/dpid/dpid.c new file mode 100644 index 00000000..9fe2e74d --- /dev/null +++ b/dpid/dpid.c @@ -0,0 +1,734 @@ +/* + Copyright (C) 2003 Ferdi Franceschini <ferdif@optusnet.com.au> + + 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 3 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 + */ + +/*! \file + * Main functions to set-up dpi information and to initialise sockets + */ +#include <errno.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <unistd.h> +#include "dpid_common.h" +#include "dpid.h" +#include "dpi.h" +#include "dpi_socket_dir.h" +#include "dpi_service.h" +#include "misc_new.h" + +#include "../dpip/dpip.h" + +#define QUEUE 5 + +volatile sig_atomic_t caught_sigchld = 0; + +/*! Return the basename of a filename + */ +char *get_basename(char *filename) +{ + char *p; + + if (filename && (p = strrchr(filename, '/'))) { + filename = p + 1; + } + return filename; +} + +/*! Close and remove the sockets in the + * given dpi attribute list + */ +void rm_dpi_sockets(struct dp *dpi_attr_list, int numdpis) +{ + int i; + + for (i = 0; i < numdpis; i++) { + a_Misc_close_fd(dpi_attr_list[i].socket); + (void) unlink(dpi_attr_list[i].sockpath); + } +} + +/*! Close and remove inactive dpi sockets + * \Return + * Number of active dpis. + */ +int rm_inactive_dpi_sockets(struct dp *dpi_attr_list, int numdpis) +{ + int i, active = 0; + + for (i = 0; i < numdpis; i++) { + if (dpi_attr_list[i].pid == 1) { + a_Misc_close_fd(dpi_attr_list[i].socket); + (void) unlink(dpi_attr_list[i].sockpath); + } else + active++; + } + return (active); +} + +/*! Remove sockets + */ +void cleanup(char *socket_dir) +{ + DIR *dir; + struct dirent *dir_entry = NULL; + char *sockpath; + + dir = opendir(socket_dir); + if (dir == NULL) { + ERRMSG("cleanup", "opendir", errno); + return; + } + while ( (dir_entry = readdir(dir)) != NULL ) { + if (dir_entry->d_name[0] == '.') + continue; + sockpath = dStrconcat(socket_dir, "/", dir_entry->d_name, NULL); + unlink(sockpath); + dFree(sockpath); + } + closedir(dir); +} + +/*! Free memory used to describe + * a set of dpi attributes + */ +void free_dpi_attr(struct dp *dpi_attr) +{ + if (dpi_attr->id != NULL) { + dFree(dpi_attr->id); + dpi_attr->id = NULL; + } + if (dpi_attr->path != NULL) { + dFree(dpi_attr->path); + dpi_attr->path = NULL; + } + if (dpi_attr->sockpath != NULL) { + dFree(dpi_attr->sockpath); + dpi_attr->sockpath = NULL; + } +} + +/*! Free memory used by the plugin list + */ +void free_plugin_list(struct dp **dpi_attr_list_ptr, int numdpis) +{ + int i; + struct dp *dpi_attr_list = *dpi_attr_list_ptr; + + if (dpi_attr_list == NULL) + return; + + for (i = 0; i < numdpis; i++) + free_dpi_attr(dpi_attr_list + i); + + dFree(dpi_attr_list); + *dpi_attr_list_ptr = NULL; +} + +/*! \todo + * Remove terminator and est_terminator unless we really want to clean up + * on abnormal exit. + */ +#if 0 +/*! Signal handler for SIGINT, SIGQUIT, and SIGTERM. Calls cleanup + */ +void terminator(int sig) +{ + (void) signal(SIGCHLD, SIG_DFL); + cleanup(); + (void) signal(sig, SIG_DFL); + (void) raise(sig); + _exit(0); +} + +/*! Establish handler for termination signals + * and register cleanup with atexit */ +void est_terminator(void) +{ + struct sigaction act; + sigset_t block; + + (void) sigemptyset(&block); + (void) sigaddset(&block, SIGINT); + (void) sigaddset(&block, SIGQUIT); + (void) sigaddset(&block, SIGTERM); + (void) sigaddset(&block, SIGSEGV); + + act.sa_handler = terminator; + act.sa_mask = block; + act.sa_flags = 0; + + if (sigaction(SIGINT, &act, NULL) || + sigaction(SIGQUIT, &act, NULL) || + sigaction(SIGTERM, &act, NULL) || sigaction(SIGSEGV, &act, NULL)) { + ERRMSG("est_terminator", "sigaction", errno); + exit(1); + } + + if (atexit(cleanup) != 0) { + ERRMSG("est_terminator", "atexit", 0); + MSG_ERR("Hey! atexit failed, how did that happen?\n"); + exit(1); + } +} +#endif + +/*! Identify a given file + * Currently there is only one file type associated with dpis. + * More file types will be added as needed + */ +enum file_type get_file_type(char *file_name) +{ + char *dot = strrchr(file_name, '.'); + + if (dot && !strcmp(dot, ".dpi")) + return DPI_FILE; /* Any filename ending in ".dpi" */ + else { + MSG_ERR("get_file_type: Unknown file type for %s\n", file_name); + return UNKNOWN_FILE; + } +} + +/*! Scans a service directory in dpi_dir and fills dpi_attr + * \Note + * Caller must allocate memory for dpi_attr. + * \Return + * \li 0 on success + * \li -1 on failure + * \todo + * Add other file types, but first we need to add files associated with a dpi + * to the design. + */ +int get_dpi_attr(char *dpi_dir, char *service, struct dp *dpi_attr) +{ + char *service_dir = NULL; + struct stat statinfo; + enum file_type ftype; + int retval = -1; + DIR *dir_stream; + struct dirent *dir_entry = NULL; + + service_dir = dStrconcat(dpi_dir, "/", service, NULL); + if (stat(service_dir, &statinfo) == -1) { + ERRMSG("get_dpi_attr", "stat", errno); + MSG_ERR("file=%s\n", service_dir); + } else if ((dir_stream = opendir(service_dir)) == NULL) { + ERRMSG("get_dpi_attr", "opendir", errno); + } else { + /* Scan the directory loking for dpi files. + * (currently there's only the dpi program, but in the future + * there may also be helper scripts.) */ + while ( (dir_entry = readdir(dir_stream)) != NULL) { + if (dir_entry->d_name[0] == '.') + continue; + + ftype = get_file_type(dir_entry->d_name); + switch (ftype) { + case DPI_FILE: + dpi_attr->path = + dStrconcat(service_dir, "/", dir_entry->d_name, NULL); + dpi_attr->id = dStrdup(service); + dpi_attr->sockpath = NULL; + dpi_attr->pid = 1; + if (strstr(dpi_attr->path, ".filter") != NULL) + dpi_attr->filter = 1; + else + dpi_attr->filter = 0; + retval = 0; + break; + default: + break; + } + } + closedir(dir_stream); + + if (retval != 0) + MSG_ERR("get_dpi_attr: No dpi plug-in in %s/%s\n", + dpi_dir, service); + } + dFree(service_dir); + return retval; +} + +/*! Register a service + * Retrieves attributes for "service" and stores them + * in dpi_attr. It looks for "service" in ~/.dillo/dpi + * first, and then in the system wide dpi directory. + * Caller must allocate memory for dpi_attr. + * \Return + * \li 0 on success + * \li -1 on failure + */ +int register_service(struct dp *dpi_attr, char *service) +{ + char *user_dpi_dir, *dpidrc, *user_service_dir, *dir = NULL; + int retval = -1; + + user_dpi_dir = dStrconcat(dGethomedir(), "/", dotDILLO_DPI, NULL); + user_service_dir = + dStrconcat(dGethomedir(), "/", dotDILLO_DPI, "/", service, NULL); + + dpidrc = dStrconcat(dGethomedir(), "/", dotDILLO_DPIDRC, NULL); + if (access(dpidrc, F_OK) == -1) { + if (access(DPIDRC_SYS, F_OK) == -1) { + ERRMSG("register_service", "Error ", 0); + MSG_ERR("\n - There is no %s or %s file\n", dpidrc, + DPIDRC_SYS); + dFree(user_dpi_dir); + dFree(user_service_dir); + dFree(dpidrc); + return(-1); + } + dFree(dpidrc); + dpidrc = dStrdup(DPIDRC_SYS); + } + + /* Check home dir for dpis */ + if (access(user_service_dir, F_OK) == 0) { + get_dpi_attr(user_dpi_dir, service, dpi_attr); + retval = 0; + } else { /* Check system wide dpis */ + if ((dir = get_dpi_dir(dpidrc)) != NULL) { + if (access(dir, F_OK) == 0) { + get_dpi_attr(dir, service, dpi_attr); + retval = 0; + } else { + ERRMSG("register_service", "get_dpi_attr failed", 0); + } + } else { + ERRMSG("register_service", "dpi_dir: Error getting dpi dir.", 0); + } + } + dFree(user_dpi_dir); + dFree(user_service_dir); + dFree(dpidrc); + dFree(dir); + return (retval); +} + +/*! + * Create dpi directory for available + * plugins and create plugin list. + * \Return + * \li Returns number of available plugins on success + * \li -1 on failure + */ +int register_all(struct dp **attlist) +{ + DIR *user_dir_stream, *sys_dir_stream; + char *user_dpidir = NULL, *sys_dpidir = NULL, *dpidrc = NULL; + char *basename=NULL; + struct dirent *user_dirent, *sys_dirent; + int j, st, not_in_user_list; + int snum, usr_srv_num; + size_t dp_sz = sizeof(struct dp); + + if (*attlist != NULL) { + ERRMSG("register_all", "attlist parameter should be NULL", 0); + return -1; + } + + user_dpidir = dStrconcat(dGethomedir(), "/", dotDILLO_DPI, NULL); + if (access(user_dpidir, F_OK) == -1) { + /* no dpis in user's space */ + dFree(user_dpidir); + user_dpidir = NULL; + } + dpidrc = dStrconcat(dGethomedir(), "/", dotDILLO_DPIDRC, NULL); + if (access(dpidrc, F_OK) == -1) { + dFree(dpidrc); + dpidrc = dStrdup(DPIDRC_SYS); + if (access(dpidrc, F_OK) == -1) { + dFree(dpidrc); + dpidrc = NULL; + } + } + if (!dpidrc || (sys_dpidir = get_dpi_dir(dpidrc)) == NULL) + sys_dpidir = NULL; + dFree(dpidrc); + + if (!user_dpidir && !sys_dpidir) { + ERRMSG("register_all", "Fatal error ", 0); + MSG_ERR("\n - Can't find the directory for dpis.\n"); + exit(1); + } + + /* Get list of services in user's .dillo/dpi directory */ + snum = usr_srv_num = 0; + if (user_dpidir && (user_dir_stream = opendir(user_dpidir)) != NULL) { + while ((user_dirent = readdir(user_dir_stream)) != NULL) { + if (user_dirent->d_name[0] == '.') + continue; + *attlist = (struct dp *) dRealloc(*attlist, (snum + 1) * dp_sz); + st=get_dpi_attr(user_dpidir, user_dirent->d_name, &(*attlist)[snum]); + if (st == 0) + snum++; + } + usr_srv_num = snum; + closedir(user_dir_stream); + } + if (sys_dpidir && (sys_dir_stream = opendir(sys_dpidir)) != NULL) { + /* if system service is not in user list then add it */ + while ((sys_dirent = readdir(sys_dir_stream)) != NULL) { + if (sys_dirent->d_name[0] == '.') + continue; + not_in_user_list = 1; + for (j = 0; j < usr_srv_num; j++) { + basename = get_basename((*attlist)[j].path); + if (strcmp(sys_dirent->d_name, basename) == 0) { + not_in_user_list = 0; + break; + } + } + if (not_in_user_list) { + *attlist = (struct dp *) dRealloc(*attlist, (snum + 1) * dp_sz); + st=get_dpi_attr(sys_dpidir, sys_dirent->d_name, &(*attlist)[snum]); + if (st == 0) + snum++; + } + } + closedir(sys_dir_stream); + } + + dFree(sys_dpidir); + dFree(user_dpidir); + + /* todo: do we consider snum == 0 an error? + * (if so, we should return -1 ) */ + return (snum); +} + +/*! Initialise the service request socket + * \Return: + * \li Number of sockets (1 == success) + * \li -1 on failure + */ +int init_srs_socket(char *sockdir) +{ + int retval = -1; + struct sockaddr_un srs_sa; + size_t sun_path_len; + socklen_t addr_sz; + + srs_name = dStrconcat(sockdir, "/", SRS_NAME, NULL); + FD_ZERO(&sock_set); + + /* Initialise srs, service request socket on startup */ + if ((srs = socket(AF_LOCAL, SOCK_STREAM, 0)) == -1) { + ERRMSG("init_srs_socket", "socket", errno); + return (retval); /* avoids nesting ifs too deeply */ + } + /* Set srs to close on exec */ + fcntl(srs, F_SETFD, FD_CLOEXEC | fcntl(srs, F_GETFD)); + + srs_sa.sun_family = AF_LOCAL; + + sun_path_len = sizeof(srs_sa.sun_path); + if (strlen(srs_name) > sun_path_len) { + ERRMSG("init_srs_socket", "srs_name is too long", 0); + MSG_ERR("\n - it should be <= %lu chars", (ulong_t)sun_path_len); + MSG_ERR("\n - srs_name = %s\n", srs_name); + return(retval); + } + strncpy(srs_sa.sun_path, srs_name, sun_path_len); + addr_sz = (socklen_t) D_SUN_LEN(&srs_sa); + + if ((bind(srs, (struct sockaddr *) &srs_sa, addr_sz)) == -1) { + if (errno == EADDRINUSE) { + ERRMSG("init_srs_socket", "bind", errno); + MSG_ERR("srs_sa.sun_path = %s\n", srs_sa.sun_path); + dpi_errno = dpid_srs_addrinuse; + } else { + ERRMSG("init_srs_socket", "bind", errno); + MSG_ERR("srs_sa.sun_path = %s\n", srs_sa.sun_path); + } + } else if (chmod(srs_sa.sun_path, S_IRUSR | S_IWUSR) == -1) { + ERRMSG("init_srs_socket", "chmod", errno); + MSG_ERR("srs_sa.sun_path = %s\n", srs_sa.sun_path); + } else if (listen(srs, QUEUE) == -1) { + ERRMSG("init_srs_socket", "listen", errno); + } else { + retval = 1; + } + + FD_SET(srs, &sock_set); + return (retval); +} + +/*! Initialise a single dpi socket + * \Return + * \li 1 on success + * \li -1 on failure + */ +int init_dpi_socket(struct dp *dpi_attr, char *sockdir) +{ + int caught_error = 0, s; + char *dpi_nm; /* pointer to basename in dpi_attr->path */ + struct sockaddr_un sa; + size_t sp_len; + socklen_t addr_sz; + size_t sock_buflen = 8192; + + sp_len = sizeof(sa.sun_path); + if ((s = socket(AF_LOCAL, SOCK_STREAM, 0)) == -1) { + ERRMSG("init_all_dpi_sockets", "socket", errno); + return (-1); /* avoids nested ifs */ + } + /* Set the socket FD to close on exec */ + fcntl(s, F_SETFD, FD_CLOEXEC | fcntl(s, F_GETFD)); + + /* set some buffering to increase the transfer's speed */ + setsockopt(s, SOL_SOCKET, SO_SNDBUF, + &sock_buflen, (socklen_t)sizeof(sock_buflen)); + + dpi_attr->socket = s; + dpi_attr->sa.sun_family = AF_LOCAL; + dpi_nm = get_basename(dpi_attr->path); + + dpi_attr->sockpath = dStrconcat(sockdir, "/", dpi_nm, NULL); + if (strlen(dpi_attr->sockpath) > sp_len) { + ERRMSG("init_all_dpi_sockets", "socket path is too long", 0); + MSG_ERR("\n - it should be <= %lu chars", (ulong_t)sp_len); + MSG_ERR("\n - socket path = %s\n", dpi_attr->sockpath); + return(-1); + } + strncpy(dpi_attr->sa.sun_path, dpi_attr->sockpath, sp_len); + addr_sz = (socklen_t) D_SUN_LEN(&dpi_attr->sa); + + if ((bind(s, (struct sockaddr *) &dpi_attr->sa, addr_sz)) == -1) { + ERRMSG("init_all_dpi_sockets", "bind", errno); + MSG_ERR("%s\n", dpi_attr->sa.sun_path); + caught_error = 1; + } else if (chmod(dpi_attr->sa.sun_path, S_IRUSR | S_IWUSR) == -1) { + ERRMSG("init_all_dpi_sockets", "chmod", errno); + MSG_ERR("%s\n", dpi_attr->sa.sun_path); + caught_error = 1; + } else if (listen(s, QUEUE) == -1) { + ERRMSG("init_all_dpi_sockets", "listen", errno); + caught_error = 1; + } + + if (caught_error) { + return (-1); + } else { + FD_SET(s, &sock_set); + return (1); + } +} + +/*! Setup sockets for the plugins and add them to + * to the set of sockets (sock_set) watched by select. + * \Return + * \li Number of sockets on success + * \li -1 on failure + * \Modifies + * dpi_attr_list.sa, dpi_attr_list.socket, numsocks, sock_set, srs + * \Uses + * numdpis, srs, srs_name + */ +int init_all_dpi_sockets(struct dp *dpi_attr_list, char *sockdir) +{ + int i; + struct sockaddr_un sa; + size_t sp_len; + + sp_len = sizeof(sa.sun_path); + + /* Initialise sockets for each dpi */ + for (i = 0; i < numdpis; i++) { + if (init_dpi_socket(dpi_attr_list + i, sockdir) == -1) + return (-1); + numsocks++; + } + + return (numsocks); +} + +/*! SIGCHLD handler + */ +void dpi_sigchld(int sig) +{ + caught_sigchld = 1; +} + +/*! Called by main loop when caught_sigchld == 1 */ +void handle_sigchld(void) +{ + // pid_t pid; + int i, status; //, num_active; + + /* For all of the dpis in the current list + * add the ones that have exited to the set of sockets being + * watched by 'select'. + */ + for (i = 0; i < numdpis; i++) { + if (waitpid(dpi_attr_list[i].pid, &status, WNOHANG) > 0) { + dpi_attr_list[i].pid = 1; + FD_SET(dpi_attr_list[i].socket, &sock_set); + numsocks++; + } + } + + /* Wait for any old dpis that have exited */ + while (waitpid(-1, &status, WNOHANG) > 0) + ; +} + +/*! Establish SIGCHLD handler */ +void est_dpi_sigchld(void) +{ + struct sigaction sigact; + sigset_t set; + + (void) sigemptyset(&set); + sigact.sa_handler = dpi_sigchld; + sigact.sa_mask = set; + sigact.sa_flags = SA_NOCLDSTOP; + if (sigaction(SIGCHLD, &sigact, NULL) == -1) { + ERRMSG("est_dpi_sigchld", "sigaction", errno); + exit(1); + } +} + +/*! Send DpiBye command to all active non-filter dpis + */ +void stop_active_dpis(struct dp *dpi_attr_list, int numdpis) +{ + static char *DpiBye_cmd = NULL; + int i, dpi_socket; + struct sockaddr_un dpi_addr; + struct sockaddr_un sa; + size_t sun_path_len, addr_len; + + if (!DpiBye_cmd) + DpiBye_cmd = a_Dpip_build_cmd("cmd=%s", "DpiBye"); + + sun_path_len = sizeof(sa.sun_path); + addr_len = sizeof(dpi_addr); + + dpi_addr.sun_family = AF_LOCAL; + + for (i = 0; i < numdpis; i++) { + /* Skip inactive dpis and filters */ + if (dpi_attr_list[i].pid == 1 || dpi_attr_list[i].filter) + continue; + + if ((dpi_socket = socket(AF_LOCAL, SOCK_STREAM, 0)) == -1) { + ERRMSG("stop_active_dpis", "socket", errno); + } + if (strlen(dpi_attr_list[i].sockpath) > sun_path_len) { + ERRMSG("stop_active_dpis", "socket path is too long", 0); + MSG_ERR("\n - it should be <= %lu chars",(ulong_t)sun_path_len); + MSG_ERR("\n - socket path = %s\n", dpi_attr_list[i].sockpath); + } + strncpy(dpi_addr.sun_path, dpi_attr_list[i].sockpath, sun_path_len); + if (connect(dpi_socket, (struct sockaddr *) &dpi_addr, addr_len) == -1) { + ERRMSG("stop_active_dpis", "connect", errno); + MSG_ERR("%s\n", dpi_addr.sun_path); + } + (void) write(dpi_socket, DpiBye_cmd, strlen(DpiBye_cmd)); + a_Misc_close_fd(dpi_socket); + } +} + +/*! Removes dpis in dpi_attr_list from the + * set of sockets watched by select and + * closes their sockets. + */ +void ignore_dpi_sockets(struct dp *dpi_attr_list, int numdpis) +{ + int i; + + for (i = 0; i < numdpis; i++) { + FD_CLR(dpi_attr_list[i].socket, &sock_set); + a_Misc_close_fd(dpi_attr_list[i].socket); + } +} + +/*! Registers available dpis and stops active non-filter dpis. + * Called when dpid receives + * cmd='register' service='all' + * command + * \Return + * Number of available dpis + */ +int register_all_cmd(char *sockdir) +{ + stop_active_dpis(dpi_attr_list, numdpis); + rm_dpi_sockets(dpi_attr_list, numdpis); + free_plugin_list(&dpi_attr_list, numdpis); + numdpis = 0; + numsocks = 1; /* the srs socket */ + FD_ZERO(&sock_set); + FD_SET(srs, &sock_set); + numdpis = register_all(&dpi_attr_list); + numsocks = init_all_dpi_sockets(dpi_attr_list, sockdir); + return (numdpis); +} + +/*! + * Get value of msg field from dpi_tag + * \Return + * message on success, NULL on failure + */ +char *get_message(int sock, char *dpi_tag) +{ + char *msg, *d_cmd; + + msg = a_Dpip_get_attr(dpi_tag, strlen(dpi_tag), "msg"); + if (msg == NULL) { + ERRMSG("get_message", "failed to parse msg", 0); + d_cmd = a_Dpip_build_cmd("cmd=%s msg=%s", + "DpiError", "Failed to parse request"); + (void) CKD_WRITE(sock, d_cmd); + dFree(d_cmd); + } + return (msg); +} + +/*! + * Send socket path that matches dpi_id to client + */ +void send_sockpath(int sock, char *dpi_tag, struct dp *dpi_attr_list) +{ + int i; + char *dpi_id; + char *d_cmd; + + dReturn_if_fail((dpi_id = get_message(sock, dpi_tag)) != NULL); + + for (i = 0; i < numdpis; i++) + if (strstr(dpi_attr_list[i].id, dpi_id)) + break; + if (i < numdpis) { + /* found */ + if (access(dpi_attr_list[i].path, F_OK) == -1) { + ERRMSG("send_sockpath", "access", errno); + MSG_ERR(" - %s\n", dpi_attr_list[i].sockpath); + d_cmd = a_Dpip_build_cmd("cmd=%s msg=%s", + "DpiError", "Plugin currently unavailable"); + (void) CKD_WRITE(sock, d_cmd); + dFree(d_cmd); + } else { + d_cmd = a_Dpip_build_cmd("cmd=%s msg=%s", + "send_data", dpi_attr_list[i].sockpath); + (void) CKD_WRITE(sock, d_cmd); + dFree(d_cmd); + } + } + + dFree(dpi_id); +} diff --git a/dpid/dpid.h b/dpid/dpid.h new file mode 100644 index 00000000..2a41f538 --- /dev/null +++ b/dpid/dpid.h @@ -0,0 +1,103 @@ +/*! \file + * Main functions to set-up dpi information and to initialise sockets + */ + +#ifndef DPID_H +#define DPID_H + +#include <assert.h> +#include <signal.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <stdlib.h> +#include <stdio.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/time.h> +#include <sys/un.h> +#include <errno.h> + +#include "d_size.h" + + +#define PATH_LEN 50 +#define CMDLEN 20 +#define MSGLEN 50 +/*! \todo: Should read this from dillorc */ +#define SRS_NAME "dpid.srs" +char *srs_name; + +/*! dpid service request socket */ +int srs; + +/*! plugin state information + */ +struct dp { + char *id; + char *path; + char *sockpath; + int socket; + struct sockaddr_un sa; + pid_t pid; + int filter; +}; + +/*! Number of available plugins */ +int numdpis; + +/*! Number of sockets being watched */ +int numsocks; + +/*! State information for each plugin. */ +struct dp *dpi_attr_list; + +/*! Set of sockets watched for connections */ +fd_set sock_set; + +/*! Set to 1 by the SIGCHLD handler dpi_sigchld */ +extern volatile sig_atomic_t caught_sigchld; + +void rm_dpi_sockets(struct dp *dpi_attr_list, int numdpis); + +int rm_inactive_dpi_sockets(struct dp *dpi_attr_list, int numdpis); + +void cleanup(char *socket_dir); + +void free_dpi_attr(struct dp *dpi_attr); + +void free_plugin_list(struct dp **dpi_attr_list_ptr, int numdpis); + +enum file_type get_file_type(char *file_name); + +int get_dpi_attr(char *dpi_dir, char *service, struct dp *dpi_attr); + +int register_service(struct dp *dpi_attr, char *service); + +int register_all(struct dp **attlist); + +int init_srs_socket(char *sockdir); + +int init_dpi_socket(struct dp *dpi_attr, char *sockdir); + +int init_all_dpi_sockets(struct dp *dpi_attr_list, char *sockdir); + +void dpi_sigchld(int sig); + +void handle_sigchld(void); + +void est_dpi_sigchld(void); + +void stop_active_dpis(struct dp *dpi_attr_list, int numdpis); + +void ignore_dpi_sockets(struct dp *dpi_attr_list, int numdpis); + +int register_all_cmd(char *sockdir); + +char *get_message(int sock, char *dpi_tag); + +void send_sockpath(int sock, char * dpi_tag, struct dp *dpi_attr_list); + +#endif diff --git a/dpid/dpid_common.c b/dpid/dpid_common.c new file mode 100644 index 00000000..a04d9c4f --- /dev/null +++ b/dpid/dpid_common.c @@ -0,0 +1,43 @@ +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include "dpid_common.h" + +/* + * Send a verbose error message. + */ +void errmsg(char *caller, char *called, int errornum, char *file, int line) +{ + MSG_ERR("%s:%d: %s: %s\n", file, line, caller, called); + if (errornum > 0) + MSG_ERR("%s\n", dStrerror(errornum)); +} + +/*! Selector function for scandir + * Do not scan files starting with '.' + */ +int no_dotfiles(const struct dirent *filedat) +{ + if (filedat->d_name[0] == '.') + return 0; + else + return 1; +} + +/*! + * Provides an error checked write command. + * Call this via the CKD_WRITE macro + * \return write return value + */ +ssize_t ckd_write(int fd, char *msg, char *file, int line) +{ + ssize_t ret; + + do { + ret = write(fd, msg, strlen(msg)); + } while (ret == -1 && errno == EINTR); + if (ret == -1) { + MSG_ERR("%s:%d: write: %s\n", file, line, dStrerror(errno)); + } + return (ret); +} diff --git a/dpid/dpid_common.h b/dpid/dpid_common.h new file mode 100644 index 00000000..4311a8a8 --- /dev/null +++ b/dpid/dpid_common.h @@ -0,0 +1,61 @@ +#ifndef DPID_COMMON_H +#define DPID_COMMON_H + +/*! \file + * Declares common functions, global variables, and types. + * + * \todo + * The dpid error codes will be used in + * the next patch + */ + +#include <stdio.h> +#include <errno.h> +#include <sys/types.h> +#include <dirent.h> + +#include "../dlib/dlib.h" + +/* + * Debugging macros + */ +#define _MSG(...) +#define MSG(...) printf("[dpid]: " __VA_ARGS__) +#define _MSG_ERR(...) +#define MSG_ERR(...) fprintf(stderr, "[dpid]: " __VA_ARGS__) + +#define dotDILLO_DPI ".dillo/dpi" +#define dotDILLO_DPIDRC ".dillo/dpidrc" +#define ERRMSG(CALLER, CALLED, ERR)\ + errmsg(CALLER, CALLED, ERR, __FILE__, __LINE__) +#define _ERRMSG(CALLER, CALLED, ERR) + + +/*! + * Macro for calling the ckd_write function + */ +#define CKD_WRITE(fd, msg) ckd_write(fd, msg, __FILE__, __LINE__) + + +/*! Error codes for dpid */ +enum { + no_errors, + dpid_srs_addrinuse /* dpid service request socket address already in use */ +} dpi_errno; + +/*! Intended for identifying dillo plugins + * and related files + */ +enum file_type { + DPI_FILE, /*! Any file name containing .dpi */ + UNKNOWN_FILE +}; + + +void errmsg(char *caller, char *called, int errornum, char *file, int line); + +int no_dotfiles(const struct dirent *filedat); + +ssize_t ckd_write(int fd, char *msg, char *file, int line); + +#endif diff --git a/dpid/dpidc b/dpid/dpidc new file mode 100644 index 00000000..88b887cb --- /dev/null +++ b/dpid/dpidc @@ -0,0 +1,31 @@ +#!/usr/bin/perl -w +# Author: Ferdi Franceschini +# +# dpid control program +# Currently allows +# register: Tells dpid to register all available dpis +# stop: Stops dpid. + +use strict; +use IO::Socket::UNIX; + +# Get socket directory name +open(DSD, "<$ENV{HOME}/.dillo/dpi_socket_dir"); +my $dir = <DSD>; +close(DSD); + +my $socket = IO::Socket::UNIX->new(Peer => "$dir/dpid.srs", Type => SOCK_STREAM, Timeout => 1000 ) or die "new: $@"; + +$socket->autoflush(1); + +my %dpi_command = ( + "register" => "<dpi cmd='register_all' '>", + "stop" => "<dpi cmd='DpiBye' '>", + ); + +if ( exists($dpi_command{$ARGV[0]}) ) { + print $socket $dpi_command{$ARGV[0]}; +} else { + close($socket); + print "Usage: dpidc register|stop\n"; +} diff --git a/dpid/main.c b/dpid/main.c new file mode 100644 index 00000000..09c46968 --- /dev/null +++ b/dpid/main.c @@ -0,0 +1,398 @@ +/* + Copyright (C) 2003 Ferdi Franceschini <ferdif@optusnet.com.au> + + 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 3 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 + */ + +#include <errno.h> +#include <unistd.h> +#include <limits.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <assert.h> +#include "dpid_common.h" +#include "dpid.h" +#include "dpi.h" +#include "dpi_socket_dir.h" +#include "misc_new.h" +#include "../dpip/dpip.h" + +sigset_t mask_sigchld; + + +/* Start a dpi filter plugin after accepting the pending connection + * \Return + * \li Child process ID on success + * \li 0 on failure + */ +static int start_filter_plugin(struct dp dpi_attr) +{ + int newsock, old_stdout=-1, old_stdin=-1; + socklen_t csz; + struct sockaddr_un clnt_addr; + pid_t pid; + + csz = (socklen_t) sizeof(clnt_addr); + + newsock = accept(dpi_attr.socket, (struct sockaddr *) &clnt_addr, &csz); + if (newsock == -1) + ERRMSG("start_plugin", "accept", errno); + + dup2(STDIN_FILENO, old_stdin); + if (dup2(newsock, STDIN_FILENO) == -1) { + ERRMSG("start_plugin", "dup2", errno); + MSG_ERR("ERROR in child proc for %s\n", dpi_attr.path); + exit(1); + } + + dup2(STDOUT_FILENO, old_stdout); + if (dup2(newsock, STDOUT_FILENO) == -1) { + ERRMSG("start_plugin", "dup2", errno); + MSG_ERR("ERROR in child proc for %s\n", dpi_attr.path); + exit(1); + } + if ((pid = fork()) == -1) { + ERRMSG("main", "fork", errno); + return 0; + } + if (pid == 0) { + /* Child, start plugin */ + if (execl(dpi_attr.path, dpi_attr.path, NULL) == -1) { + ERRMSG("start_plugin", "execl", errno); + MSG_ERR("ERROR in child proc for %s\n", dpi_attr.path); + exit(1); + } + } + + /* Parent, Close sockets fix stdio and return pid */ + if (a_Misc_close_fd(newsock) == -1) { + ERRMSG("start_plugin", "close", errno); + MSG_ERR("ERROR in child proc for %s\n", dpi_attr.path); + exit(1); + } + a_Misc_close_fd(STDIN_FILENO); + a_Misc_close_fd(STDOUT_FILENO); + dup2(old_stdin, STDIN_FILENO); + dup2(old_stdout, STDOUT_FILENO); + return pid; +} + +static void start_server_plugin(struct dp dpi_attr) +{ + if (dup2(dpi_attr.socket, STDIN_FILENO) == -1) { + ERRMSG("start_plugin", "dup2", errno); + MSG_ERR("ERROR in child proc for %s\n", dpi_attr.path); + exit(1); + } + if (a_Misc_close_fd(dpi_attr.socket) == -1) { + ERRMSG("start_plugin", "close", errno); + MSG_ERR("ERROR in child proc for %s\n", dpi_attr.path); + exit(1); + } + if (execl(dpi_attr.path, dpi_attr.path, NULL) == -1) { + ERRMSG("start_plugin", "execl", errno); + MSG_ERR("ERROR in child proc for %s\n", dpi_attr.path); + exit(1); + } +} + +/*! + * Read service request from sock + * \Return + * pointer to dynamically allocated request tag + */ +static char *get_request(int sock) +{ + char *req, buf[10]; + size_t buflen; + size_t rqsz; + ssize_t rdln; + + req = NULL; + buf[0] = '\0'; + buflen = sizeof(buf) / sizeof(buf[0]); + + (void) sigprocmask(SIG_BLOCK, &mask_sigchld, NULL); + for (rqsz = 0; (rdln = read(sock, buf, buflen)) != 0; rqsz += rdln) { + if (rdln == -1) + break; + req = (char *) realloc(req, rqsz + rdln + 1); + if (rqsz == 0) + req[0] = '\0'; + strncat(req, buf, (size_t) rdln); + } + (void) sigprocmask(SIG_UNBLOCK, &mask_sigchld, NULL); + if (rdln == -1) { + ERRMSG("get_request", "read", errno); + } + + return (req); +} + +/*! + * Get value of cmd field in dpi_tag + * \Return + * command code on success, -1 on failure + */ +static int get_command(int sock, char *dpi_tag, struct dp *dpi_attr_list) +{ + char *cmd, *d_cmd; + int COMMAND; + + if (dpi_tag == NULL) { + _ERRMSG("get_command", "dpid tag is NULL", 0); + return (-1); + } + + cmd = a_Dpip_get_attr(dpi_tag, strlen(dpi_tag), "cmd"); + + if (cmd == NULL) { + ERRMSG("get_command", "a_Dpip_get_attr", 0); + MSG_ERR(": dpid failed to parse cmd in %s\n", dpi_tag); + d_cmd = a_Dpip_build_cmd("cmd=%s msg=%s", + "DpiError", "Failed to parse request"); + (void) CKD_WRITE(sock, d_cmd); + dFree(d_cmd); + COMMAND = -1; + } else if (strcmp("DpiBye", cmd) == 0) { + COMMAND = BYE_CMD; + } else if (strcmp("check_server", cmd) == 0) { + COMMAND = CHECK_SERVER_CMD; + } else if (strcmp("register_all", cmd) == 0) { + COMMAND = REGISTER_ALL_CMD; + } else if (strcmp("register_service", cmd) == 0) { + COMMAND = REGISTER_SERVICE_CMD; + } else { /* Error unknown command */ + COMMAND = UNKNOWN_CMD; + } + + dFree(cmd); + return (COMMAND); +} + +/* + * Check whether a dpi server is running + */ +int server_is_running(char *server_id) +{ + int i; + + /* Search in the set of running servers */ + for (i = 0; i < numdpis; i++) { + if (!dpi_attr_list[i].filter && dpi_attr_list[i].pid > 1 && + strcmp(dpi_attr_list[i].id, server_id) == 0) + return 1; + } + return 0; +} + + +/* + * Get MAX open FD limit (yes, it's tricky --Jcid). + */ +static int get_open_max(void) +{ +#ifdef OPEN_MAX + return OPEN_MAX; +#else + int ret = sysconf(_SC_OPEN_MAX); + if (ret < 0) + ret = 256; + return ret; +#endif +} + +/*! \todo + * \li Add a dpid_idle_timeout variable to dpidrc + * \bug Infinite loop if plugin crashes before it accepts a connection + */ +int main(void) +{ + int i, n = 0, open_max; + char *dirname = NULL, *sockdir = NULL; + int dpid_idle_timeout = 60 * 60; /* default, in seconds */ + struct timeval select_timeout; + sigset_t mask_none; + fd_set selected_set; + + dpi_attr_list = NULL; + //daemon(0,0); /* Use 0,1 for feedback */ + /* todo: call setsid() ?? */ + + /* Allow read and write access, but only for the user. + * todo: can this cause trouble with umount? */ + umask(0077); + /* todo: make dpid work on any directory. */ + // chdir("/"); + + /* close inherited file descriptors */ + open_max = get_open_max(); + for (i = 3; i < open_max; i++) + a_Misc_close_fd(i); + + /* this sleep used to unmask a race condition */ + // sleep(2); + + dpi_errno = no_errors; + + /* Get list of available dpis */ + numdpis = register_all(&dpi_attr_list); + + /* Get name of socket directory */ + dirname = a_Dpi_sockdir_file(); + if ((sockdir = init_sockdir(dirname)) == NULL) { + ERRMSG("main", "init_sockdir", 0); + MSG_ERR("Failed to create socket directory\n"); + exit(1); + } + + /* Remove any sockets that may have been leftover from a crash */ + cleanup(sockdir); + /* Initialise sockets */ + if ((numsocks = init_srs_socket(sockdir)) == -1) { + switch (dpi_errno) { + case dpid_srs_addrinuse: + MSG_ERR("dpid refuses to start, possibly because:\n"); + MSG_ERR("\t1) An instance of dpid is already running.\n"); + MSG_ERR("\t2) A previous dpid didn't clean up on exit.\n"); + exit(1); + default: + ERRMSG("main", "init_srs_sockets failed", 0); + exit(1); + } + } + numsocks = init_all_dpi_sockets(dpi_attr_list, sockdir); + //est_terminator(); /* Do we still want to clean up on an abnormal exit? */ + est_dpi_sigchld(); + + (void) sigemptyset(&mask_sigchld); + (void) sigaddset(&mask_sigchld, SIGCHLD); + (void) sigemptyset(&mask_none); + (void) sigprocmask(SIG_SETMASK, &mask_none, NULL); + + printf("dpid started\n"); +/* Start main loop */ + while (1) { + do { + (void) sigprocmask(SIG_BLOCK, &mask_sigchld, NULL); + if (caught_sigchld) { + handle_sigchld(); + caught_sigchld = 0; + } + (void) sigprocmask(SIG_UNBLOCK, &mask_sigchld, NULL); + select_timeout.tv_sec = dpid_idle_timeout; + select_timeout.tv_usec = 0; + selected_set = sock_set; + n = select(FD_SETSIZE, &selected_set, NULL, NULL, &select_timeout); + if (n == 0) { /* select timed out, try to exit */ + /* BUG: This is a workaround for dpid not to exit when the + * downloads server is active. The proper way to handle it is with + * a dpip command that asks the server whether it's busy. + * Note: the cookies server may lose session info too. */ + if (server_is_running("downloads")) + continue; + + stop_active_dpis(dpi_attr_list, numdpis); + cleanup(sockdir); + exit(0); + } + } while (n == -1 && errno == EINTR); + + if (n == -1) { + ERRMSG("main", "select", errno); + exit(1); + } + /* If the service req socket is selected then service the req. */ + if (FD_ISSET(srs, &selected_set)) { + int sock; + socklen_t csz; + struct sockaddr_un clnt_addr; + char *req = NULL; + + --n; + assert(n >= 0); + csz = (socklen_t) sizeof(clnt_addr); + sock = accept(srs, (struct sockaddr *) &clnt_addr, &csz); + if (sock == -1) { + ERRMSG("main", "accept", errno); + MSG_ERR("accept on srs socket failed\n"); + MSG_ERR("service pending connections, and continue\n"); + } else { + int command; + + req = get_request(sock); + command = get_command(sock, req, dpi_attr_list); + switch (command) { + case BYE_CMD: + stop_active_dpis(dpi_attr_list, numdpis); + cleanup(sockdir); + exit(0); + break; + case CHECK_SERVER_CMD: + send_sockpath(sock, req, dpi_attr_list); + break; + case REGISTER_ALL_CMD: + register_all_cmd(sockdir); + break; + case UNKNOWN_CMD: + { + char *d_cmd = a_Dpip_build_cmd("cmd=%s msg=%s", + "DpiError", "Unknown command"); + (void) CKD_WRITE(sock, d_cmd); + dFree(d_cmd); + ERRMSG("main", "Unknown command", 0); + MSG_ERR(" for request: %s\n", req); + break; + } + case -1: + _ERRMSG("main", "get_command failed", 0); + break; + } + if (req) + free(req); + a_Misc_close_fd(sock); + } + } + + /* While there's a request on one of the plugin sockets + * find the matching plugin and start it. */ + for (i = 0; n > 0 && i < numdpis; i++) { + if (FD_ISSET(dpi_attr_list[i].socket, &selected_set)) { + --n; + assert(n >= 0); + + if (dpi_attr_list[i].filter) { + /* start a dpi filter plugin and continue watching its socket + * for new connections */ + (void) sigprocmask(SIG_SETMASK, &mask_none, NULL); + start_filter_plugin(dpi_attr_list[i]); + } else { + /* start a dpi server plugin but don't wait for new connections + * on its socket */ + numsocks--; + assert(numsocks >= 0); + FD_CLR(dpi_attr_list[i].socket, &sock_set); + if ((dpi_attr_list[i].pid = fork()) == -1) { + ERRMSG("main", "fork", errno); + /* exit(1); */ + } else if (dpi_attr_list[i].pid == 0) { + (void) sigprocmask(SIG_SETMASK, &mask_none, NULL); + start_server_plugin(dpi_attr_list[i]); + } + } + } + } + } +} diff --git a/dpid/misc_new.c b/dpid/misc_new.c new file mode 100644 index 00000000..038e1fa6 --- /dev/null +++ b/dpid/misc_new.c @@ -0,0 +1,172 @@ +#include <stdio.h> +#include <time.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <errno.h> +#include <fcntl.h> +#include "d_size.h" +#include "misc_new.h" +#include "dpid_common.h" + +#include "misc_new.h" /* for function prototypes */ + + +/* + * Close a FD handling EINTR. + */ +int a_Misc_close_fd(int fd) +{ + int st; + + do { + st = close(fd); + } while (st < 0 && errno == EINTR); + return st; +} + +/*! Reads a dpi tag from a socket + * \li Continues after a signal interrupt + * \Return + * Dstr pointer to tag on success, NULL on failure + * \important Caller is responsible for freeing the returned Dstr * + */ +Dstr *a_Misc_rdtag(int socket) +{ + char c = '\0'; + ssize_t rdlen; + Dstr *tag; + + tag = dStr_sized_new(64); + + errno = 0; + + do { + rdlen = read(socket, &c, 1); + if (rdlen == -1 && errno != EINTR) + break; + dStr_append_c(tag, c); + } while (c != '>'); + + if (rdlen == -1) { + perror("a_Misc_rdtag"); + dStr_free(tag, TRUE); + return (NULL); + } + return (tag); +} + +/*! + * Read a dpi tag from sock + * \return + * pointer to dynamically allocated request tag + */ +char *a_Misc_readtag(int sock) +{ + char *tag, c, buf[10]; + size_t buflen, i; + size_t taglen = 0, tagmem = 10; + ssize_t rdln = 1; + + tag = NULL; + buf[0] = '\0'; + buflen = sizeof(buf) / sizeof(buf[0]); + // new start + tag = (char *) dMalloc(tagmem + 1); + for (i = 0; (rdln = read(sock, &c, 1)) != 0; i++) { + if (i == tagmem) { + tagmem += tagmem; + tag = (char *) dRealloc(tag, tagmem + 1); + } + tag[i] = c; + taglen += rdln; + if (c == '>') { + tag[i + 1] = '\0'; + break; + } + } + // new end + if (rdln == -1) { + ERRMSG("a_Misc_readtag", "read", errno); + } + + return (tag); +} + +/*! Reads a dpi tag from a socket without hanging on read. + * \li Continues after a signal interrupt + * \Return + * \li 1 on success + * \li 0 if input is not available within timeout microseconds. + * \li -1 on failure + * \important Caller is responsible for freeing the returned Dstr * + */ +/* Is this useful? +int a_Misc_nohang_rdtag(int socket, int timeout, Dstr **tag) +{ + int n_fd; + fd_set sock_set, select_set; + struct timeval tout; + + FD_ZERO(&sock_set); + FD_SET(socket, &sock_set); + + errno = 0; + do { + select_set = sock_set; + tout.tv_sec = 0; + tout.tv_usec = timeout; + n_fd = select(socket + 1, &select_set, NULL, NULL, &tout); + } while (n_fd == -1 && errno == EINTR); + + if (n_fd == -1) { + MSG_ERR("%s:%d: a_Misc_nohang_rdtag: %s\n", + __FILE__, __LINE__, dStrerror(errno)); + return(-1); + } + if (n_fd == 0) { + return(0); + } else { + *tag = a_Misc_rdtag(socket); + return(1); + } +} +*/ + +/* + * Alternative to mkdtemp(). + * Not as strong as mkdtemp, but enough for creating a directory. + * (adapted from dietlibc) + */ +char *a_Misc_mkdtemp(char *template) +{ + char *tmp = template + strlen(template) - 6; + int i; + unsigned int random; + + if (tmp < template) + goto error; + for (i = 0; i < 6; ++i) + if (tmp[i] != 'X') { + error: + errno = EINVAL; + return 0; + } + srand((uint_t)(time(0) ^ getpid())); + for (;;) { + random = (unsigned) rand(); + for (i = 0; i < 6; ++i) { + int hexdigit = (random >> (i * 5)) & 0x1f; + + tmp[i] = hexdigit > 9 ? hexdigit + 'a' - 10 : hexdigit + '0'; + } + if (mkdir(template, 0700) == 0) + break; + if (errno == EEXIST) + continue; + return 0; + } + return template; +} diff --git a/dpid/misc_new.h b/dpid/misc_new.h new file mode 100644 index 00000000..94e10676 --- /dev/null +++ b/dpid/misc_new.h @@ -0,0 +1,12 @@ +#ifndef MISC_NEW_H +#define MISC_NEW_H + +#include "../dlib/dlib.h" + + +int a_Misc_close_fd(int fd); +Dstr *a_Misc_rdtag(int socket); +char *a_Misc_readtag(int sock); +char *a_Misc_mkdtemp(char *template); + +#endif diff --git a/dpip/Makefile.am b/dpip/Makefile.am new file mode 100644 index 00000000..099ac7d4 --- /dev/null +++ b/dpip/Makefile.am @@ -0,0 +1,5 @@ +noinst_LIBRARIES = libDpip.a + +libDpip_a_SOURCES = \ + dpip.h \ + dpip.c diff --git a/dpip/dpip.c b/dpip/dpip.c new file mode 100644 index 00000000..846f61b0 --- /dev/null +++ b/dpip/dpip.c @@ -0,0 +1,168 @@ +/* + * File: dpip.c + * + * Copyright 2005 Jorge Arellano Cid <jcid@dillo.org> + * + * 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 3 of the License, or + * (at your option) any later version. + * + */ + +#include <stdio.h> +#include <stdarg.h> +#include <string.h> + +#include "../dlib/dlib.h" +#include "dpip.h" +#include "d_size.h" + +static char Quote = '\''; + +/* + * Basically the syntax of a dpip tag is: + * + * "<"[*alpha] *(<name>"="Quote<escaped_value>Quote) " "Quote">" + * + * 1.- No space is allowed around the "=" sign between a name and its value. + * 2.- The Quote character is not allowed in <name>. + * 3.- Attribute values stuff Quote as QuoteQuote. + * + * e.g. (with ' as Quote): + * + * <a='b' b='c' '> OK + * <dpi a='b i' b='12' '> OK + * <a='>' '> OK + * <a='ain''t no doubt' '> OK + * <a='ain''t b=''no'' b='' doubt' '> OK + * <a = '>' '> Wrong + * + * Notes: + * + * Restriction #1 is for easy finding of end of tag (EOT=Space+Quote+>). + * Restriction #2 can be removed, but what for? ;) + * The functions here provide for this functionality. + */ + +typedef enum { + SEEK_NAME, + MATCH_NAME, + SKIP_VALUE, + SKIP_QUOTE, + FOUND +} DpipTagParsingState; + +/* ------------------------------------------------------------------------- */ + +/* + * Printf like function for building dpip commands. + * It takes care of dpip escaping of its arguments. + * NOTE : It ONLY accepts string parameters, and + * only one %s per parameter. + */ +char *a_Dpip_build_cmd(const char *format, ...) +{ + va_list argp; + char *p, *q, *s; + Dstr *cmd; + + /* Don't allow Quote characters in attribute names */ + if (strchr(format, Quote)) + return NULL; + + cmd = dStr_sized_new(64); + dStr_append_c(cmd, '<'); + va_start(argp, format); + for (p = q = (char*)format; *q; ) { + p = strstr(q, "%s"); + if (!p) { + dStr_append(cmd, q); + break; + } else { + /* Copy format's part */ + while (q != p) + dStr_append_c(cmd, *q++); + q += 2; + + dStr_append_c(cmd, Quote); + /* Stuff-copy of argument */ + s = va_arg (argp, char *); + for ( ; *s; ++s) { + dStr_append_c(cmd, *s); + if (*s == Quote) + dStr_append_c(cmd, *s); + } + dStr_append_c(cmd, Quote); + } + } + va_end(argp); + dStr_append_c(cmd, ' '); + dStr_append_c(cmd, Quote); + dStr_append_c(cmd, '>'); + + p = cmd->str; + dStr_free(cmd, FALSE); + return p; +} + +/* + * Task: given a tag and an attribute name, return its value. + * (stuffing of ' is removed here) + * Return value: the attribute value, or NULL if not present or malformed. + */ +char *a_Dpip_get_attr(char *tag, size_t tagsize, char *attrname) +{ + uint_t i, n = 0, found = 0; + char *p, *q, *start, *val = NULL; + DpipTagParsingState state = SEEK_NAME; + + if (!attrname || !*attrname) + return NULL; + + for (i = 1; i < tagsize && !found; ++i) { + switch (state) { + case SEEK_NAME: + if (tag[i] == attrname[0] && (tag[i-1] == ' ' || tag[i-1] == '<')) { + n = 1; + state = MATCH_NAME; + } else if (tag[i] == Quote && tag[i-1] == '=') + state = SKIP_VALUE; + break; + case MATCH_NAME: + if (tag[i] == attrname[n]) + ++n; + else if (tag[i] == '=' && !attrname[n]) + state = FOUND; + else + state = SEEK_NAME; + break; + case SKIP_VALUE: + if (tag[i] == Quote) + state = (tag[i+1] == Quote) ? SKIP_QUOTE : SEEK_NAME; + break; + case SKIP_QUOTE: + state = SKIP_VALUE; + break; + case FOUND: + found = 1; + break; + } + } + + if (found) { + p = start = tag + i; + while ((q = strchr(p, Quote)) && q[1] == Quote) + p = q + 2; + if (q && q[1] == ' ') { + val = dStrndup(start, (uint_t)(q - start)); + for (p = q = val; (*q = *p); ++p, ++q) + if (*p == Quote && p[1] == p[0]) + ++p; + } + } + return val; +} + +/* ------------------------------------------------------------------------- */ + diff --git a/dpip/dpip.h b/dpip/dpip.h new file mode 100644 index 00000000..8a8623bc --- /dev/null +++ b/dpip/dpip.h @@ -0,0 +1,34 @@ +/* + * Library for dealing with dpip tags (dillo plugin protocol tags). + */ + +#ifndef __DPIP_H__ +#define __DPIP_H__ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +/* + * Printf like function for building dpip commands. + * It takes care of dpip escaping of its arguments. + * NOTE : It ONLY accepts string parameters, and + * only one %s per parameter. + */ +char *a_Dpip_build_cmd(const char *format, ...); + +/* + * Task: given a tag and an attribute name, return its value. + * (dpip character escaping is removed here) + * Return value: the attribute value, or NULL if not present or malformed. + */ +char *a_Dpip_get_attr(char *tag, size_t tagsize, char *attrname); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __DPIP_H__ */ + diff --git a/install-dpi-local b/install-dpi-local new file mode 100755 index 00000000..e843e6c1 --- /dev/null +++ b/install-dpi-local @@ -0,0 +1,39 @@ +#!/bin/sh +# +# Install the dpi framework programs inside the user's account. +# + +BASE="$HOME/.dillo" +BASE2="$BASE/dpi" + +if [ -r $BASE/dpi_socket_dir ] ; then + rm -r `cat $BASE/dpi_socket_dir` + rm $BASE/dpi_socket_dir +fi + +if [ ! -x dpid/dpid ] ; then + echo "You MUST run this script after make." + exit 1 +fi + +if [ ! -d $BASE ] ; then + mkdir $BASE +fi +if [ ! -d $BASE2 ] ; then + mkdir $BASE2 +fi + +cp dpid/dpid dpid/dpidc $BASE +strip $BASE/dpid + +cd dpi +for F in *.dpi ; do + D="`echo $F | sed 's/\..*$//'`" + if [ ! -d $BASE2/$D ] ; then + mkdir $BASE2/$D + fi + cp $F $BASE2/$D + strip $BASE2/$D/$F +done +cd .. + diff --git a/src/IO/IO.c b/src/IO/IO.c new file mode 100644 index 00000000..060fe200 --- /dev/null +++ b/src/IO/IO.c @@ -0,0 +1,412 @@ +/* + * File: IO.c + * + * Copyright (C) 2000-2006 Jorge Arellano Cid <jcid@dillo.org> + * + * 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 3 of the License, or + * (at your option) any later version. + */ + +/* + * Dillo's event driven IO engine + */ + +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/stat.h> +#include <sys/uio.h> +#include <sys/socket.h> +#include "../msg.h" +#include "../chain.h" +#include "../klist.h" +#include "../list.h" +#include "IO.h" +#include "iowatch.hh" + +#define DEBUG_LEVEL 5 +//#define DEBUG_LEVEL 1 +#include "../debug.h" + +/* + * Symbolic defines for shutdown() function + * (Not defined in the same header file, for all distros --Jcid) + */ +#define IO_StopRd 0 +#define IO_StopWr 1 +#define IO_StopRdWr 2 + + +typedef struct { + int Key; /* Primary Key (for klist) */ + int Op; /* IORead | IOWrite */ + int FD; /* Current File Descriptor */ + int Flags; /* Flag array (look definitions above) */ + int Status; /* errno code */ + Dstr *Buf; /* Internal buffer */ + + void *Info; /* CCC Info structure for this IO */ + int events; /* FLTK events for this IO */ +} IOData_t; + + +/* + * Local data + */ +static Klist_t *ValidIOs = NULL; /* Active IOs list. It holds pointers to + * IOData_t structures. */ + +/* + * Forward declarations + */ +void a_IO_ccc(int Op, int Branch, int Dir, ChainLink *Info, + void *Data1, void *Data2); + + +/* IO API - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +/* + * Return a newly created, and initialized, 'io' struct + */ +static IOData_t *IO_new(int op, int fd) +{ + IOData_t *io = dNew0(IOData_t, 1); + io->Op = op; + io->FD = fd; + io->Flags = 0; + io->Key = 0; + io->Buf = dStr_sized_new(IOBufLen); + + return io; +} + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +/* + * Register an IO in ValidIOs + */ +static void IO_ins(IOData_t *io) +{ + if (io->Key == 0) { + io->Key = a_Klist_insert(&ValidIOs, io); + } +} + +/* + * Remove an IO from ValidIOs + */ +static void IO_del(IOData_t *io) +{ + if (io->Key != 0) { + a_Klist_remove(ValidIOs, io->Key); + } + io->Key = 0; + _MSG(" -->ValidIOs: %d\n", a_Klist_length(ValidIOs)); +} + +/* + * Return a io by its Key (NULL if not found) + */ +static IOData_t *IO_get(int Key) +{ + return (IOData_t *)a_Klist_get_data(ValidIOs, Key); +} + +/* + * Free an 'io' struct + */ +static void IO_free(IOData_t *io) +{ + dStr_free(io->Buf, 1); + dFree(io); +} + +/* + * Close an open FD, and remove io controls. + * (This function can be used for Close and Abort operations) + */ +static void IO_close_fd(IOData_t *io, int CloseCode) +{ + int st; + + /* With HTTP, if we close the writing part, the reading one also gets + * closed! (other clients may set 'IOFlag_ForceClose') */ + if ((io->Flags & IOFlag_ForceClose) || (CloseCode == IO_StopRdWr)) { + do + st = close(io->FD); + while (st < 0 && errno == EINTR); + } + /* Remove this IOData_t reference, from our ValidIOs list + * We don't deallocate it here, just remove from the list.*/ + IO_del(io); + + /* Stop the polling on this FD */ + a_IOwatch_remove_fd(io->FD, io->events); +} + +/* + * Read data from a file descriptor into a specific buffer + */ +static bool_t IO_read(IOData_t *io) +{ + char Buf[IOBufLen]; + ssize_t St; + bool_t ret = FALSE; + + DEBUG_MSG(3, " IO_read\n"); + + /* this is a new read-buffer */ + dStr_truncate(io->Buf, 0); + io->Status = 0; + + while (1) { + St = read(io->FD, Buf, IOBufLen); + if (St > 0) { + dStr_append_l(io->Buf, Buf, St); + continue; + } else if (St < 0) { + if (errno == EINTR) { + continue; + } else if (errno == EAGAIN) { + ret = TRUE; + break; + } else { + io->Status = errno; + break; + } + } else { /* St == 0 */ + break; + } + } + + if (io->Buf->len > 0) { + /* send what we've got so far */ + a_IO_ccc(OpSend, 2, FWD, io->Info, io, NULL); + } + if (St == 0) { + /* All data read (EOF) */ + a_IO_ccc(OpEnd, 2, FWD, io->Info, io, NULL); + } + return ret; +} + +/* + * Write data, from a specific buffer, into a file descriptor + */ +static bool_t IO_write(IOData_t *io) +{ + ssize_t St; + bool_t ret = FALSE; + + DEBUG_MSG(3, " IO_write\n"); + io->Status = 0; + + while (1) { + St = write(io->FD, io->Buf->str, io->Buf->len); + if (St < 0) { + /* Error */ + if (errno == EINTR) { + continue; + } else if (errno == EAGAIN) { + ret = TRUE; + break; + } else { + io->Status = errno; + break; + } + } else if (St < io->Buf->len) { + /* Not all data written */ + dStr_erase (io->Buf, 0, St); + } else { + /* All data in buffer written */ + dStr_truncate(io->Buf, 0); + break; + } + } + + return ret; +} + +/* + * Handle background IO for a given FD (reads | writes) + * (This function gets called when there's activity in the FD) + */ +static int IO_callback(int fd, IOData_t *io) +{ + bool_t ret = FALSE; + + _MSG("IO_callback:: (%s) FD = %d\n", + (io->Op == IORead) ? "IORead" : "IOWrite", io->FD); + + if (io->Op == IORead) { /* Read */ + ret = IO_read(io); + } else if (io->Op == IOWrite) { /* Write */ + ret = IO_write(io); + } + return (ret) ? 1 : 0; +} + +/* + * Handle the READ event of a FD. + */ +static void IO_fd_read_cb(int fd, void *data) +{ + int io_key = (int)data; + IOData_t *io = IO_get(io_key); + + /* There should be no more events on already closed FDs --Jcid */ + if (io == NULL) { + MSG_ERR("IO_fd_read_cb: call on already closed io!\n"); + a_IOwatch_remove_fd(fd, DIO_READ); + + } else { + if (IO_callback(fd, io) == 0) + a_IOwatch_remove_fd(fd, DIO_READ); + } +} + +/* + * Handle the WRITE event of a FD. + */ +static void IO_fd_write_cb(int fd, void *data) +{ + int io_key = (int)data; + IOData_t *io = IO_get(io_key); + + if (io == NULL) { + /* There must be no more events on already closed FDs --Jcid */ + MSG_ERR("IO_fd_write_cb: call on already closed io!\n"); + a_IOwatch_remove_fd(fd, DIO_WRITE); + + } else { + if (IO_callback(fd, io) == 0) + a_IOwatch_remove_fd(fd, DIO_WRITE); + } +} + +/* + * Receive an IO request (IORead | IOWrite), + * Set a watch for it, and let it flow! + */ +static void IO_submit(IOData_t *r_io) +{ + /* Insert this IO in ValidIOs */ + IO_ins(r_io); + + _MSG("IO_submit:: (%s) FD = %d\n", + (io->Op == IORead) ? "IORead" : "IOWrite", io->FD); + + /* Set FD to background and to close on exec. */ + fcntl(r_io->FD, F_SETFL, O_NONBLOCK | fcntl(r_io->FD, F_GETFL)); + fcntl(r_io->FD, F_SETFD, FD_CLOEXEC | fcntl(r_io->FD, F_GETFD)); + + if (r_io->Op == IORead) { + r_io->events = DIO_READ; + a_IOwatch_add_fd(r_io->FD, r_io->events, + IO_fd_read_cb, (void*)(r_io->Key)); + + } else if (r_io->Op == IOWrite) { + r_io->events = DIO_WRITE; + a_IOwatch_add_fd(r_io->FD, r_io->events, + IO_fd_write_cb, (void*)(r_io->Key)); + } +} + +/* + * CCC function for the IO module + * ( Data1 = IOData_t* ; Data2 = NULL ) + */ +void a_IO_ccc(int Op, int Branch, int Dir, ChainLink *Info, + void *Data1, void *Data2) +{ + IOData_t *io; + DataBuf *dbuf; + + a_Chain_debug_msg("a_IO_ccc", Op, Branch, Dir); + + if (Branch == 1) { + if (Dir == BCK) { + /* Write data using select */ + switch (Op) { + case OpStart: + io = IO_new(IOWrite, *(int*)Data1); /* SockFD */ + Info->LocalKey = io; + break; + case OpSend: + io = Info->LocalKey; + dbuf = Data1; + dStr_append_l(io->Buf, dbuf->Buf, dbuf->Size); + IO_submit(io); + break; + case OpEnd: + case OpAbort: + io = Info->LocalKey; + if (io->Buf->len > 0) { + MSG_WARN("IO_write, closing with pending data not sent\n"); + MSG_WARN(" \"%s\"\n", io->Buf->str); + } + /* close FD, remove from ValidIOs and remove its watch */ + IO_close_fd(io, IO_StopRdWr); + IO_free(io); + dFree(Info); + break; + default: + MSG_WARN("Unused CCC\n"); + break; + } + } else { /* FWD */ + /* Write-data status */ + switch (Op) { + default: + MSG_WARN("Unused CCC\n"); + break; + } + } + + } else if (Branch == 2) { + if (Dir == BCK) { + /* This part catches the reader's messages */ + switch (Op) { + case OpStart: + io = IO_new(IORead, *(int*)Data2); /* SockFD */ + Info->LocalKey = io; + io->Info = Info; + IO_submit(io); + break; + case OpAbort: + io = Info->LocalKey; + IO_close_fd(io, IO_StopRdWr); + IO_free(io); + dFree(Info); + break; + default: + MSG_WARN("Unused CCC\n"); + break; + } + } else { /* FWD */ + /* Send read-data */ + io = Data1; + switch (Op) { + case OpSend: + dbuf = a_Chain_dbuf_new(io->Buf->str, io->Buf->len, 0); + a_Chain_fcb(OpSend, Info, dbuf, NULL); + dFree(dbuf); + break; + case OpEnd: + a_Chain_fcb(OpEnd, Info, NULL, NULL); + IO_close_fd(io, IO_StopRdWr); + IO_free(io); + dFree(Info); + break; + default: + MSG_WARN("Unused CCC\n"); + break; + } + } + } +} + diff --git a/src/IO/IO.h b/src/IO/IO.h new file mode 100644 index 00000000..71ed25b4 --- /dev/null +++ b/src/IO/IO.h @@ -0,0 +1,45 @@ +#ifndef __IO_H__ +#define __IO_H__ + +#include <unistd.h> +#include <sys/uio.h> + +#include "d_size.h" +#include "../../dlib/dlib.h" +#include "../chain.h" +#include "iowatch.hh" + +/* + * IO Operations + */ +#define IORead 0 +#define IOWrite 1 +#define IOClose 2 +#define IOAbort 3 + +/* + * IO Flags (unused) + */ +#define IOFlag_ForceClose (1 << 1) +#define IOFlag_SingleWrite (1 << 2) + +/* + * IO constants + */ +#define IOBufLen 8192 + + +/* + * Exported functions + */ +/* Note: a_IO_ccc() is defined in Url.h together with the *_ccc() set */ + + +/* + * Exported data + */ +extern const char *AboutSplash; + + +#endif /* __IO_H__ */ + diff --git a/src/IO/Makefile.am b/src/IO/Makefile.am new file mode 100644 index 00000000..bff4667f --- /dev/null +++ b/src/IO/Makefile.am @@ -0,0 +1,14 @@ +noinst_LIBRARIES = libDiof.a + +libDiof_a_SOURCES = \ + mime.c \ + mime.h \ + about.c \ + Url.h \ + proto.c \ + http.c \ + dpi.c \ + IO.c \ + iowatch.cc \ + iowatch.hh \ + IO.h diff --git a/src/IO/Url.h b/src/IO/Url.h new file mode 100644 index 00000000..91a9c1bd --- /dev/null +++ b/src/IO/Url.h @@ -0,0 +1,40 @@ +#ifndef __IO_URL_H__ +#define __IO_URL_H__ + +#include "../chain.h" +#include "../url.h" + + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* + * External functions + */ +extern void a_Http_freeall(void); +int a_Http_init(void); +int a_Http_proxy_auth(void); +void a_Http_set_proxy_passwd(char *str); +char *a_Http_make_query_str(const DilloUrl *url, bool_t use_proxy); + +void a_Http_ccc (int Op, int Branch, int Dir, ChainLink *Info, + void *Data1, void *Data2); +void a_About_ccc(int Op, int Branch, int Dir, ChainLink *Info, + void *Data1, void *Data2); +void a_IO_ccc (int Op, int Branch, int Dir, ChainLink *Info, + void *Data1, void *Data2); +void a_Dpi_ccc (int Op, int Branch, int Dir, ChainLink *Info, + void *Data1, void *Data2); + +char *a_Dpi_send_blocking_cmd(const char *server_name, const char *cmd); +void a_Dpi_bye_dpid(void); +void a_Dpi_init(void); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __IO_URL_H__ */ + diff --git a/src/IO/about.c b/src/IO/about.c new file mode 100644 index 00000000..a3a0c420 --- /dev/null +++ b/src/IO/about.c @@ -0,0 +1,344 @@ +/* + * File: about.c + * + * Copyright (C) 1999-2006 Jorge Arellano Cid <jcid@dillo.org> + * + * 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 3 of the License, or + * (at your option) any later version. + */ + +#include <config.h> + +/* + * HTML text for startup screen + */ +const char *AboutSplash= +"<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN'>\n" +"<html>\n" +"<head>\n" +"<title>Splash screen for dillo-" VERSION "</title>\n" +"</head>\n" +"<body bgcolor='#778899' text='#000000' link='#000000' vlink='#000000'>\n" +"\n" +"\n" +"<!-- the head of the page -->\n" +"\n" +"<table width='100%' border='0' cellspacing='1' cellpadding='3'>\n" +" <tr><td>\n" +" <table border='1' cellspacing='1' cellpadding='0'>\n" +" <tr>\n" +" <td bgcolor='#000000'>\n" +" <table width='100%' border='0' bgcolor='#ffffff'>\n" +" <tr>\n" +" <td valign='top' align='left'>\n" +" <h1> Welcome to Dillo " VERSION " </h1>\n" +" </table>\n" +" </table>\n" +"</table>\n" +"\n" +"<br>\n" +"\n" +"\n" +"<!-- the main layout table, definition -->\n" +"\n" +"<table width='100%' border='0' cellspacing='0' cellpadding='0'>\n" +"<tr><td valign='top' width='150' align='center'>\n" +"\n" +"\n" +"<!-- The navigation bar -->\n" +"\n" +"<table border='0' cellspacing='0' cellpadding='0' width='140' bgcolor='#000000'>\n" +"<tr>\n" +" <td>\n" +" <table width='100%' border='0' cellspacing='1' cellpadding='3'>\n" +" <tr>\n" +" <td colspan='1' bgcolor='#CCCCCC'>Dillo\n" +" <tr>\n" +" <td bgcolor='#FFFFFF'>\n" +" <table border='0' cellspacing='0' cellpadding='5'><tr><td>\n" +" <table border='0' cellspacing='0' cellpadding='2'><tr>\n" +" <td>\n" +" <td>\n" +" <a href='http://www.dillo.org/dillo-help.html'>\n" +" Help</a>\n" +" <tr>\n" +" <td> \n" +" <td>\n" +" <a href='http://www.dillo.org/'>Home</a>\n" +" <tr>\n" +" <td> \n" +" <td>\n" +" <a href='http://www.dillo.org/funding/objectives.html'>\n" +" Objectives</a>\n" +" <tr>\n" +" <td> \n" +" <td>\n" +" <a href='http://www.dillo.org/ChangeLog.html'>\n" +" ChangeLog</a>\n" +" <tr>\n" +" <td> \n" +" <td>\n" +" <a href='http://www.dillo.org/interview.html'>\n" +" Interview</a>\n" +" <tr>\n" +" <td> \n" +" <td>\n" +" <a href='http://www.dillo.org/D_authors.html'>\n" +" Authors</a>\n" +" <tr>\n" +" <td> \n" +" <td>\n" +" <a href='http://www.dillo.org/donations.html'>\n" +" Donate</a>\n" +" </table>\n" +" </table>\n" +" </table>\n" +"</table>\n" +"\n" +"<br>\n" +"\n" +"<table border='0' cellspacing='0' cellpadding='0' width='140' bgcolor='#000000'>\n" +"<tr>\n" +" <td>\n" +" <table width='100%' border='0' cellspacing='1' cellpadding='3'>\n" +" <tr>\n" +" <td colspan='1' bgcolor='#CCCCCC'>Magazines\n" +"\n" +" <tr>\n" +" <td bgcolor='#FFFFFF'>\n" +" <table border='0' cellspacing='0' cellpadding='5'><tr><td>\n" +" <table border='0' cellpadding='2'>\n" +" <tr>\n" +" <td> \n" +" <td>\n" +" <a href='http://lwn.net/'>LWN</a>\n" +" <tr>\n" +" <td> \n" +" <td>\n" +" <a href='http://slashdot.org/'>Slashdot</a>\n" +" <tr>\n" +" <td> \n" +" <td>\n" +" <a href='http://www.kuro5hin.org/?op=section;section=__all__'>KuroShin</a>\n" +" <tr>\n" +" <td> \n" +" <td>\n" +" <a href='http://www.nexusmagazine.com/'>Nexus M.</a>\n" +" <tr>\n" +" <td> \n" +" <td>\n" +" <a href='http://www.gnu-darwin.org/update.html'>Monster News</a>\n" +" <tr>\n" +" <td> \n" +" <td>\n" +" <a href='http://www.theregister.co.uk/index.html'>The Register</a>\n" +" </table>\n" +" </table>\n" +" </table>\n" +"</table>\n" +"\n" +"<br>\n" +"\n" +"<table border='0' cellspacing='0' cellpadding='0' width='140' bgcolor='#000000'>\n" +"<tr>\n" +" <td>\n" +" <table width='100%' border='0' cellspacing='1' cellpadding='3'>\n" +" <tr>\n" +" <td colspan='1' bgcolor='#CCCCCC'>Additional Stuff\n" +"\n" +" <tr>\n" +" <td bgcolor='#FFFFFF'>\n" +" <table border='0' cellspacing='0' cellpadding='5'><tr><td>\n" +" <table border='0' cellpadding='2'><tr>\n" +" <td> \n" +" <td><a href='http://www.google.com/'>Google</a>\n" +" <tr>\n" +" <td> \n" +" <td><a href='http://www.wikipedia.org/'>Wikipedia</a>\n" +" <tr>\n" +" <td> \n" +" <td><a href='http://www.gutenberg.org/'>P. Gutenberg</a>\n" +" <tr>\n" +" <td> \n" +" <td><a href='http://freshmeat.net/'>FreshMeat</a>\n" +" <tr>\n" +" <td> \n" +" <td><a href='http://www.gnu.org/gnu/thegnuproject.html'>GNU\n" +" project</a>\n" +" <tr>\n" +" <td> \n" +" <td><a href='http://www.linuxfund.org/'>LinuxFund</a>\n" +" </table>\n" +" </table>\n" +" </table>\n" +"</table>\n" +"\n" +"<br>\n" +"\n" +"<table border='0' cellspacing='0' cellpadding='0' width='140' bgcolor='#000000'>\n" +"<tr>\n" +" <td>\n" +" <table width='100%' border='0' cellspacing='1' cellpadding='3'>\n" +" <tr>\n" +" <td colspan='1' bgcolor='#CCCCCC'>Essential Readings\n" +"\n" +" <tr>\n" +" <td bgcolor='#FFFFFF'>\n" +" <table border='0' cellspacing='0' cellpadding='5'><tr><td>\n" +" <table border='0' cellpadding='2'>\n" +" <tr><td> \n" +" <td><a href='http://www.violence.de'>Peace&Violence</a>\n" +" <tr><td> \n" +" <td><a href='http://www.fsf.org/philosophy/right-to-read.html'>" +" Right to Read</a>\n" +" </table>\n" +" </table>\n" +" </table>\n" +"</table>\n" +"\n" +"<table border='0' width='100%' cellpadding='0' cellspacing='0'><tr><td height='10'></table>\n" +"\n" +"\n" +"<!-- the main layout table, a small vertical spacer -->\n" +"\n" +"<td width='20'><td valign='top'>\n" +"\n" +"\n" +"<!-- Main Part of the page -->\n" +"\n" +"<table border='0' cellpadding='0' cellspacing='0' align='center' bgcolor='#000000' width='100%'><tr><td>\n" +"<table border='0' cellpadding='5' cellspacing='1' width='100%'>\n" +"<tr>\n" +" <td bgcolor='#CCCCCC'>\n" +" <h4>Free Software</h4>\n" +"<tr>\n" +" <td bgcolor='#FFFFFF'>\n" +" <table border='0' cellspacing='0' cellpadding='5'><tr><td>\n" +" <p>\n" +" Dillo is Free Software in the terms of the GPL.\n" +" This means you have four basic freedoms:\n" +" <ul>\n" +" <li>Freedom to use the program any way you see fit.\n" +" <li>Freedom to study and modify the source code.\n" +" <li>Freedom to make backup copies.\n" +" <li>Freedom to redistribute it.\n" +" </ul>\n" +" The <a href='http://www.gnu.org/licenses/gpl.html'>GPL</a>\n" +" is the legal mechanism that gives you these freedoms.\n" +" It also protects them from being taken away: any derivative work\n" +" based on the program must be under the GPL.<br>\n" +" </table>\n" +"</table>\n" +"</table>\n" +"\n" +"<br>\n" +"\n" +"<table border='0' cellpadding='0' cellspacing='0' align='center' bgcolor='#000000' width='100%'><tr><td>\n" +"<table border='0' cellpadding='5' cellspacing='1' width='100%'>\n" +"<tr>\n" +" <td bgcolor='#CCCCCC'>\n" +" <h4>Release overview</h4>\n" +" ??, 2005\n" +"<tr>\n" +" <td bgcolor='#FFFFFF'>\n" +" <table border='0' cellspacing='0' cellpadding='5'>\n" +" <tr>\n" +" <td>\n" +"<p>\n" +"[...]\n" +"<p>\n" +"Remember that dillo project uses a release model where every new\n" +"browser shall be better than the former.\n" +"<EM>Keep up with the latest one!</EM>\n" +" </table>\n" +"</table>\n" +"</table>\n" +"\n" +"<br>\n" +"\n" +"<table border='0' cellpadding='0' cellspacing='0' align='center' bgcolor='#000000' width='100%'><tr><td>\n" +"<table border='0' cellpadding='5' cellspacing='1' width='100%'>\n" +"<tr>\n" +" <td bgcolor='#CCCCCC'>\n" +" <h4>ChangeLog highlights</h4>\n" +" (Extracted from the\n" +" <a href='http://www.dillo.org/ChangeLog.html'>full\n" +" ChangeLog</a>)\n" +"<tr>\n" +" <td bgcolor='#FFFFFF'>\n" +" <table border='0' cellspacing='0' cellpadding='5'>\n" +" <tr>\n" +" <td>\n" +"<ul>\n" +"<li>[...]\n" +"</ul>\n" +" </table>\n" +"</table>\n" +"</table>\n" +"\n" +"<br>\n" +"\n" +"<table border='0' cellpadding='0' cellspacing='0' align='center' bgcolor='#000000' width='100%'><tr><td>\n" +"<table border='0' cellpadding='5' cellspacing='1' width='100%'>\n" +"<tr>\n" +" <td bgcolor='#CCCCCC'>\n" +" <h4>Notes</h4>\n" +"<tr>\n" +" <td bgcolor='#FFFFFF'>\n" +" <table border='0' cellspacing='0' cellpadding='5'>\n" +" <tr>\n" +" <td>\n" +"<ul>\n" +" <li> There's a\n" +" <a href='http://www.dillo.org/dillorc'>dillorc</a>\n" +" (readable config) file within the tarball; It is well commented\n" +" and has plenty of options to customize dillo, so <STRONG>copy\n" +" it</STRONG> to your <STRONG>~/.dillo/</STRONG> directory, and\n" +" modify to your taste.\n" +" <li> There's documentation for developers in the <CODE>/doc</CODE>\n" +" dir within the tarball; you can find directions on everything\n" +" else at the home page.\n" +" <li> Dillo has context sensitive menus using the\n" +" right mouse button (available on pages, links, images,\n" +" the Back and Forward buttons, and bug meter).\n" +" <li> Dillo behaves very nicely when browsing local files, images, and HTML.\n" +" It's also very good for Internet searching (try Google!).\n" +" <li> This release is mainly intended <strong>for developers</strong>\n" +" and <em>advanced users</em>.\n" +" <li> Frames, Java and Javascript are not supported.\n" +"</ul>\n" +"<br>\n" +" </table>\n" +"</table>\n" +"</table>\n" +"\n" +"<table border='0' width='100%' cellpadding='0' cellspacing='0'><tr><td height='10'></table>\n" +"\n" +"\n" +"<!-- the main layout table, a small vertical spacer -->\n" +"\n" +"<td width='20'>\n" +"\n" +"\n" +"\n" +"<!-- The right column (info) -->\n" +"<td valign='top' align='center'>\n" +"\n" +"\n" +"\n" +"<!-- end of the main layout table -->\n" +"\n" +"\n" +"</table>\n" +"\n" +"<!-- footnotes -->\n" +"\n" +"<br><br><center>\n" +"<hr size='2'>\n" +"<hr size='2'>\n" +"</center>\n" +"</body>\n" +"</html>\n"; + diff --git a/src/IO/dpi.c b/src/IO/dpi.c new file mode 100644 index 00000000..13cd1f74 --- /dev/null +++ b/src/IO/dpi.c @@ -0,0 +1,779 @@ +/* + * File: dpi.c + * + * Copyright (C) 2002-2006 Jorge Arellano Cid <jcid@dillo.org> + * + * 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 3 of the License, or + * (at your option) any later version. + */ + +/* + * Dillo plugins (small programs that interact with dillo) + * + * Dillo plugins are designed to handle: + * bookmarks, cookies, FTP, downloads, files, preferences, https, + * datauri and a lot of any-to-html filters. + */ + + +#include <unistd.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <string.h> +#include <stdio.h> +#include <errno.h> /* for errno */ + +#include <stdio.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> + +#include "../msg.h" +#include "../klist.h" +#include "IO.h" +#include "Url.h" +#include "../misc.h" +#include "../../dpip/dpip.h" + +/* #define DEBUG_LEVEL 2 */ +#define DEBUG_LEVEL 4 +#include "../debug.h" + +/* This one is tricky, some sources state it should include the byte + * for the terminating NULL, and others say it shouldn't. */ +# define D_SUN_LEN(ptr) ((size_t) (((struct sockaddr_un *) 0)->sun_path) \ + + strlen ((ptr)->sun_path)) + +/* Solaris may not have this one... */ +#ifndef AF_LOCAL +#define AF_LOCAL AF_UNIX +#endif + + +typedef struct { + int InTag; + int Send2EOF; + + int DataTotalSize; + int DataRecvSize; + + Dstr *Buf; + + int BufIdx; + int TokIdx; + int TokSize; + int TokIsTag; + + ChainLink *InfoRecv; + int Key; +} dpi_conn_t; + + +/* + * Local data + */ +static Klist_t *ValidConns = NULL; /* Active connections list. It holds + * pointers to dpi_conn_t structures. */ + + +/* + * Initialize local data + */ +void a_Dpi_init(void) +{ + /* empty */ +} + +/* + * Close a FD handling EINTR + */ +static void Dpi_close_fd(int fd) +{ + int st; + + do + st = close(fd); + while (st < 0 && errno == EINTR); +} + +/* + * Create a new connection data structure + */ +static dpi_conn_t *Dpi_conn_new(ChainLink *Info) +{ + dpi_conn_t *conn = dNew0(dpi_conn_t, 1); + + conn->Buf = dStr_sized_new(8*1024); + conn->InfoRecv = Info; + conn->Key = a_Klist_insert(&ValidConns, conn); + + return conn; +} + +/* + * Free a connection data structure + */ +static void Dpi_conn_free(dpi_conn_t *conn) +{ + a_Klist_remove(ValidConns, conn->Key); + dStr_free(conn->Buf, 1); + dFree(conn); +} + +/* + * Check whether a conn is still valid. + * Return: 1 if found, 0 otherwise + */ +int Dpi_conn_valid(int key) +{ + return (a_Klist_get_data(ValidConns, key)) ? 1 : 0; +} + +/* + * Append the new buffer in 'dbuf' to Buf in 'conn' + */ +static void Dpi_append_dbuf(dpi_conn_t *conn, DataBuf *dbuf) +{ + if (dbuf->Code == 0 && dbuf->Size > 0) { + dStr_append_l(conn->Buf, dbuf->Buf, dbuf->Size); + } +} + +/* + * Split the data stream into tokens. + * Here, a token is either: + * a) a dpi tag + * b) a raw data chunk + * + * Return Value: 0 upon a new token, -1 on not enough data. + * + * TODO: define an API and move this function into libDpip.a. +*/ +static int Dpi_get_token(dpi_conn_t *conn) +{ + int i, resp = -1; + char *buf = conn->Buf->str; + + if (conn->BufIdx == conn->Buf->len) { + dStr_truncate(conn->Buf, 0); + conn->BufIdx = 0; + return resp; + } + + if (conn->Send2EOF) { + conn->TokIdx = conn->BufIdx; + conn->TokSize = conn->Buf->len - conn->BufIdx; + conn->BufIdx = conn->Buf->len; + return 0; + } + + _MSG("conn->BufIdx = %d; conn->Buf->len = %d\nbuf: [%s]\n", + conn->BufIdx,conn->Buf->len, conn->Buf->str + conn->BufIdx); + + if (!conn->InTag) { + /* search for start of tag */ + while (conn->BufIdx < conn->Buf->len && buf[conn->BufIdx] != '<') + ++conn->BufIdx; + if (conn->BufIdx < conn->Buf->len) { + /* found */ + conn->InTag = 1; + conn->TokIdx = conn->BufIdx; + } else { + MSG_ERR("[Dpi_get_token] Can't find token start\n"); + } + } + + if (conn->InTag) { + /* search for end of tag (EOT=" '>") */ + for (i = conn->BufIdx; i < conn->Buf->len; ++i) + if (buf[i] == '>' && i >= 2 && buf[i-1] == '\'' && buf[i-2] == ' ') + break; + conn->BufIdx = i; + + if (conn->BufIdx < conn->Buf->len) { + /* found EOT */ + conn->TokIsTag = 1; + conn->TokSize = conn->BufIdx - conn->TokIdx + 1; + ++conn->BufIdx; + conn->InTag = 0; + resp = 0; + } + } + + return resp; +} + +/* + * Parse a dpi tag and take the appropriate actions + */ +static void Dpi_parse_token(dpi_conn_t *conn) +{ + char *tag, *cmd, *msg, *urlstr; + DataBuf *dbuf; + char *Tok = conn->Buf->str + conn->TokIdx; + + if (conn->Send2EOF) { + /* we're receiving data chunks from a HTML page */ + dbuf = a_Chain_dbuf_new(Tok, conn->TokSize, 0); + a_Chain_fcb(OpSend, conn->InfoRecv, dbuf, "send_page_2eof"); + dFree(dbuf); + return; + } + + tag = dStrndup(Tok, (size_t)conn->TokSize); + _MSG("Dpi_parse_token: {%s}\n", tag); + + cmd = a_Dpip_get_attr(Tok, conn->TokSize, "cmd"); + if (strcmp(cmd, "send_status_message") == 0) { + msg = a_Dpip_get_attr(Tok, conn->TokSize, "msg"); + a_Chain_fcb(OpSend, conn->InfoRecv, msg, cmd); + dFree(msg); + + } else if (strcmp(cmd, "chat") == 0) { + msg = a_Dpip_get_attr(Tok, conn->TokSize, "msg"); + a_Chain_fcb(OpSend, conn->InfoRecv, msg, cmd); + dFree(msg); + + } else if (strcmp(cmd, "dialog") == 0) { + /* For now will send the dpip tag... */ + a_Chain_fcb(OpSend, conn->InfoRecv, tag, cmd); + + } else if (strcmp(cmd, "start_send_page") == 0) { + conn->Send2EOF = 1; + urlstr = a_Dpip_get_attr(Tok, conn->TokSize, "url"); + a_Chain_fcb(OpSend, conn->InfoRecv, urlstr, cmd); + dFree(urlstr); + /* todo: a_Dpip_get_attr(Tok, conn->TokSize, "send_mode") */ + + } else if (strcmp(cmd, "reload_request") == 0) { + urlstr = a_Dpip_get_attr(Tok, conn->TokSize, "url"); + a_Chain_fcb(OpSend, conn->InfoRecv, urlstr, cmd); + dFree(urlstr); + } + dFree(cmd); + + dFree(tag); +} + + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +/* + * Get a new data buffer (within a 'dbuf'), save it into local data, + * split in tokens and parse the contents. + */ +static void Dpi_process_dbuf(int Op, void *Data1, dpi_conn_t *conn) +{ + DataBuf *dbuf = Data1; + int key = conn->Key; + + /* Very useful for debugging: show the data stream as received. */ + /* fwrite(dbuf->Buf, dbuf->Size, 1, stdout); */ + + if (Op == IORead) { + Dpi_append_dbuf(conn, dbuf); + /* 'conn' has to be validated because Dpi_parse_token() MAY call abort */ + while (Dpi_conn_valid(key) && Dpi_get_token(conn) != -1) { + Dpi_parse_token(conn); + } + + } else if (Op == IOClose) { + /* unused */ + } +} + +/* + * Start dpid. + * Return: 0 starting now, 1 Error. + */ +static int Dpi_start_dpid(void) +{ + pid_t pid; + int st_pipe[2], n, ret = 1; + char buf[16]; + + /* create a pipe to track our child's status */ + if (pipe(st_pipe)) + return 1; + + pid = fork(); + if (pid == 0) { + /* This is the child process. Execute the command. */ + char *path1 = dStrconcat(dGethomedir(), "/.dillo/dpid", NULL); + Dpi_close_fd(st_pipe[0]); + if (execl(path1, "dpid", NULL) == -1) { + dFree(path1); + if (execlp("dpid", "dpid", NULL) == -1) { + DEBUG_MSG(4, "Dpi_start_dpid (child): %s\n", dStrerror(errno)); + do + n = write(st_pipe[1], "ERROR", 5); + while (n == -1 && errno == EINTR); + Dpi_close_fd(st_pipe[1]); + _exit (EXIT_FAILURE); + } + } + } else if (pid < 0) { + /* The fork failed. Report failure. */ + DEBUG_MSG(4, "Dpi_start_dpid: %s\n", dStrerror(errno)); + /* close the unused pipe */ + Dpi_close_fd(st_pipe[0]); + Dpi_close_fd(st_pipe[1]); + + } else { + /* This is the parent process, check our child status... */ + Dpi_close_fd(st_pipe[1]); + do + n = read(st_pipe[0], buf, 16); + while (n == -1 && errno == EINTR); + DEBUG_MSG(2, "Dpi_start_dpid: n = %d\n", n); + if (n != 5) { + ret = 0; + } else { + DEBUG_MSG(4, "Dpi_start_dpid: %s\n", dStrerror(errno)); + } + } + + return ret; +} + +/* + * Make a connection test for a UDS. + * Return: 0 OK, 1 Not working. + */ +static int Dpi_check_uds(char *uds_name) +{ + struct sockaddr_un pun; + int SockFD, ret = 1; + + if (access(uds_name, W_OK) == 0) { + /* socket connection test */ + memset(&pun, 0, sizeof(struct sockaddr_un)); + pun.sun_family = AF_LOCAL; + strncpy(pun.sun_path, uds_name, sizeof (pun.sun_path)); + + if ((SockFD = socket(AF_LOCAL, SOCK_STREAM, 0)) == -1 || + connect(SockFD, (void*)&pun, D_SUN_LEN(&pun)) == -1) { + DEBUG_MSG(4, "Dpi_check_uds: %s %s\n", dStrerror(errno), uds_name); + } else { + Dpi_close_fd(SockFD); + ret = 0; + } + } + return ret; +} + +/* + * Return the directory where the UDS are in, + * NULL if it can't be found. + */ +static char *Dpi_get_dpid_uds_dir(void) +{ + FILE *in; + char *saved_name_filename; /* :) */ + char dpid_uds_dir[256], *p = NULL; + + saved_name_filename = + dStrconcat(dGethomedir(), "/.dillo/dpi_socket_dir", NULL); + in = fopen(saved_name_filename, "r"); + dFree(saved_name_filename); + + if (in != NULL) { + fgets(dpid_uds_dir, 256, in); + fclose(in); + if ((p = strchr(dpid_uds_dir, '\n'))) { + *p = 0; + } + if (access(dpid_uds_dir, F_OK) == 0) { + p = dStrdup(dpid_uds_dir); + _MSG("Dpi_get_dpid_uds_dir:: %s\n", p); + } + } + + _MSG("Dpi_get_dpid_uds_dir: %s \n", dStrerror(errno)); + return p; +} + +/* + * Return the dpid's UDS name, NULL on failure. + */ +static char *Dpi_get_dpid_uds_name(void) +{ + char *dpid_uds_dir, *dpid_uds_name = NULL; + + if ((dpid_uds_dir = Dpi_get_dpid_uds_dir()) != NULL) + dpid_uds_name= dStrconcat(dpid_uds_dir, "/", "dpid.srs", NULL); + + dFree(dpid_uds_dir); + return dpid_uds_name; +} + +/* + * Confirm that the dpid is running. If not, start it. + * Return: 0 running OK, 1 starting (EAGAIN), 2 Error. + */ +static int Dpi_check_dpid(int num_tries) +{ + static int starting = 0; + char *dpid_uds_name; + int check_st = 1, ret = 2; + + if ((dpid_uds_name = Dpi_get_dpid_uds_name())) + check_st = Dpi_check_uds(dpid_uds_name); + + _MSG("Dpi_check_dpid: dpid_uds_name=%s, check_st=%d\n", + dpid_uds_name, check_st); + + if (check_st == 0) { + /* connection test with dpi server passed */ + starting = 0; + ret = 0; + } else if (!dpid_uds_name || check_st) { + if (!starting) { + /* start dpid */ + if (Dpi_start_dpid() == 0) { + starting = 1; + ret = 1; + } + } else if (++starting < num_tries) { + ret = 1; + } else { + /* we waited too much, report an error... */ + starting = 0; + } + } + + dFree(dpid_uds_name); + DEBUG_MSG(2, "Dpi_check_dpid:: %s\n", + (ret == 0) ? "OK" : (ret == 1 ? "EAGAIN" : "ERROR")); + return ret; +} + +/* + * Confirm that the dpid is running. If not, start it. + * Return: 0 running OK, 2 Error. + */ +static int Dpi_blocking_start_dpid(void) +{ + int cst, try = 0, + n_tries = 12; /* 3 seconds */ + + /* test the dpid, and wait a bit for it to start if necessary */ + while ((cst = Dpi_check_dpid(n_tries)) == 1) { + MSG("Dpi_blocking_start_dpid: try %d\n", ++try); + usleep(250000); /* 1/4 sec */ + } + return cst; +} + +/* + * Return the UDS name of a dpi server. + * (A query is sent to dpid and then its answer parsed) + * note: as the available servers and/or the dpi socket directory can + * change at any time, we'll ask each time. If someday we find + * that connecting each time significantly degrades performance, + * an optimized approach can be tried. + */ +static char *Dpi_get_server_uds_name(const char *server_name) +{ + char *dpid_uds_dir, *dpid_uds_name = NULL, + *server_uds_name = NULL; + int st; + + dReturn_val_if_fail (server_name != NULL, NULL); + DEBUG_MSG(2, "Dpi_get_server_uds_name:: server_name = [%s]\n", server_name); + + dpid_uds_dir = Dpi_get_dpid_uds_dir(); + if (dpid_uds_dir) { + struct sockaddr_un dpid; + int sock, req_sz, rdlen; + char buf[128], *cmd, *request, *rply; + size_t buflen; + + /* Get the server's uds name from dpid */ + sock = socket(AF_LOCAL, SOCK_STREAM, 0); + dpid.sun_family = AF_LOCAL; + dpid_uds_name = dStrconcat(dpid_uds_dir, "/", "dpid.srs", NULL); + _MSG("dpid_uds_name = [%s]\n", dpid_uds_name); + strncpy(dpid.sun_path, dpid_uds_name, sizeof(dpid.sun_path)); + + if (connect(sock, (struct sockaddr *) &dpid, sizeof(dpid)) == -1) + perror("connect"); + /* ask dpid to check the server plugin and send its UDS name back */ + request = a_Dpip_build_cmd("cmd=%s msg=%s", "check_server", server_name); + DEBUG_MSG(2, "[%s]\n", request); + do + st = write(sock, request, strlen(request)); + while (st < 0 && errno == EINTR); + if (st < 0 && errno != EINTR) + perror("writing request"); + dFree(request); + shutdown(sock, 1); /* signals no more writes to dpid */ + + /* Get the reply */ + rply = NULL; + buf[0] = '\0'; + buflen = sizeof(buf)/sizeof(buf[0]); + for (req_sz = 0; (rdlen = read(sock, buf, buflen)) != 0; + req_sz += rdlen) { + if (rdlen == -1 && errno == EINTR) + continue; + if (rdlen == -1) { + perror(" ** Dpi_get_server_uds_name **"); + break; + } + rply = dRealloc(rply, (uint_t)(req_sz + rdlen + 1)); + if (req_sz == 0) + rply[0] = '\0'; + strncat(rply, buf, (size_t)rdlen); + } + Dpi_close_fd(sock); + DEBUG_MSG(2, "rply = [%s]\n", rply); + + /* Parse reply */ + if (rdlen == 0 && rply) { + cmd = a_Dpip_get_attr(rply, (int)strlen(rply), "cmd"); + if (strcmp(cmd, "send_data") == 0) + server_uds_name = a_Dpip_get_attr(rply, (int)strlen(rply), "msg"); + dFree(cmd); + dFree(rply); + } + } + dFree(dpid_uds_dir); + dFree(dpid_uds_name); + DEBUG_MSG(2, "Dpi_get_server_uds_name:: %s\n", server_uds_name); + return server_uds_name; +} + + +/* + * Connect a socket to a dpi server and return the socket's FD. + * We have to ask 'dpid' (dpi daemon) for the UDS of the target dpi server. + * Once we have it, then the proper file descriptor is returned (-1 on error). + */ +static int Dpi_connect_socket(const char *server_name, int retry) +{ + char *server_uds_name; + struct sockaddr_un pun; + int SockFD, err; + + /* Query dpid for the UDS name for this server */ + server_uds_name = Dpi_get_server_uds_name(server_name); + DEBUG_MSG(2, "server_uds_name = [%s]\n", server_uds_name); + + if (access(server_uds_name, F_OK) != 0) { + MSG("server socket was NOT found\n"); + return -1; + } + + /* connect with this server's socket */ + memset(&pun, 0, sizeof(struct sockaddr_un)); + pun.sun_family = AF_LOCAL; + strncpy(pun.sun_path, server_uds_name, sizeof (pun.sun_path)); + dFree(server_uds_name); + + if ((SockFD = socket(AF_LOCAL, SOCK_STREAM, 0)) == -1) + perror("[dpi::socket]"); + else if (connect(SockFD, (void*)&pun, D_SUN_LEN(&pun)) == -1) { + err = errno; + SockFD = -1; + MSG("[dpi::connect] errno:%d %s\n", errno, dStrerror(errno)); + if (retry) { + switch (err) { + case ECONNREFUSED: case EBADF: case ENOTSOCK: case EADDRNOTAVAIL: + /* the server may crash and its socket name survive */ + unlink(pun.sun_path); + SockFD = Dpi_connect_socket(server_name, FALSE); + break; + } + } + } + + return SockFD; +} + + +/* + * CCC function for the Dpi module + */ +void a_Dpi_ccc(int Op, int Branch, int Dir, ChainLink *Info, + void *Data1, void *Data2) +{ + dpi_conn_t *conn; + int SockFD = -1, st; + + a_Chain_debug_msg("a_Dpi_ccc", Op, Branch, Dir); + + if (Branch == 1) { + if (Dir == BCK) { + /* Send commands to dpi-server */ + switch (Op) { + case OpStart: + if ((st = Dpi_blocking_start_dpid()) == 0) { + SockFD = Dpi_connect_socket(Data1, TRUE); + if (SockFD != -1) { + int *fd = dNew(int, 1); + *fd = SockFD; + Info->LocalKey = fd; + a_Chain_link_new(Info, a_Dpi_ccc, BCK, a_IO_ccc, 1, 1); + a_Chain_bcb(OpStart, Info, Info->LocalKey, NULL); + /* tell the capi to start the receiving branch */ + a_Chain_fcb(OpSend, Info, Info->LocalKey, "SockFD"); + } + } + + if (st == 0 && SockFD != -1) { + a_Chain_fcb(OpSend, Info, NULL, "DpidOK"); + } else { + MSG_ERR("dpi.c: can't start dpi daemon\n"); + a_Dpi_ccc(OpAbort, 1, FWD, Info, NULL, "DpidERROR"); + } + break; + case OpSend: + a_Chain_bcb(OpSend, Info, Data1, NULL); + break; + case OpEnd: + a_Chain_bcb(OpEnd, Info, NULL, NULL); + dFree(Info->LocalKey); + dFree(Info); + break; + default: + MSG_WARN("Unused CCC\n"); + break; + } + } else { /* FWD */ + /* Send commands to dpi-server (status) */ + switch (Op) { + case OpAbort: + a_Chain_fcb(OpAbort, Info, NULL, Data2); + dFree(Info); + break; + default: + MSG_WARN("Unused CCC\n"); + break; + } + } + + } else if (Branch == 2) { + if (Dir == FWD) { + /* Receiving from server */ + switch (Op) { + case OpSend: + /* Data1 = dbuf */ + Dpi_process_dbuf(IORead, Data1, Info->LocalKey); + break; + case OpEnd: + a_Chain_fcb(OpEnd, Info, NULL, NULL); + Dpi_conn_free(Info->LocalKey); + dFree(Info); + break; + default: + MSG_WARN("Unused CCC\n"); + break; + } + } else { /* BCK */ + switch (Op) { + case OpStart: + conn = Dpi_conn_new(Info); + Info->LocalKey = conn; + + /* Hack: for receiving HTTP through the DPI framework */ + if (strcmp(Data2, "http") == 0) { + conn->Send2EOF = 1; + } + + a_Chain_link_new(Info, a_Dpi_ccc, BCK, a_IO_ccc, 2, 2); + a_Chain_bcb(OpStart, Info, NULL, Data1); /* IORead, SockFD */ + break; + case OpAbort: + a_Chain_bcb(OpAbort, Info, NULL, NULL); + Dpi_conn_free(Info->LocalKey); + dFree(Info); + break; + default: + MSG_WARN("Unused CCC\n"); + break; + } + } + } +} + +/*! Send DpiBye to dpid + * Note: currently disabled. Maybe it'd be better to have a + * dpid_idle_timeout variable in the config file. + */ +void a_Dpi_bye_dpid() +{ + char *DpiBye_cmd; + struct sockaddr_un sa; + size_t sun_path_len, addr_len; + char *srs_name; + int new_socket; + + srs_name = Dpi_get_dpid_uds_name(); + sun_path_len = sizeof(sa.sun_path); + addr_len = sizeof(sa); + + sa.sun_family = AF_LOCAL; + + if ((new_socket = socket(AF_LOCAL, SOCK_STREAM, 0)) == -1) { + DEBUG_MSG(4, "a_Dpi_bye_dpid: %s\n", dStrerror(errno)); + } + strncpy(sa.sun_path, srs_name, sizeof (sa.sun_path)); + if (connect(new_socket, (struct sockaddr *) &sa, addr_len) == -1) { + DEBUG_MSG(4, "a_Dpi_bye_dpid: %s\n", dStrerror(errno)); + MSG("%s\n", sa.sun_path); + } + DpiBye_cmd = a_Dpip_build_cmd("cmd=%s", "DpiBye"); + (void) write(new_socket, DpiBye_cmd, strlen(DpiBye_cmd)); + dFree(DpiBye_cmd); + Dpi_close_fd(new_socket); +} + + +/* + * Send a command to a dpi server, and block until the answer is got. + * Return value: the dpip tag answer as an string, NULL on error. + */ +char *a_Dpi_send_blocking_cmd(const char *server_name, const char *cmd) +{ + int cst, SockFD; + ssize_t st; + char buf[16384], *retval = NULL; + + /* test the dpid, and wait a bit for it to start if necessary */ + if ((cst = Dpi_blocking_start_dpid()) != 0) { + return retval; + } + + SockFD = Dpi_connect_socket(server_name, TRUE); + if (SockFD != -1) { + /* todo: handle the case of (st < strlen(cmd)) */ + do + st = write(SockFD, cmd, strlen(cmd)); + while (st == -1 && errno == EINTR); + + /* todo: if the answer is too long... */ + do + st = read(SockFD, buf, 16384); + while (st < 0 && errno == EINTR); + + if (st == -1) + perror("[a_Dpi_send_blocking_cmd]"); + else if (st > 0) + retval = dStrndup(buf, (size_t)st); + + Dpi_close_fd(SockFD); + + } else { + perror("[a_Dpi_send_blocking_cmd]"); + } + + return retval; +} + diff --git a/src/IO/http.c b/src/IO/http.c new file mode 100644 index 00000000..f9b483fa --- /dev/null +++ b/src/IO/http.c @@ -0,0 +1,494 @@ +/* + * File: http.c + * + * Copyright (C) 2000, 2001 Jorge Arellano Cid <jcid@dillo.org> + * + * 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 3 of the License, or + * (at your option) any later version. + */ + +/* + * HTTP connect functions + */ + + +#include <config.h> + +#include <unistd.h> +#include <errno.h> /* for errno */ +#include <stdlib.h> +#include <signal.h> +#include <fcntl.h> +#include <sys/wait.h> +#include <sys/socket.h> /* for lots of socket stuff */ +#include <netinet/in.h> /* for ntohl and stuff */ +#include <arpa/inet.h> /* for inet_ntop */ + +#include "IO.h" +#include "Url.h" +#include "../msg.h" +#include "../klist.h" +#include "../dns.h" +#include "../cache.h" +#include "../web.hh" +#include "../cookies.h" +#include "../prefs.h" +#include "../misc.h" + +#include "../uicmd.hh" + +/* Used to send a message to the bw's status bar */ +#define MSG_BW(web, root, ...) \ +D_STMT_START { \ + if (a_Web_valid((web)) && (!(root) || (web)->flags & WEB_RootUrl)) \ + a_UIcmd_set_msg((web)->bw, __VA_ARGS__); \ +} D_STMT_END + +#define _MSG_BW(web, root, ...) + +#define DEBUG_LEVEL 5 +#include "../debug.h" + + + +/* 'Url' and 'web' are just references (no need to deallocate them here). */ +typedef struct { + int SockFD; + const DilloUrl *Url; /* reference to original URL */ + uint_t port; /* need a separate port in order to support PROXY */ + bool_t use_proxy; /* indicates whether to use proxy or not */ + DilloWeb *web; /* reference to client's web structure */ + Dlist *addr_list; /* Holds the DNS answer */ + int Err; /* Holds the errno of the connect() call */ + ChainLink *Info; /* Used for CCC asynchronous operations */ +} SocketData_t; + + +/* + * Local data + */ +static Klist_t *ValidSocks = NULL; /* Active sockets list. It holds pointers to + * SocketData_t structures. */ + +static DilloUrl *HTTP_Proxy = NULL; +static char *HTTP_Proxy_Auth_base64 = NULL; + +/* + * Initialize proxy vars. + */ +int a_Http_init(void) +{ + char *env_proxy = getenv("http_proxy"); + + if (env_proxy && strlen(env_proxy)) + HTTP_Proxy = a_Url_new(env_proxy, NULL, 0, 0, 0); + if (!HTTP_Proxy && prefs.http_proxy) + HTTP_Proxy = a_Url_dup(prefs.http_proxy); + +/* This allows for storing the proxy password in "user:passwd" format + * in dillorc, but as this constitutes a security problem, it was disabled. + * + if (HTTP_Proxy && prefs.http_proxyuser && strchr(prefs.http_proxyuser, ':')) + HTTP_Proxy_Auth_base64 = a_Misc_encode_base64(prefs.http_proxyuser); + */ + return 0; +} + +/* + * Tell whether the proxy auth is already set (user:password) + * Return: 1 Yes, 0 No + */ +int a_Http_proxy_auth(void) +{ + return (HTTP_Proxy_Auth_base64 ? 1 : 0); +} + +/* + * Activate entered proxy password for HTTP. + */ +void a_Http_set_proxy_passwd(char *str) +{ + char *http_proxyauth = dStrconcat(prefs.http_proxyuser, ":", str, NULL); + HTTP_Proxy_Auth_base64 = a_Misc_encode_base64(http_proxyauth); + dFree(http_proxyauth); +} + +/* + * Create and init a new SocketData_t struct, insert into ValidSocks, + * and return a primary key for it. + */ +static int Http_sock_new(void) +{ + SocketData_t *S = dNew0(SocketData_t, 1); + return a_Klist_insert(&ValidSocks, S); +} + +/* + * Free SocketData_t struct + */ +static void Http_socket_free(int SKey) +{ + SocketData_t *S; + + if ((S = a_Klist_get_data(ValidSocks, SKey))) { + a_Klist_remove(ValidSocks, SKey); + dFree(S); + } +} + +/* + * Close the socket's FD + */ +static void Http_socket_close(SocketData_t *S) +{ + int st; + do + st = close(S->SockFD); + while (st < 0 && errno == EINTR); +} + +/* + * Make the http query string + */ +char *a_Http_make_query_str(const DilloUrl *url, bool_t use_proxy) +{ + char *str, *ptr, *cookies; + Dstr *s_port = dStr_new(""), + *query = dStr_new(""), + *full_path = dStr_new(""), + *proxy_auth = dStr_new(""); + + /* Sending the default port in the query may cause a 302-answer. --Jcid */ + if (URL_PORT(url) && URL_PORT(url) != DILLO_URL_HTTP_PORT) + dStr_sprintfa(s_port, ":%d", URL_PORT(url)); + + if (use_proxy) { + dStr_sprintfa(full_path, "%s%s", + URL_STR(url), + (URL_PATH_(url) || URL_QUERY_(url)) ? "" : "/"); + if ((ptr = strrchr(full_path->str, '#'))) + dStr_truncate(full_path, ptr - full_path->str); + if (HTTP_Proxy_Auth_base64) + dStr_sprintf(proxy_auth, "Proxy-Authorization: Basic %s\r\n", + HTTP_Proxy_Auth_base64); + } else { + dStr_sprintfa(full_path, "%s%s%s%s", + URL_PATH(url), + URL_QUERY_(url) ? "?" : "", + URL_QUERY(url), + (URL_PATH_(url) || URL_QUERY_(url)) ? "" : "/"); + } + + cookies = a_Cookies_get(url); + if (URL_FLAGS(url) & URL_Post) { + dStr_sprintfa( + query, + "POST %s HTTP/1.0\r\n" + "Accept-Charset: utf-8, iso-8859-1\r\n" + "Host: %s%s\r\n" + "%s" + "User-Agent: Dillo/%s\r\n" + "Cookie2: $Version=\"1\"\r\n" + "%s" + "Content-type: application/x-www-form-urlencoded\r\n" + "Content-length: %ld\r\n" + "\r\n" + "%s", + full_path->str, URL_HOST(url), s_port->str, + proxy_auth->str, VERSION, cookies, + (long)strlen(URL_DATA(url)), + URL_DATA(url)); + + } else { + dStr_sprintfa( + query, + "GET %s HTTP/1.0\r\n" + "%s" + "Accept-Charset: utf-8, iso-8859-1\r\n" + "Host: %s%s\r\n" + "%s" + "User-Agent: Dillo/%s\r\n" + "Cookie2: $Version=\"1\"\r\n" + "%s" + "\r\n", + full_path->str, + (URL_FLAGS(url) & URL_E2EReload) ? + "Cache-Control: no-cache\r\nPragma: no-cache\r\n" : "", + URL_HOST(url), s_port->str, + proxy_auth->str, + VERSION, + cookies); + } + dFree(cookies); + + str = query->str; + dStr_free(query, FALSE); + dStr_free(s_port, TRUE); + dStr_free(full_path, TRUE); + dStr_free(proxy_auth, TRUE); + DEBUG_MSG(4, "Query:\n%s", str); + return str; +} + +/* + * Create and submit the HTTP query to the IO engine + */ +static void Http_send_query(ChainLink *Info, SocketData_t *S) +{ + char *query; + DataBuf *dbuf; + + /* Create the query */ + query = a_Http_make_query_str(S->Url, S->use_proxy); + dbuf = a_Chain_dbuf_new(query, (int)strlen(query), 0); + + /* actually this message is sent too early. + * It should go when the socket is ready for writing (i.e. connected) */ + _MSG_BW(S->web, 1, "Sending query to %s...", URL_HOST_(S->Url)); + + /* send query */ + a_Chain_link_new(Info, a_Http_ccc, BCK, a_IO_ccc, 1, 1); + a_Chain_bcb(OpStart, Info, &S->SockFD, NULL); + a_Chain_bcb(OpSend, Info, dbuf, NULL); + dFree(dbuf); + dFree(query); + + /* Tell the cache to start the receiving CCC for the answer */ + a_Chain_fcb(OpSend, Info, &S->SockFD, "SockFD"); +} + +/* + * This function gets called after the DNS succeeds solving a hostname. + * Task: Finish socket setup and start connecting the socket. + * Return value: 0 on success; -1 on error. + */ +static int Http_connect_socket(ChainLink *Info) +{ + int status; +#ifdef ENABLE_IPV6 + struct sockaddr_in6 name; +#else + struct sockaddr_in name; +#endif + SocketData_t *S; + DilloHost *dh; + socklen_t socket_len = 0; + + S = a_Klist_get_data(ValidSocks, VOIDP2INT(Info->LocalKey)); + + /* TODO: iterate this address list until success, or end-of-list */ + dh = dList_nth_data(S->addr_list, 0); + + if ((S->SockFD = socket(dh->af, SOCK_STREAM, IPPROTO_TCP)) < 0) { + S->Err = errno; + DEBUG_MSG(5, "Http_connect_socket ERROR: %s\n", dStrerror(errno)); + return -1; + } + /* set NONBLOCKING and close on exec. */ + fcntl(S->SockFD, F_SETFL, O_NONBLOCK | fcntl(S->SockFD, F_GETFL)); + fcntl(S->SockFD, F_SETFD, FD_CLOEXEC | fcntl(S->SockFD, F_GETFD)); + + /* Some OSes require this... */ + memset(&name, 0, sizeof(name)); + /* Set remaining parms. */ + switch (dh->af) { + case AF_INET: + { + struct sockaddr_in *sin = (struct sockaddr_in *)&name; + socket_len = sizeof(struct sockaddr_in); + sin->sin_family = dh->af; + sin->sin_port = S->port ? htons(S->port) : htons(DILLO_URL_HTTP_PORT); + memcpy(&sin->sin_addr, dh->data, (size_t)dh->alen); + if (a_Web_valid(S->web) && (S->web->flags & WEB_RootUrl)) + DEBUG_MSG(5, "Connecting to %s\n", inet_ntoa(sin->sin_addr)); + break; + } +#ifdef ENABLE_IPV6 + case AF_INET6: + { + char buf[128]; + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&name; + socket_len = sizeof(struct sockaddr_in6); + sin6->sin6_family = dh->af; + sin6->sin6_port = S->port ? htons(S->port) : htons(DILLO_URL_HTTP_PORT); + memcpy(&sin6->sin6_addr, dh->data, dh->alen); + inet_ntop(dh->af, dh->data, buf, sizeof(buf)); + if (a_Web_valid(S->web) && (S->web->flags & WEB_RootUrl)) + DEBUG_MSG(5, "Connecting to %s\n", buf); + break; + } +#endif + } + + MSG_BW(S->web, 1, "Contacting host..."); + status = connect(S->SockFD, (struct sockaddr *)&name, socket_len); + if (status == -1 && errno != EINPROGRESS) { + S->Err = errno; + Http_socket_close(S); + DEBUG_MSG(5, "Http_connect_socket ERROR: %s\n", dStrerror(S->Err)); + return -1; + } else { + Http_send_query(S->Info, S); + } + + return 0; /* Success */ +} + +/* + * Test proxy settings and check the no_proxy domains list + * Return value: whether to use proxy or not. + */ +static int Http_must_use_proxy(const DilloUrl *url) +{ + char *np, *p, *tok; + int ret = 0; + + if (HTTP_Proxy) { + ret = 1; + if (prefs.no_proxy) { + np = dStrdup(prefs.no_proxy); + for (p = np; (tok = dStrsep(&p, " ")); ) { + if (dStristr(URL_AUTHORITY(url), tok)) { + ret = 0; + break; + } + } + dFree(np); + } + } + return ret; +} + +/* + * Callback function for the DNS resolver. + * Continue connecting the socket, or abort upon error condition. + */ +void a_Http_dns_cb(int Status, Dlist *addr_list, void *data) +{ + int SKey = VOIDP2INT(data); + SocketData_t *S; + + S = a_Klist_get_data(ValidSocks, SKey); + if (S) { + if (Status == 0 && addr_list) { + /* Successful DNS answer; save the IP */ + S->addr_list = addr_list; + /* start connecting the socket */ + if (Http_connect_socket(S->Info) < 0) { + MSG_BW(S->web, 1, "ERROR: %s", dStrerror(S->Err)); + a_Chain_fcb(OpAbort, S->Info, NULL, NULL); + dFree(S->Info); + Http_socket_free(SKey); + } + + } else { + /* DNS wasn't able to resolve the hostname */ + MSG_BW(S->web, 0, "ERROR: Dns can't resolve %s", + (S->use_proxy) ? URL_HOST_(HTTP_Proxy) : URL_HOST_(S->Url)); + a_Chain_fcb(OpAbort, S->Info, NULL, NULL); + dFree(S->Info); + Http_socket_free(SKey); + } + } +} + +/* + * Asynchronously create a new http connection for 'Url' + * We'll set some socket parameters; the rest will be set later + * when the IP is known. + * ( Data1 = Web structure ) + * Return value: 0 on success, -1 otherwise + */ +static int Http_get(ChainLink *Info, void *Data1) +{ + SocketData_t *S; + char *hostname; + + S = a_Klist_get_data(ValidSocks, VOIDP2INT(Info->LocalKey)); + /* Reference Web data */ + S->web = Data1; + /* Reference URL data */ + S->Url = S->web->url; + /* Reference Info data */ + S->Info = Info; + + /* Proxy support */ + if (Http_must_use_proxy(S->Url)) { + hostname = dStrdup(URL_HOST(HTTP_Proxy)); + S->port = URL_PORT(HTTP_Proxy); + S->use_proxy = TRUE; + } else { + hostname = dStrdup(URL_HOST(S->Url)); + S->port = URL_PORT(S->Url); + S->use_proxy = FALSE; + } + + /* Let the user know what we'll do */ + MSG_BW(S->web, 1, "DNS resolving %s", URL_HOST_(S->Url)); + + /* Let the DNS engine resolve the hostname, and when done, + * we'll try to connect the socket from the callback function */ + a_Dns_resolve(hostname, a_Http_dns_cb, Info->LocalKey); + + dFree(hostname); + return 0; +} + +/* + * CCC function for the HTTP module + */ +void a_Http_ccc(int Op, int Branch, int Dir, ChainLink *Info, + void *Data1, void *Data2) +{ + int SKey = VOIDP2INT(Info->LocalKey); + + a_Chain_debug_msg("a_Http_ccc", Op, Branch, Dir); + + if (Branch == 1) { + if (Dir == BCK) { + /* HTTP query branch */ + switch (Op) { + case OpStart: + /* ( Data1 = Web ) */ + SKey = Http_sock_new(); + Info->LocalKey = INT2VOIDP(SKey); + Http_get(Info, Data1); + break; + case OpEnd: + /* finished the HTTP query branch */ + a_Chain_bcb(OpEnd, Info, NULL, NULL); + Http_socket_free(SKey); + dFree(Info); + break; + case OpAbort: + /* something bad happened... */ + a_Chain_bcb(OpAbort, Info, NULL, NULL); + Http_socket_free(SKey); + dFree(Info); + break; + } + } else { /* FWD */ + /* HTTP send-query status branch */ + switch (Op) { + default: + MSG_WARN("Unused CCC\n"); + break; + } + } + } +} + + + +/* + * Deallocate memory used by http module + * (Call this one at exit time) + */ +void a_Http_freeall(void) +{ + a_Klist_free(&ValidSocks); + a_Url_free(HTTP_Proxy); + dFree(HTTP_Proxy_Auth_base64); +} diff --git a/src/IO/iowatch.cc b/src/IO/iowatch.cc new file mode 100644 index 00000000..2c1465ba --- /dev/null +++ b/src/IO/iowatch.cc @@ -0,0 +1,35 @@ +/* + * File: iowatch.cc + * + * Copyright (C) 2005 Jorge Arellano Cid <jcid@dillo.org> + * + * 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 3 of the License, or + * (at your option) any later version. + */ + +// Simple ADT for watching file descriptor activity + +#include <fltk/run.h> +#include "iowatch.hh" + +using namespace fltk; + + +// +// Hook a Callback for a certain activities in a FD +// +void a_IOwatch_add_fd(int fd, int when, FileHandler Callback, void *usr_data=0) +{ + add_fd(fd, when, Callback, usr_data); +} + +// +// Remove a Callback for a given FD (or just remove some events) +// +void a_IOwatch_remove_fd(int fd, int when) +{ + remove_fd(fd, when); +} + diff --git a/src/IO/iowatch.hh b/src/IO/iowatch.hh new file mode 100644 index 00000000..681d0080 --- /dev/null +++ b/src/IO/iowatch.hh @@ -0,0 +1,25 @@ +#ifndef __IO_WATCH_H__ +#define __IO_WATCH_H__ + +/* + * BUG: enum {READ = 1, WRITE = 4, EXCEPT = 8} borrowed from fltk/run.h + */ +#define DIO_READ 1 +#define DIO_WRITE 4 +#define DIO_EXCEPT 8 + +typedef void (*CbFunction_t)(int fd, void *data); + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +void a_IOwatch_add_fd(int fd,int when,CbFunction_t Callback,void *usr_data); +void a_IOwatch_remove_fd(int fd,int when); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __IO_WATCH_H__ */ + diff --git a/src/IO/mime.c b/src/IO/mime.c new file mode 100644 index 00000000..3a8040ae --- /dev/null +++ b/src/IO/mime.c @@ -0,0 +1,152 @@ +/* + * File: mime.c + * + * Copyright (C) 2000 Jorge Arellano Cid <jcid@dillo.org> + * + * 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 3 of the License, or + * (at your option) any later version. + */ + +#include "mime.h" +#include "../msg.h" +#include "../list.h" + + +typedef struct { + const char *Name; /* MIME type name */ + Viewer_t Data; /* Pointer to a function */ +} MimeItem_t; + + +/* + * Local data + */ +static int MimeMinItemsSize = 0, MimeMinItemsMax = 8; +static MimeItem_t *MimeMinItems = NULL; + +static int MimeMajItemsSize = 0, MimeMajItemsMax = 8; +static MimeItem_t *MimeMajItems = NULL; + + +/* + * Add a specific MIME type (as "image/png") to our viewer list + * 'Key' is the content-type string that identifies the MIME type + * 'Method' is the function that handles it + */ +static int Mime_add_minor_type(const char *Key, Viewer_t Method) +{ + a_List_add(MimeMinItems, MimeMinItemsSize, MimeMinItemsMax); + MimeMinItems[MimeMinItemsSize].Name = Key; + MimeMinItems[MimeMinItemsSize].Data = Method; + MimeMinItemsSize++; + return 0; +} + +/* + * Add a major MIME type (as "text") to our viewer list + * 'Key' is the content-type string that identifies the MIME type + * 'Method' is the function that handles it + */ +static int Mime_add_major_type(const char *Key, Viewer_t Method) +{ + a_List_add(MimeMajItems, MimeMajItemsSize, MimeMajItemsMax); + MimeMajItems[MimeMajItemsSize].Name = Key; + MimeMajItems[MimeMajItemsSize].Data = Method; + MimeMajItemsSize++; + return 0; +} + +/* + * Search the list of specific MIME viewers, for a Method that matches 'Key' + * 'Key' is the content-type string that identifies the MIME type + */ +static Viewer_t Mime_minor_type_fetch(const char *Key, uint_t Size) +{ + int i; + + if (Size) { + for ( i = 0; i < MimeMinItemsSize; ++i ) + if (dStrncasecmp(Key, MimeMinItems[i].Name, Size) == 0) + return MimeMinItems[i].Data; + } + return NULL; +} + +/* + * Search the list of major MIME viewers, for a Method that matches 'Key' + * 'Key' is the content-type string that identifies the MIME type + */ +static Viewer_t Mime_major_type_fetch(const char *Key, uint_t Size) +{ + int i; + + if (Size) { + for ( i = 0; i < MimeMajItemsSize; ++i ) + if (dStrncasecmp(Key, MimeMajItems[i].Name, Size) == 0) + return MimeMajItems[i].Data; + } + return NULL; +} + + +/* + * Initializes Mime module and, sets the supported Mime types. + */ +void a_Mime_init() +{ +#ifdef ENABLE_GIF + Mime_add_minor_type("image/gif", a_Gif_image); +#endif +#ifdef ENABLE_JPEG + Mime_add_minor_type("image/jpeg", a_Jpeg_image); + Mime_add_minor_type("image/pjpeg", a_Jpeg_image); + Mime_add_minor_type("image/jpg", a_Jpeg_image); +#endif +#ifdef ENABLE_PNG + Mime_add_minor_type("image/png", a_Png_image); + Mime_add_minor_type("image/x-png", a_Png_image); /* deprecated */ +#endif + Mime_add_minor_type("text/html", a_Html_text); + + /* Add a major type to handle all the text stuff */ + Mime_add_major_type("text", a_Plain_text); +} + + +/* + * Call the handler for the MIME type to set Call and Data as appropriate + * + * Return Value: + * On success: a new Dw (and Call and Data properly set). + * On failure: NULL (and Call and Data untouched). + */ +void *a_Mime_set_viewer(const char *content_type, void *Ptr, + CA_Callback_t *Call, void **Data) +{ + + Viewer_t viewer; + uint_t MinSize, MajSize, i; + const char *str = content_type; + + MajSize = 0; + for (i = 0; str[i] && str[i] != ' ' && str[i] != ';'; ++i) { + if (str[i] == '/' && !MajSize) + MajSize = i; + } + MinSize = i; + + /* Try minor type */ + viewer = Mime_minor_type_fetch(content_type, MinSize); + if (viewer) + return viewer(content_type, Ptr, Call, Data); + + /* Try major type */ + viewer = Mime_major_type_fetch(content_type, MajSize); + if (viewer) + return viewer(content_type, Ptr, Call, Data); + + /* Type not handled */ + return NULL; +} diff --git a/src/IO/mime.h b/src/IO/mime.h new file mode 100644 index 00000000..0f51a1e2 --- /dev/null +++ b/src/IO/mime.h @@ -0,0 +1,58 @@ +/* + * File: mime.h + * + * Copyright (C) 2005 Jorge Arellano Cid <jcid@dillo.org> + * + * 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. + */ + +#ifndef __MIME_H__ +#define __MIME_H__ + +#include <config.h> +#include <stddef.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +#include "../cache.h" + +typedef void* (*Viewer_t) (const char*, void*, CA_Callback_t*, void**); + +/* + * Function prototypes defined elsewhere + */ +void *a_Html_text (const char *Type,void *web, CA_Callback_t *Call, + void **Data); +void *a_Plain_text(const char *Type,void *web, CA_Callback_t *Call, + void **Data); +#ifdef ENABLE_JPEG +void *a_Jpeg_image(const char *Type,void *web, CA_Callback_t *Call, + void **Data); +#endif +#ifdef ENABLE_PNG +void *a_Png_image (const char *Type,void *web, CA_Callback_t *Call, + void **Data); +#endif +#ifdef ENABLE_GIF +void *a_Gif_image (const char *Type,void *web, CA_Callback_t *Call, + void **Data); +#endif + +/* + * Functions defined inside Mime module + */ +void a_Mime_init(void); +void *a_Mime_set_viewer(const char *content_type, void *Ptr, + CA_Callback_t *Call, void **Data); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __MIME_H__ */ diff --git a/src/IO/proto.c b/src/IO/proto.c new file mode 100644 index 00000000..3a9e2177 --- /dev/null +++ b/src/IO/proto.c @@ -0,0 +1,13 @@ +/* + * File: proto.c + * + * Copyright (C) 2003 Jorge Arellano Cid <jcid@dillo.org>, + * + * 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 3 of the License, or + * (at your option) any later version. + */ + +/* This module may be programmed to manage dpi-programs. */ + diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 00000000..a2afa02e --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,87 @@ +AM_CPPFLAGS=-DDILLORC_SYS='"$(sysconfdir)/dillorc"' @LIBJPEG_CPPFLAGS@ +AM_CFLAGS = -I../../dw-testbed @LIBPNG_CFLAGS@ +AM_CXXFLAGS = -I../../dw-testbed @LIBPNG_CFLAGS@ @LIBFLTK_CXXFLAGS@ + +SUBDIRS = IO + +bin_PROGRAMS = dillo-fltk + +dillo_fltk_LDADD = \ + ../dlib/libDlib.a \ + ../dpip/libDpip.a \ + IO/libDiof.a \ + ../../dw-testbed/dw/libDw-widgets.a \ + ../../dw-testbed/dw/libDw-fltk.a \ + ../../dw-testbed/dw/libDw-core.a \ + ../../dw-testbed/lout/liblout.a \ + @LIBJPEG_LIBS@ @LIBPNG_LIBS@ @LIBFLTK_LIBS@ @LIBZ_LIBS@ + +dillo_fltk_SOURCES = \ + dillo.cc \ + dir.c \ + dir.h \ + ui.cc \ + ui.hh \ + uicmd.cc \ + uicmd.hh \ + bw.h \ + bw.c \ + cookies.c \ + cookies.h \ + colors.c \ + colors.h \ + binaryconst.h \ + misc.c \ + misc.h \ + history.h \ + history.c \ + prefs.c \ + prefs.h \ + debug.h \ + msg.h \ + list.h \ + url.c \ + url.h \ + bitvec.c \ + bitvec.h \ + klist.c \ + klist.h \ + chain.c \ + chain.h \ + timeout.cc \ + timeout.hh \ + dialog.cc \ + dialog.hh \ + \ + \ + web.cc \ + web.hh \ + nav.c \ + nav.h \ + cache.c \ + cache.h \ + dicache.c \ + dicache.h \ + capi.c \ + capi.h \ + plain.cc \ + form.cc \ + form.hh \ + html.cc \ + html.hh \ + bookmark.c \ + bookmark.h \ + dns.c \ + dns.h \ + gif.c \ + jpeg.c \ + png.c \ + image.cc \ + image.hh \ + menu.hh \ + menu.cc \ + dpiapi.c \ + dpiapi.h \ + pixmaps.h + +EXTRA_DIST = chg srch diff --git a/src/binaryconst.h b/src/binaryconst.h new file mode 100644 index 00000000..b5b9735b --- /dev/null +++ b/src/binaryconst.h @@ -0,0 +1,38 @@ +#ifndef __BINARYCONST_H__ +#define __BINARYCONST_H__ + +/* Macros for allowing binary constants in C + * By Tom Torfs - donated to the public domain */ + +#define HEX__(n) 0x##n##LU + +/* 8-bit conversion function */ +#define B8__(x) ((x&0x0000000FLU)?1:0) \ + +((x&0x000000F0LU)?2:0) \ + +((x&0x00000F00LU)?4:0) \ + +((x&0x0000F000LU)?8:0) \ + +((x&0x000F0000LU)?16:0) \ + +((x&0x00F00000LU)?32:0) \ + +((x&0x0F000000LU)?64:0) \ + +((x&0xF0000000LU)?128:0) + + +/* + * *** USER MACROS *** + */ + +/* for upto 8-bit binary constants */ +#define B8(d) ((unsigned char)B8__(HEX__(d))) + +/* for upto 16-bit binary constants, MSB first */ +#define B16(dmsb,dlsb) (((unsigned short)B8(dmsb)<<8) + B8(dlsb)) + +/* + * Sample usage: + * B8(01010101) = 85 + * B16(10101010,01010101) = 43605 + */ + + +#endif /* __BINARYCONST_H__ */ + diff --git a/src/bitvec.c b/src/bitvec.c new file mode 100644 index 00000000..fc308fc6 --- /dev/null +++ b/src/bitvec.c @@ -0,0 +1,59 @@ +/* + * File: bitvec.c + * + * Copyright 2001 Jorge Arellano Cid <jcid@dillo.org> + * + * 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 3 of the License, or + * (at your option) any later version. + */ + +/* + * A simple ADT for bit arrays + */ + +#include "../dlib/dlib.h" +#include "bitvec.h" + + +/* + * Create a new bitvec with 'num_bits' size + */ +bitvec_t *a_Bitvec_new(int num_bits) +{ + bitvec_t *bvec = dNew(bitvec_t, 1); + + bvec->vec = dNew0(uchar_t, num_bits/BVEC_SIZE + 1); + bvec->len = num_bits; + return bvec; +} + +/* + * Free a bitvec + */ +void a_Bitvec_free(bitvec_t *bvec) +{ + if (bvec) { + dFree(bvec->vec); + dFree(bvec); + } +} + +/* + * Get a bit + */ +int a_Bitvec_get_bit(bitvec_t *bvec, int pos) +{ + dReturn_val_if_fail (pos < bvec->len, 0); + return (bvec->vec[pos/BVEC_SIZE] & 1 << pos % BVEC_SIZE); +} + +/* + * Set a bit + */ +void a_Bitvec_set_bit(bitvec_t *bvec, int pos) +{ + dReturn_if_fail (pos < bvec->len); + bvec->vec[pos/BVEC_SIZE] |= 1 << (pos % BVEC_SIZE); +} diff --git a/src/bitvec.h b/src/bitvec.h new file mode 100644 index 00000000..ab6797ce --- /dev/null +++ b/src/bitvec.h @@ -0,0 +1,36 @@ +#ifndef __BITVEC_H__ +#define __BITVEC_H__ + +#include "d_size.h" + +#define BVEC_TYPE uchar_t +#define BVEC_SIZE sizeof(BVEC_TYPE) + +typedef struct _bitvec bitvec_t; + +struct _bitvec { + BVEC_TYPE *vec; + int len; /* number of bits [1 based] */ +}; + + +/* + * Function prototypes + */ +bitvec_t *a_Bitvec_new(int bits); +void a_Bitvec_free(bitvec_t *bvec); +int a_Bitvec_get_bit(bitvec_t *bvec, int pos); +void a_Bitvec_set_bit(bitvec_t *bvec, int pos); + +/* +#define a_Bitvec_get_bit(bvec,pos) \ + ((bvec)->vec[(pos)/BVEC_SIZE] & 1 << (pos) % BVEC_SIZE) + +#define a_Bitvec_set_bit(bvec,pos) \ + ((bvec)->vec[(pos)/BVEC_SIZE] |= 1 << (pos) % BVEC_SIZE) +*/ +#define a_Bitvec_clear_bit(bvec,pos) \ + ((bvec)->vec[(pos)/BVEC_SIZE] &= ~(1 << (pos) % BVEC_SIZE)) + + +#endif /* __BITVEC_H__ */ diff --git a/src/bookmark.c b/src/bookmark.c new file mode 100644 index 00000000..af37bfed --- /dev/null +++ b/src/bookmark.c @@ -0,0 +1,89 @@ +/* + * File: bookmark.c + * + * Copyright 2002 Jorge Arellano Cid <jcid@dillo.org> + * + * 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 3 of the License, or + * (at your option) any later version. + */ + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "msg.h" +#include "history.h" +#include "capi.h" +#include "bookmark.h" /* for prototypes */ +#include "../dpip/dpip.h" + + + +/* + * Have a short chat with the bookmarks server, + * and finally ask it to add a new bookmark. + * (this is an example of dpi chat) + */ +void a_Bookmarks_chat_add(BrowserWindow *Bw, char *Cmd, char *answer) +{ + static char *cmd1 = NULL, *cmd2 = NULL, *cmd3 = NULL, *cmd4 = NULL; + static BrowserWindow *bw = NULL; + + if (!cmd1) { + cmd1 = a_Dpip_build_cmd("cmd=%s msg=%s", "chat", "Hi server"); + cmd2 = a_Dpip_build_cmd("cmd=%s msg=%s", "chat", + "I want to set a bookmark"); + cmd3 = a_Dpip_build_cmd("cmd=%s msg=%s", "chat", "Sure it is!"); + } + + _MSG("a_Bookmarks_chat_add\n answer=%s\n", answer ? answer : "(null)"); + + if (Bw) + bw = Bw; + if (!cmd4 && Cmd) + cmd4 = dStrdup(Cmd); + + if (!answer) { + a_Capi_dpi_send_cmd(NULL, bw, cmd1, "bookmarks", 1); + + } else { + /* we have an answer */ + if (answer) { + if (*answer == 'H') { + /* "Hi browser" */ + a_Capi_dpi_send_cmd(NULL, bw, cmd2, "bookmarks", 0); + } else if (*answer == 'I') { + /* "Is it worth?" */ + a_Capi_dpi_send_cmd(NULL, bw, cmd3, "bookmarks", 0); + } else if (*answer == 'O') { + /* "OK, send it!" */ + a_Capi_dpi_send_cmd(NULL, bw, cmd4, "bookmarks", 0); + dFree(cmd4); + cmd4 = NULL; + } + } + } +} + +/* + * Add the new bookmark through the bookmarks server + */ +void a_Bookmarks_add(BrowserWindow *bw, DilloUrl *url) +{ + const char *title; + char *cmd; + + dReturn_if_fail(url != NULL); + + /* if the page has no title, we'll use the url string */ + title = a_History_get_title_by_url(url, 1); + + cmd = a_Dpip_build_cmd("cmd=%s url=%s title=%s", + "add_bookmark", URL_STR(url), title); + a_Bookmarks_chat_add(bw, cmd, NULL); + dFree(cmd); +} + diff --git a/src/bookmark.h b/src/bookmark.h new file mode 100644 index 00000000..e1053204 --- /dev/null +++ b/src/bookmark.h @@ -0,0 +1,19 @@ +#ifndef __BOOKMARK_H__ +#define __BOOKMARK_H__ + +#include "bw.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +void a_Bookmarks_add(BrowserWindow *bw, DilloUrl *url); + +/* todo: this is for testing purposes */ +void a_Bookmarks_chat_add(BrowserWindow *Bw, char *Cmd, char *answer); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __BOOKMARK_H__ */ diff --git a/src/bw.c b/src/bw.c new file mode 100644 index 00000000..f207557b --- /dev/null +++ b/src/bw.c @@ -0,0 +1,248 @@ +/* + * File: bw.c + * + * Copyright (C) 2006 Jorge Arellano Cid <jcid@dillo.org> + * + * 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 3 of the License, or + * (at your option) any later version. + */ + +/* Data structures for each browser window */ + + +#include "bw.h" +#include "list.h" +#include "capi.h" +#include "uicmd.hh" + + +/* + * Local Data + */ +/* A list of working browser windows */ +static BrowserWindow **bws; +static int num_bws, num_bws_max; + + +/* + * Initialize global data + */ +void a_Bw_init(void) +{ + num_bws = 0; + num_bws_max = 16; + bws = NULL; +} + +/* + * Create a new browser window and return it. + * (the new window is stored in browser_window[]) + */ +BrowserWindow *a_Bw_new(int width, int height, uint32_t xid) +{ + BrowserWindow *bw; + + /* We use dNew0() to zero the memory */ + bw = dNew0(BrowserWindow, 1); + a_List_add(bws, num_bws, num_bws_max); + bws[num_bws++] = bw; + + /* Initialize nav_stack */ + bw->nav_stack_size = 0; + bw->nav_stack_size_max = 16; + bw->nav_stack = NULL; + bw->nav_stack_ptr = -1; + bw->nav_expecting = FALSE; + bw->nav_expect_url = NULL; + +// if (!xid) +// bw->main_window = gtk_window_new(GTK_WINDOW_TOPLEVEL); +// else +// bw->main_window = gtk_plug_new(xid); + + + bw->redirect_level = 0; + bw->sens_idle_up = 0; + + bw->RootClients = dList_new(8); + bw->ImageClients = dList_new(8); + bw->NumImages = 0; + bw->NumImagesGot = 0; + bw->PageUrls = dList_new(8); + + bw->question_dialog_data = NULL; + + bw->num_page_bugs = 0; + bw->page_bugs = dStr_new(""); + + /* now that the bw is made, let's customize it.. */ + //Interface_browser_window_customize(bw); + + return bw; +} + +/* + * Free resources associated to a bw. + */ +void a_Bw_free(BrowserWindow *bw) +{ + int i, j; + + for (i = 0; i < num_bws; i++) { + if (bws[i] == bw) { + a_List_remove(bws, i, num_bws); + + dList_free(bw->RootClients); + dList_free(bw->ImageClients); + + for (j = 0; j < dList_length(bw->PageUrls); ++j) + a_Url_free(dList_nth_data(bw->PageUrls, j)); + dList_free(bw->PageUrls); + + dFree(bw->nav_stack); + dStr_free(bw->page_bugs, 1); + dFree(bw); + break; + } + } +} + +/*- Clients ----------------------------------------------------------------*/ +/* + * Add a reference to a cache-client. It is kept int this bw's list. + * This helps us keep track of which are active in the window so that it's + * possible to abort/stop them. + * (Root: Flag, whether a Root URL or not) + * + * TODO: Make NumImages count different images. + */ +void a_Bw_add_client(BrowserWindow *bw, int Key, int Root) +{ + dReturn_if_fail ( bw != NULL ); + + if (Root) { + dList_append(bw->RootClients, INT2VOIDP(Key)); + } else { + dList_append(bw->ImageClients, INT2VOIDP(Key)); + bw->NumImages++; + /* --Images progress-bar stuff-- */ + a_UIcmd_set_img_prog(bw, bw->NumImagesGot, bw->NumImages, 1); + } +} + +/* + * Remove the cache-client from the bw's list + * (client can be a image or a html page) + * Return: 0 if found, 1 otherwise. + */ +int a_Bw_remove_client(BrowserWindow *bw, int ClientKey) +{ + void *data; + + if ((data = dList_find(bw->RootClients, INT2VOIDP(ClientKey)))) { + dList_remove_fast(bw->RootClients, data); + } else if ((data = dList_find(bw->ImageClients, INT2VOIDP(ClientKey)))) { + dList_remove_fast(bw->ImageClients, data); + ++bw->NumImagesGot; + } + return data ? 0 : 1; +} + +/* + * Close a cache-client upon successful retrieval. + * Remove the cache-client from the bw list and update the meters. + * (client can be a image or a html page) + */ +void a_Bw_close_client(BrowserWindow *bw, int ClientKey) +{ + if (a_Bw_remove_client(bw, ClientKey) == 0) { + a_UIcmd_set_img_prog(bw, bw->NumImagesGot, bw->NumImages, 1); + if (bw->NumImagesGot == bw->NumImages) + a_UIcmd_set_img_prog(bw, 0, 0, 0); + if (dList_length(bw->RootClients) == 0) + a_UIcmd_set_buttons_sens(bw); + } +} + +/* + * Stop the active clients of this bw's top page. + * Note: rendering stops, but the cache continues to be fed. + */ +void a_Bw_stop_clients(BrowserWindow *bw, int flags) +{ + void *data; + + if (flags & BW_Root) { + /* Remove root clients */ + while ((data = dList_nth_data(bw->RootClients, 0))) { + a_Capi_stop_client(VOIDP2INT(data), (flags & Bw_Force)); + dList_remove_fast(bw->RootClients, data); + } + } + + if (flags & BW_Img) { + /* Remove image clients */ + while ((data = dList_nth_data(bw->ImageClients, 0))) { + a_Capi_stop_client(VOIDP2INT(data), (flags & Bw_Force)); + dList_remove_fast(bw->ImageClients, data); + } + } +} + +/*- PageUrls ---------------------------------------------------------------*/ +/* + * Add an URL to the browser window's list. + * This helps us keep track of page-requested URLs so that it's + * possible to stop, abort and reload them. + */ +void a_Bw_add_url(BrowserWindow *bw, const DilloUrl *Url) +{ + dReturn_if_fail ( bw != NULL && Url != NULL ); + + if (!dList_find_custom(bw->PageUrls, Url, (dCompareFunc)a_Url_cmp)) { + dList_append(bw->PageUrls, a_Url_dup(Url)); + } +} + +/*- Cleanup ----------------------------------------------------------------*/ +/* + * Empty RootClients, ImageClients and PageUrls lists and + * reset progress bar data. + */ +void a_Bw_cleanup(BrowserWindow *bw) +{ + void *data; + + /* Remove root clients */ + while ((data = dList_nth_data(bw->RootClients, 0))) { + dList_remove_fast(bw->RootClients, data); + } + /* Remove image clients */ + while ((data = dList_nth_data(bw->ImageClients, 0))) { + dList_remove_fast(bw->ImageClients, data); + } + /* Remove PageUrls */ + while ((data = dList_nth_data(bw->PageUrls, 0))) { + a_Url_free(data); + dList_remove_fast(bw->PageUrls, data); + } + + /* Zero image-progress data */ + bw->NumImages = 0; + bw->NumImagesGot = 0; +} + +/*--------------------------------------------------------------------------*/ + +/* + * TODO: remove this Hack. + */ +BrowserWindow *a_Bw_get() +{ + if (num_bws > 0) + return bws[0]; + return NULL; +} + diff --git a/src/bw.h b/src/bw.h new file mode 100644 index 00000000..42cb97d6 --- /dev/null +++ b/src/bw.h @@ -0,0 +1,96 @@ +#ifndef __BW_H__ +#define __BW_H__ + +#include <sys/types.h> + +#include "url.h" /* for DilloUrl */ + +/* + * Flag Defines for a_Bw_stop_clients() + */ +#define BW_Root (1) /* Root URLs */ +#define BW_Img (2) /* Image URLs */ +#define Bw_Force (4) /* Stop connection too */ + + +typedef struct _BrowserWindow BrowserWindow; + + +/* browser_window contains the specific data for a single window */ +struct _BrowserWindow +{ + /* Pointer to the UI object this bw belongs to */ + void *ui; + + /* All the rendering is done by this. + * It is defined as a void pointer to avoid C++ in this structure. + * C++ sources have to include browser.h and cast it into an object. */ + void *render_layout; + + /* A list of active cache clients in the window (The primary Key) */ + Dlist *RootClients; + /* Image Keys for all active connections in the window */ + Dlist *ImageClients; + /* Number of images in the page */ + int NumImages; + /* Number of images already loaded */ + int NumImagesGot; + /* List of all Urls requested by this page (and its types) */ + Dlist *PageUrls; + + /* The navigation stack (holds indexes to history list) */ + int *nav_stack; + int nav_stack_size; /* [1 based] */ + int nav_stack_size_max; + /* 'nav_stack_ptr' refers to what's being displayed */ + int nav_stack_ptr; /* [0 based] */ + /* When the user clicks a link, the URL isn't pushed directly to history; + * nav_expect_url holds it until the first answer-bytes are got. Only then + * it is sent to history and referenced in 'nav_stack[++nav_stack_ptr]' */ + DilloUrl *nav_expect_url; + /* 'nav_expecting' is true if the last URL is being loaded for + * the first time and has not gotten the dw yet. */ + bool_t nav_expecting; + + /* Counter for the number of hops on a redirection. Used to stop + * redirection loops (accounts for WEB_RootUrl only) */ + int redirect_level; + + /* flag for button sens idle function */ + int sens_idle_up; + + /* TODO: remove me */ + void *question_dialog_data; + void *question_dialog_answer; + + /* TODO: maybe this fits better in the linkblock. + * Although having it here avoids having a signal for handling it. */ + int num_page_bugs; + Dstr *page_bugs; +}; + + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +void a_Bw_init(void); +BrowserWindow *a_Bw_new(int width, int height, uint32_t xid); +void a_Bw_free(BrowserWindow *bw); +BrowserWindow *a_Bw_get(); + +void a_Bw_add_client(BrowserWindow *bw, int Key, int Root); +int a_Bw_remove_client(BrowserWindow *bw, int ClientKey); +void a_Bw_close_client(BrowserWindow *bw, int ClientKey); +void a_Bw_stop_clients(BrowserWindow *bw, int flags); +void a_Bw_add_url(BrowserWindow *bw, const DilloUrl *Url); +void a_Bw_cleanup(BrowserWindow *bw); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __BROWSER_H__ */ + diff --git a/src/cache.c b/src/cache.c new file mode 100644 index 00000000..efe7d0c1 --- /dev/null +++ b/src/cache.c @@ -0,0 +1,932 @@ +/* + * File: cache.c + * + * Copyright 2000, 2001, 2002, 2003, 2004 Jorge Arellano Cid <jcid@dillo.org> + * + * 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 3 of the License, or + * (at your option) any later version. + */ + +/* + * Dillo's cache module + */ + +#include <ctype.h> /* for tolower */ +#include <sys/types.h> + +#include <sys/stat.h> +#include <stdlib.h> +#include <string.h> +#include <fcntl.h> +#include <unistd.h> + +#include "msg.h" +#include "list.h" +#include "IO/Url.h" +#include "IO/IO.h" +#include "web.hh" +#include "dicache.h" +#include "nav.h" +#include "cookies.h" +#include "misc.h" +#include "capi.h" + +#include "timeout.hh" +#include "uicmd.hh" + +#define NULLKey 0 + +#define DEBUG_LEVEL 5 +#include "debug.h" + +/* Maximun initial size for the automatically-growing data buffer */ +#define MAX_INIT_BUF 1024*1024 +/* Maximum filesize for a URL, before offering a download */ +#define HUGE_FILESIZE 5*1024*1024 + +/* + * Local data types + */ + +typedef struct { + const DilloUrl *Url; /* Cached Url. Url is used as a primary Key */ + char *TypeDet; /* MIME type string (detected from data) */ + char *TypeHdr; /* MIME type string as from the HTTP Header */ + Dstr *Header; /* HTTP header */ + const DilloUrl *Location; /* New URI for redirects */ + Dstr *Data; /* Pointer to raw data */ + int TotalSize; /* Goal size of the whole data (0 if unknown) */ + uint_t Flags; /* Look Flag Defines in cache.h */ +} CacheEntry_t; + + +/* + * Local data + */ +/* A sorted list for cached data. Holds pointers to CacheEntry_t structs */ +static Dlist *CachedURLs; + +/* A list for cache clients. + * Although implemented as a list, we'll call it ClientQueue --Jcid */ +static Dlist *ClientQueue; + +/* A list for delayed clients (it holds weak pointers to cache entries, + * which are used to make deferred calls to Cache_process_queue) */ +static Dlist *DelayedQueue; +static uint_t DelayedQueueIdleId = 0; + + +/* + * Forward declarations + */ +static void Cache_process_queue(CacheEntry_t *entry); +static void Cache_delayed_process_queue(CacheEntry_t *entry); + + +/* + * Determine if two cache entries are equal (used by CachedURLs) + */ +static int Cache_entry_cmp(const void *v1, const void *v2) +{ + const CacheEntry_t *d1 = v1, *d2 = v2; + + return a_Url_cmp(d1->Url, d2->Url); +} + +/* + * Determine if two cache entries are equal, using a URL as key. + */ +static int Cache_entry_by_url_cmp(const void *v1, const void *v2) +{ + const DilloUrl *u1 = ((CacheEntry_t*)v1)->Url; + const DilloUrl *u2 = v2; + + return a_Url_cmp(u1, u2); +} + +/* + * Initialize dicache data + */ +void a_Cache_init(void) +{ + ClientQueue = dList_new(32); + DelayedQueue = dList_new(32); + CachedURLs = dList_new(256); + + /* inject the splash screen in the cache */ + { + DilloUrl *url = a_Url_new("about:splash", NULL, 0, 0, 0); + Dstr *ds = dStr_new(AboutSplash); + a_Cache_entry_inject(url, ds); + dStr_free(ds, 1); + a_Url_free(url); + } +} + +/* Client operations ------------------------------------------------------ */ + +/* + * Make a unique primary-key for cache clients + */ +static int Cache_client_make_key(void) +{ + static int ClientKey = 0; /* Provide a primary key for each client */ + + if (++ClientKey < 0) + ClientKey = 1; + return ClientKey; +} + +/* + * Add a client to ClientQueue. + * - Every client-camp is just a reference (except 'Web'). + * - Return a unique number for identifying the client. + */ +static int Cache_client_enqueue(const DilloUrl *Url, DilloWeb *Web, + CA_Callback_t Callback, void *CbData) +{ + int ClientKey; + CacheClient_t *NewClient; + + NewClient = dNew(CacheClient_t, 1); + ClientKey = Cache_client_make_key(); + NewClient->Key = ClientKey; + NewClient->Url = Url; + NewClient->Buf = NULL; + NewClient->Callback = Callback; + NewClient->CbData = CbData; + NewClient->Web = Web; + + dList_append(ClientQueue, NewClient); + + return ClientKey; +} + +/* + * Compare function for searching a Client by its key + */ +static int Cache_client_by_key_cmp(const void *client, const void *key) +{ + return ((CacheClient_t *)client)->Key - VOIDP2INT(key); +} + +/* + * Remove a client from the queue + */ +static void Cache_client_dequeue(CacheClient_t *Client, int Key) +{ + if (!Client) { + Client = dList_find_custom(ClientQueue, INT2VOIDP(Key), + Cache_client_by_key_cmp); + } + if (Client) { + dList_remove(ClientQueue, Client); + a_Web_free(Client->Web); + dFree(Client); + } +} + + +/* Entry operations ------------------------------------------------------- */ + +/* + * Set safe values for a new cache entry + */ +static void Cache_entry_init(CacheEntry_t *NewEntry, const DilloUrl *Url) +{ + NewEntry->Url = a_Url_dup(Url); + NewEntry->TypeDet = NULL; + NewEntry->TypeHdr = NULL; + NewEntry->Header = dStr_new(""); + NewEntry->Location = NULL; + NewEntry->Data = dStr_sized_new(8*1024); + NewEntry->TotalSize = 0; + NewEntry->Flags = 0; +} + +/* + * Get the data structure for a cached URL (using 'Url' as the search key) + * If 'Url' isn't cached, return NULL + */ +static CacheEntry_t *Cache_entry_search(const DilloUrl *Url) +{ + return dList_find_sorted(CachedURLs, Url, Cache_entry_by_url_cmp); +} + +/* + * Allocate and set a new entry in the cache list + */ +static CacheEntry_t *Cache_entry_add(const DilloUrl *Url) +{ + CacheEntry_t *old_entry, *new_entry; + + if ((old_entry = Cache_entry_search(Url))) { + MSG_WARN("Cache_entry_add, leaking an entry.\n"); + dList_remove(CachedURLs, old_entry); + } + + new_entry = dNew(CacheEntry_t, 1); + Cache_entry_init(new_entry, Url); /* Set safe values */ + dList_insert_sorted(CachedURLs, new_entry, Cache_entry_cmp); + return new_entry; +} + +/* + * Inject full page content directly into the cache. + * Used for "about:splash". May be used for "about:cache" too. + */ +void a_Cache_entry_inject(const DilloUrl *Url, Dstr *data_ds) +{ + CacheEntry_t *entry; + + if (!(entry = Cache_entry_search(Url))) + entry = Cache_entry_add(Url); + entry->Flags |= CA_GotData + CA_GotHeader + CA_GotLength + CA_InternalUrl; + dStr_truncate(entry->Data, 0); + dStr_append_l(entry->Data, data_ds->str, data_ds->len); + dStr_fit(entry->Data); + entry->TotalSize = entry->Data->len; +} + +/* + * Free the components of a CacheEntry_t struct. + */ +static void Cache_entry_free(CacheEntry_t *entry) +{ + a_Url_free((DilloUrl *)entry->Url); + dFree(entry->TypeDet); + dFree(entry->TypeHdr); + dStr_free(entry->Header, TRUE); + a_Url_free((DilloUrl *)entry->Location); + dStr_free(entry->Data, 1); + dFree(entry); +} + +/* + * Remove an entry, from the cache. + * All the entry clients are removed too! (it may stop rendering of this + * same resource on other windows, but nothing more). + */ +void Cache_entry_remove(CacheEntry_t *entry, DilloUrl *url) +{ + int i; + CacheClient_t *Client; + + if (!entry && !(entry = Cache_entry_search(url))) + return; + if (entry->Flags & CA_InternalUrl) + return; + + /* remove all clients for this entry */ + for (i = 0; (Client = dList_nth_data(ClientQueue, i)); ++i) { + if (Client->Url == entry->Url) { + a_Cache_stop_client(Client->Key); + --i; + } + } + + /* remove from DelayedQueue */ + dList_remove(DelayedQueue, entry); + + /* remove from dicache */ + a_Dicache_invalidate_entry(entry->Url); + + /* remove from cache */ + dList_remove(CachedURLs, entry); + Cache_entry_free(entry); +} + +/* + * Wrapper for capi. + */ +void a_Cache_entry_remove_by_url(DilloUrl *url) +{ + Cache_entry_remove(NULL, url); +} + +/* Misc. operations ------------------------------------------------------- */ + +/* + * Try finding the url in the cache. If it hits, send the cache contents + * from there. If it misses, set up a new connection. + * + * - 'Web' is an auxiliar data structure with misc. parameters. + * - 'Call' is the callback that receives the data + * - 'CbData' is custom data passed to 'Call' + * Note: 'Call' and/or 'CbData' can be NULL, in that case they get set + * later by a_Web_dispatch_by_type, based on content/type and 'Web' data. + * + * Return value: A primary key for identifying the client, + * 0 if the client is aborted in the process. + */ +int a_Cache_open_url(void *web, CA_Callback_t Call, void *CbData) +{ + int ClientKey; + CacheEntry_t *entry; + DilloWeb *Web = web; + DilloUrl *Url = Web->url; + + if (URL_FLAGS(Url) & URL_E2EReload) { + /* remove current entry */ + Cache_entry_remove(NULL, Url); + } + + if ((entry = Cache_entry_search(Url))) { + /* URL is cached: feed our client with cached data */ + ClientKey = Cache_client_enqueue(entry->Url, Web, Call, CbData); + Cache_delayed_process_queue(entry); + + } else { + /* URL not cached: create an entry, send our client to the queue, + * and open a new connection */ + entry = Cache_entry_add(Url); + ClientKey = Cache_client_enqueue(entry->Url, Web, Call, CbData); + } + + return ClientKey; +} + +/* + * Get the pointer to the URL document, and its size, from the cache entry. + * Return: 1 cached, 0 not cached. + */ +int a_Cache_get_buf(const DilloUrl *Url, char **PBuf, int *BufSize) +{ + int i; + CacheEntry_t *entry; + + for (i = 0; (entry = Cache_entry_search(Url)); ++i) { + + /* Test for a redirection loop */ + if (entry->Flags & CA_RedirectLoop || i == 3) { + MSG_WARN("Redirect loop for URL: >%s<\n", URL_STR_(Url)); + break; + } + /* Test for a working redirection */ + if (entry && entry->Flags & CA_Redirect && entry->Location) { + Url = entry->Location; + } else + break; + } + + *BufSize = (entry) ? entry->Data->len : 0; + *PBuf = (entry) ? entry->Data->str : NULL; + return (entry ? 1 : 0); +} + +/* + * Extract a single field from the header, allocating and storing the value + * in 'field'. ('fieldname' must not include the trailing ':') + * Return a new string with the field-content if found (NULL on error) + * (This function expects a '\r' stripped header) + */ +static char *Cache_parse_field(const char *header, const char *fieldname) +{ + char *field; + uint_t i, j; + + for (i = 0; header[i]; i++) { + /* Search fieldname */ + for (j = 0; fieldname[j]; j++) + if (tolower(fieldname[j]) != tolower(header[i + j])) + break; + if (fieldname[j]) { + /* skip to next line */ + for ( i += j; header[i] != '\n'; i++); + continue; + } + + i += j; + while (header[i] == ' ') i++; + if (header[i] == ':') { + /* Field found! */ + while (header[++i] == ' '); + for (j = 0; header[i + j] != '\n'; j++); + field = dStrndup(header + i, j); + return field; + } + } + return NULL; +} + +#ifndef DISABLE_COOKIES +/* + * Extract multiple fields from the header. + */ +static Dlist *Cache_parse_multiple_fields(const char *header, + const char *fieldname) +{ + uint_t i, j; + Dlist *fields = dList_new(8); + char *field; + + for (i = 0; header[i]; i++) { + /* Search fieldname */ + for (j = 0; fieldname[j]; j++) + if (tolower(fieldname[j]) != tolower(header[i + j])) + break; + if (fieldname[j]) { + /* skip to next line */ + for (i += j; header[i] != '\n'; i++); + continue; + } + + i += j; + for ( ; header[i] == ' '; i++); + if (header[i] == ':') { + /* Field found! */ + while (header[++i] == ' '); + for (j = 0; header[i + j] != '\n'; j++); + field = dStrndup(header + i, j); + dList_append(fields, field); + } + } + return fields; +} +#endif /* !DISABLE_COOKIES */ + +/* + * Scan, allocate, and set things according to header info. + * (This function needs the whole header to work) + */ +static void Cache_parse_header(CacheEntry_t *entry, + const char *buf, size_t buf_size, int HdrLen) +{ + char *header = entry->Header->str; + char *Length, *Type, *location_str; +#ifndef DISABLE_COOKIES + Dlist *Cookies; + void *data; + int i; +#endif + + if (HdrLen < 12) { + /* Not enough info. */ + + } if (header[9] == '3' && header[10] == '0') { + /* 30x: URL redirection */ + entry->Flags |= CA_Redirect; + if (header[11] == '1') + entry->Flags |= CA_ForceRedirect; /* 301 Moved Permanently */ + else if (header[11] == '2') + entry->Flags |= CA_TempRedirect; /* 302 Temporal Redirect */ + + location_str = Cache_parse_field(header, "Location"); + entry->Location = a_Url_new(location_str, URL_STR_(entry->Url), 0, 0, 0); + dFree(location_str); + + } else if (strncmp(header + 9, "404", 3) == 0) { + entry->Flags |= CA_NotFound; + } + + if ((Length = Cache_parse_field(header, "Content-Length")) != NULL) { + entry->Flags |= CA_GotLength; + entry->TotalSize = strtol(Length, NULL, 10); + if (entry->TotalSize < 0) + entry->TotalSize = 0; + dFree(Length); + } + +#ifndef DISABLE_COOKIES + /* BUG: If a server feels like mixing Set-Cookie2 and Set-Cookie + * responses which aren't identical, then we have a problem. I don't + * know if that is a real issue though. */ + if ((Cookies = Cache_parse_multiple_fields(header, "Set-Cookie2")) || + (Cookies = Cache_parse_multiple_fields(header, "Set-Cookie"))) { + a_Cookies_set(Cookies, entry->Url); + for (i = 0; (data = dList_nth_data(Cookies, i)); ++i) + dFree(data); + dList_free(Cookies); + } +#endif /* !DISABLE_COOKIES */ + + if (entry->TotalSize > 0) { + if (entry->TotalSize > HUGE_FILESIZE) { + entry->Flags |= CA_HugeFile; + } + /* Avoid some reallocs. With MAX_INIT_BUF we avoid a SEGFAULT + * with huge files (e.g. iso files). + * Note: the buffer grows automatically. */ + dStr_free(entry->Data, 1); + entry->Data = dStr_sized_new(MIN(entry->TotalSize+1, MAX_INIT_BUF)); + } + dStr_append_l(entry->Data, buf + HdrLen, (int)buf_size - HdrLen); + + /* Get Content-Type */ + if ((Type = Cache_parse_field(header, "Content-Type")) == NULL) { + MSG_HTTP("Server didn't send Content-Type in header.\n"); + } else { + entry->TypeHdr = Type; + /* This Content-Type is not trusted. It's checked against real data + * in Cache_process_queue(); only then CA_GotContentType becomes true. + */ + } +} + +/* + * Consume bytes until the whole header is got (up to a "\r\n\r\n" sequence) + * (Also strip '\r' chars from header) + */ +static int Cache_get_header(CacheEntry_t *entry, + const char *buf, size_t buf_size) +{ + size_t N, i; + Dstr *hdr = entry->Header; + + /* Header finishes when N = 2 */ + N = (hdr->len && hdr->str[hdr->len - 1] == '\n'); + for (i = 0; i < buf_size && N < 2; ++i) { + if (buf[i] == '\r' || !buf[i]) + continue; + N = (buf[i] == '\n') ? N + 1 : 0; + dStr_append_c(hdr, buf[i]); + } + + if (N == 2) { + /* Got whole header */ + _MSG("Header [buf_size=%d]\n%s", i, hdr->str); + entry->Flags |= CA_GotHeader; + dStr_fit(hdr); + /* Return number of header bytes in 'buf' [1 based] */ + return i; + } + return 0; +} + +/* + * Receive new data, update the reception buffer (for next read), update the + * cache, and service the client queue. + * + * This function gets called whenever the IO has new data. + * 'Op' is the operation to perform + * 'VPtr' is a (void) pointer to the IO control structure + */ +void a_Cache_process_dbuf(int Op, const char *buf, size_t buf_size, + const DilloUrl *Url) +{ + int len; + CacheEntry_t *entry = Cache_entry_search(Url); + + /* Assert a valid entry (not aborted) */ + if (!entry) + return; + + if (Op == IOClose) { + if (entry->Flags & CA_GotLength && entry->TotalSize != entry->Data->len){ + MSG_HTTP("Content-Length does NOT match message body,\n" + " at: %s\n", URL_STR_(entry->Url)); + } + entry->Flags |= CA_GotData; + entry->Flags &= ~CA_Stopped; /* it may catch up! */ + entry->TotalSize = entry->Data->len; + dStr_fit(entry->Data); /* fit buffer size! */ + Cache_process_queue(entry); + return; + } else if (Op == IOAbort) { + /* unused */ + MSG("a_Cache_process_dbuf Op = IOAbort; not implemented!\n"); + return; + } + + if (!(entry->Flags & CA_GotHeader)) { + /* Haven't got the whole header yet */ + len = Cache_get_header(entry, buf, buf_size); + if (entry->Flags & CA_GotHeader) { + /* Let's scan, allocate, and set things according to header info */ + Cache_parse_header(entry, buf, buf_size, len); + /* Now that we have it parsed, let's update our clients */ + Cache_process_queue(entry); + } + return; + } + + dStr_append_l(entry->Data, buf, (int)buf_size); + Cache_process_queue(entry); +} + +/* + * Process redirections (HTTP 30x answers) + * (This is a work in progress --not finished yet) + */ +static int Cache_redirect(CacheEntry_t *entry, int Flags, BrowserWindow *bw) +{ + DilloUrl *NewUrl; + + _MSG(" Cache_redirect: redirect_level = %d\n", bw->redirect_level); + + /* if there's a redirect loop, stop now */ + if (bw->redirect_level >= 5) + entry->Flags |= CA_RedirectLoop; + + if (entry->Flags & CA_RedirectLoop) { + a_UIcmd_set_msg(bw, "ERROR: redirect loop for: %s", URL_STR_(entry->Url)); + bw->redirect_level = 0; + return 0; + } + + if ((entry->Flags & CA_Redirect && entry->Location) && + (entry->Flags & CA_ForceRedirect || entry->Flags & CA_TempRedirect || + !entry->Data->len || entry->Data->len < 1024)) { + + _MSG(">>>Redirect from: %s\n to %s\n", + URL_STR_(entry->Url), URL_STR_(entry->Location)); + _MSG("%s", entry->Header->str); + + if (Flags & WEB_RootUrl) { + /* Redirection of the main page */ + NewUrl = a_Url_new(URL_STR_(entry->Location), URL_STR_(entry->Url), + 0, 0, 0); + if (entry->Flags & CA_TempRedirect) + a_Url_set_flags(NewUrl, URL_FLAGS(NewUrl) | URL_E2EReload); + a_Nav_push(bw, NewUrl); + a_Url_free(NewUrl); + } else { + /* Sub entity redirection (most probably an image) */ + if (!entry->Data->len) { + DEBUG_MSG(3,">>>Image redirection without entity-content<<<\n"); + } else { + DEBUG_MSG(3, ">>>Image redirection with entity-content<<<\n"); + } + } + } + return 0; +} + +/* + * Check whether a URL scheme is downloadable. + * Return: 1 enabled, 0 disabled. + */ +int Cache_download_enabled(const DilloUrl *url) +{ + if (!dStrcasecmp(URL_SCHEME(url), "http") || + !dStrcasecmp(URL_SCHEME(url), "https") || + !dStrcasecmp(URL_SCHEME(url), "ftp")) + return 1; + return 0; +} + +/* + * Don't process data any further, but let the cache fill the entry. + * (Currently used to handle WEB_RootUrl redirects, + * and to ignore image redirects --Jcid) + */ +static void Cache_null_client(int Op, CacheClient_t *Client) +{ + DilloWeb *Web = Client->Web; + + /* make the stop button insensitive when done */ + if (Op == CA_Close) { + if (Web->flags & WEB_RootUrl) { + /* Remove this client from our active list */ + a_Bw_close_client(Web->bw, Client->Key); + } + } + + /* else ignore */ + + return; +} + +/* + * Update cache clients for a single cache-entry + * Tasks: + * - Set the client function (if not already set) + * - Look if new data is available and pass it to client functions + * - Remove clients when done + * - Call redirect handler + * + * todo: Implement CA_Abort Op in client callback + */ +static void Cache_process_queue(CacheEntry_t *entry) +{ + uint_t i; + int st; + const char *Type; + CacheClient_t *Client; + DilloWeb *ClientWeb; + BrowserWindow *Client_bw = NULL; + static bool_t Busy = FALSE; + bool_t AbortEntry = FALSE; + bool_t OfferDownload = FALSE; + bool_t TypeMismatch = FALSE; + + if (Busy) + MSG_ERR("FATAL!: >>>> Cache_process_queue Caught busy!!! <<<<\n"); + if (!(entry->Flags & CA_GotHeader)) + return; + if (!(entry->Flags & CA_GotContentType)) { + st = a_Misc_get_content_type_from_data( + entry->Data->str, entry->Data->len, &Type); + if (st == 0 || entry->Flags & CA_GotData) { + if (a_Misc_content_type_check(entry->TypeHdr, Type) < 0) { + MSG_HTTP("Content-Type '%s' doesn't match the real data.\n", + entry->TypeHdr); + TypeMismatch = TRUE; + } + entry->TypeDet = dStrdup(Type); + entry->Flags |= CA_GotContentType; + } else + return; /* i.e., wait for more data */ + } + + Busy = TRUE; + for (i = 0; (Client = dList_nth_data(ClientQueue, i)); ++i) { + if (Client->Url == entry->Url) { + ClientWeb = Client->Web; /* It was a (void*) */ + Client_bw = ClientWeb->bw; /* 'bw' in a local var */ + + if (ClientWeb->flags & WEB_RootUrl) { + if (!(entry->Flags & CA_MsgErased)) { + /* clear the "expecting for reply..." message */ + a_UIcmd_set_msg(Client_bw, ""); + entry->Flags |= CA_MsgErased; + } + if (TypeMismatch) { + a_UIcmd_set_msg(Client_bw,"HTTP warning: Content-Type '%s' " + "doesn't match the real data.", entry->TypeHdr); + } + if (entry->Flags & CA_Redirect) { + if (!Client->Callback) { + Client->Callback = Cache_null_client; + Client_bw->redirect_level++; + } + } else { + Client_bw->redirect_level = 0; + } + if (entry->Flags & CA_HugeFile) { + a_UIcmd_set_msg(Client_bw,"Huge file! (%dMB)", + entry->TotalSize / (1024*1024)); + AbortEntry = OfferDownload = TRUE; + } + } else { + /* For non root URLs, ignore redirections and 404 answers */ + if (entry->Flags & CA_Redirect || entry->Flags & CA_NotFound) + Client->Callback = Cache_null_client; + } + + /* Set the client function */ + if (!Client->Callback) { + Client->Callback = Cache_null_client; + if (TypeMismatch) { + AbortEntry = TRUE; + } else { + st = a_Web_dispatch_by_type( + entry->TypeHdr ? entry->TypeHdr : entry->TypeDet, + ClientWeb, &Client->Callback, &Client->CbData); + if (st == -1) { + /* MIME type is not viewable */ + if (ClientWeb->flags & WEB_RootUrl) { + /* prepare a download offer... */ + AbortEntry = OfferDownload = TRUE; + } else { + /* TODO: Resource Type not handled. + * Not aborted to avoid multiple connections on the same + * resource. A better idea is to abort the connection and + * to keep a failed-resource flag in the cache entry. */ + } + } + } + if (AbortEntry) { + a_Bw_remove_client(Client_bw, Client->Key); + Cache_client_dequeue(Client, NULLKey); + --i; /* Keep the index value in the next iteration */ + continue; + } + } + + /* Send data to our client */ + if ((Client->BufSize = entry->Data->len) > 0) { + Client->Buf = entry->Data->str; + (Client->Callback)(CA_Send, Client); + } + + /* Remove client when done */ + if (entry->Flags & CA_GotData) { + /* Copy flags to a local var */ + int flags = ClientWeb->flags; + /* We finished sending data, let the client know */ + (Client->Callback)(CA_Close, Client); + Cache_client_dequeue(Client, NULLKey); + --i; /* Keep the index value in the next iteration */ + + /* within CA_GotData, we assert just one redirect call */ + if (entry->Flags & CA_Redirect) + Cache_redirect(entry, flags, Client_bw); + } + } + } /* for */ + + if (AbortEntry) { + /* Abort the entry, remove it from cache, and maybe offer download. */ + DilloUrl *url = a_Url_dup(entry->Url); + a_Capi_conn_abort_by_url(url); + Cache_entry_remove(entry, NULL); + if (OfferDownload && Cache_download_enabled(url)) { + a_UIcmd_save_link(Client_bw, url); + } + a_Url_free(url); + } + + /* Trigger cleanup when there're no cache clients */ + if (dList_length(ClientQueue) == 0) { + MSG(" a_Dicache_cleanup()\n"); + a_Dicache_cleanup(); + } + + Busy = FALSE; + _MSG("QueueSize ====> %d\n", dList_length(ClientQueue)); +} + +/* + * Callback function for Cache_delayed_process_queue. + */ +static void Cache_delayed_process_queue_callback(void *data) +{ + void *entry; + + while ((entry = dList_nth_data(DelayedQueue, 0))) { + Cache_process_queue((CacheEntry_t *)entry); + /* note that if Cache_process_queue removes the entry, + * the following dList_remove has no effect. */ + dList_remove(DelayedQueue, entry); + } + DelayedQueueIdleId = 0; + a_Timeout_remove(); +} + +/* + * Set a call to Cache_process_queue from the main cycle. + */ +static void Cache_delayed_process_queue(CacheEntry_t *entry) +{ + /* there's no need to repeat entries in the queue */ + if (!dList_find(DelayedQueue, entry)) + dList_append(DelayedQueue, entry); + + if (DelayedQueueIdleId == 0) { + _MSG(" Setting timeout callback\n"); + a_Timeout_add(0.0, Cache_delayed_process_queue_callback, NULL); + DelayedQueueIdleId = 1; + } +} + +/* + * Last Client for this entry? + * Return: Client if true, NULL otherwise + * (cache.c has only one call to a capi function. This avoids a second one) + */ +CacheClient_t *a_Cache_client_get_if_unique(int Key) +{ + int i, n = 0; + CacheClient_t *Client, *iClient; + + if ((Client = dList_find_custom(ClientQueue, INT2VOIDP(Key), + Cache_client_by_key_cmp))) { + for (i = 0; (iClient = dList_nth_data(ClientQueue, i)); ++i) { + if (iClient->Url == Client->Url) { + ++n; + } + } + } + return (n == 1) ? Client : NULL; +} + +/* + * Remove a client from the client queue + * todo: notify the dicache and upper layers + */ +void a_Cache_stop_client(int Key) +{ + CacheClient_t *Client; + + if ((Client = dList_find_custom(ClientQueue, INT2VOIDP(Key), + Cache_client_by_key_cmp))) { + Cache_client_dequeue(Client, NULLKey); + } else { + _MSG("WARNING: Cache_stop_client, inexistent client\n"); + } +} + + +/* + * Memory deallocator (only called at exit time) + */ +void a_Cache_freeall(void) +{ + CacheClient_t *Client; + void *data; + + /* free the client queue */ + while ((Client = dList_nth_data(ClientQueue, 0))) + Cache_client_dequeue(Client, NULLKey); + + /* Remove every cache entry */ + while ((data = dList_nth_data(CachedURLs, 0))) { + dList_remove(CachedURLs, data); + Cache_entry_free(data); + } + /* Remove the cache list */ + dList_free(CachedURLs); +} diff --git a/src/cache.h b/src/cache.h new file mode 100644 index 00000000..e3891740 --- /dev/null +++ b/src/cache.h @@ -0,0 +1,75 @@ +#ifndef __CACHE_H__ +#define __CACHE_H__ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +#include "chain.h" +#include "url.h" + +/* + * Cache Op codes + */ +#define CA_Send (0) /* Normal update */ +#define CA_Close (1) /* Successful operation close */ +#define CA_Abort (2) /* Operation abort */ + +/* + * Flag Defines + */ +#define CA_GotHeader (1) /* True after header is completely got */ +#define CA_GotContentType (2) /* True after Content-Type is known */ +#define CA_GotLength (4) /* True if Content-Length is known */ +#define CA_GotData (8) /* True if we have all Data in cache */ +#define CA_FreeData (16) /* Free the cache Data on close */ +#define CA_Redirect (32) /* Data actually points to a redirect */ +#define CA_ForceRedirect (64) /* Unconditional redirect */ +#define CA_TempRedirect (128) /* Temporal redirect */ +#define CA_NotFound (256) /* True if remote server didn't find the URL */ +#define CA_Stopped (512) /* True if the entry has been stopped */ +#define CA_MsgErased (1024) /* Used to erase the bw's status bar */ +#define CA_RedirectLoop (2048) /* Redirect loop */ +#define CA_InternalUrl (4096) /* URL content is generated by dillo */ +#define CA_HugeFile (8192) /* URL content is too big */ + +/* + * Callback type for cache clients + */ +typedef struct _CacheClient CacheClient_t; +typedef void (*CA_Callback_t)(int Op, CacheClient_t *Client); + +/* + * Data structure for cache clients. + */ +struct _CacheClient { + int Key; /* Primary Key for this client */ + const DilloUrl *Url; /* Pointer to a cache entry Url */ + void *Buf; /* Pointer to cache-data */ + uint_t BufSize; /* Valid size of cache-data */ + CA_Callback_t Callback; /* Client function */ + void *CbData; /* Client function data */ + void *Web; /* Pointer to the Web structure of our client */ +}; + +/* + * Function prototypes + */ +void a_Cache_init(void); +int a_Cache_open_url(void *Web, CA_Callback_t Call, void *CbData); +int a_Cache_get_buf(const DilloUrl *Url, char **PBuf, int *BufSize); +void a_Cache_process_dbuf(int Op, const char *buf, size_t buf_size, + const DilloUrl *Url); +void a_Cache_entry_inject(const DilloUrl *Url, Dstr *data_ds); +void a_Cache_entry_remove_by_url(DilloUrl *url); +void a_Cache_freeall(void); +CacheClient_t *a_Cache_client_get_if_unique(int Key); +void a_Cache_stop_client(int Key); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* __CACHE_H__ */ + diff --git a/src/capi.c b/src/capi.c new file mode 100644 index 00000000..2b77614d --- /dev/null +++ b/src/capi.c @@ -0,0 +1,587 @@ +/* + * File: capi.c + * + * Copyright 2002-2006 Jorge Arellano Cid <jcid@dillo.org> + * + * 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 3 of the License, or + * (at your option) any later version. + */ + +/* + * Cache API + * This is the module that manages the cache and starts the CCC chains + * to get the requests served. Kind of a broker. + */ + +#include <string.h> + +#include "msg.h" +#include "capi.h" +#include "IO/IO.h" /* for IORead &friends */ +#include "IO/Url.h" +#include "chain.h" +#include "list.h" +#include "history.h" +#include "nav.h" +#include "misc.h" +#include "dpiapi.h" +#include "uicmd.hh" +#include "../dpip/dpip.h" + +/* for testing dpi chat */ +#include "bookmark.h" + +#define DEBUG_LEVEL 5 +#include "debug.h" + +typedef struct { + DilloUrl *url; /* local copy of web->url */ + void *bw; + char *server; + char *datastr; + int SockFD; + int Flags; + ChainLink *InfoSend; + ChainLink *InfoRecv; + + int Ref; +} capi_conn_t; + +/* Flags for conn */ +enum { + PENDING = 1, + TIMEOUT = 2, /* unused */ + ABORTED = 4 +}; + +/* + * Local data + */ +/* Data list for active dpi connections */ +static capi_conn_t **DpiConn = NULL; +static int DpiConnSize; +static int DpiConnMax = 4; + + +/* + * Forward declarations + */ +void a_Capi_ccc(int Op, int Branch, int Dir, ChainLink *Info, + void *Data1, void *Data2); + + +/* ------------------------------------------------------------------------- */ + +/* + * Initialize capi&cache data + */ +void a_Capi_init(void) +{ + /* nothing to do for capi yet, just for cache */ + a_Cache_init(); +} + +/* ------------------------------------------------------------------------- */ + +/* + * Create a new connection data structure + */ +static capi_conn_t * + Capi_conn_new(DilloUrl *url, void *bw, char *server, char *datastr) +{ + capi_conn_t *conn; + + conn = dNew(capi_conn_t, 1); + conn->url = url ? a_Url_dup(url) : NULL; + conn->bw = bw; + conn->server = dStrdup(server); + conn->datastr = dStrdup(datastr); + conn->SockFD = -1; + conn->Flags = (strcmp(server, "http") != 0) ? PENDING : 0; + conn->InfoSend = NULL; + conn->InfoRecv = NULL; + conn->Ref = 0; /* Reference count */ + return conn; +} + +/* + * Increment the reference count and add to the list if not present + */ +static void Capi_conn_ref(capi_conn_t *conn) +{ + if (++conn->Ref == 1) { + /* add the connection data to list */ + a_List_add(DpiConn, DpiConnSize, DpiConnMax); + DpiConn[DpiConnSize] = conn; + DpiConnSize++; + } + _MSG(" Capi_conn_ref #%d %p\n", conn->Ref, conn); +} + +/* + * Decrement the reference count (and remove from list when zero) + */ +static void Capi_conn_unref(capi_conn_t *conn) +{ + int i, j; + + _MSG(" Capi_conn_unref #%d %p\n", conn->Ref - 1, conn); + + if (--conn->Ref == 0) { + for (i = 0; i < DpiConnSize; ++i) { + if (DpiConn[i] == conn) { + /* remove conn preserving the list order */ + for (j = i; j + 1 < DpiConnSize; ++j) + DpiConn[j] = DpiConn[j + 1]; + --DpiConnSize; + /* free dynamic memory */ + a_Url_free(conn->url); + dFree(conn->server); + dFree(conn->datastr); + dFree(conn); + break; + } + } + } +} + +/* + * Find connection data by server + */ +static capi_conn_t *Capi_conn_find(char *server) +{ + int i; + + for (i = 0; i < DpiConnSize; ++i) + if (strcmp(server, DpiConn[i]->server) == 0) + return DpiConn[i]; + + return NULL; +} + +/* + * Resume connections that were waiting for dpid to start. + */ +static void Capi_conn_resume(void) +{ + int i; + DataBuf *dbuf; + + for (i = 0; i < DpiConnSize; ++i) { + if (DpiConn[i]->Flags & PENDING) { + capi_conn_t *conn = DpiConn[i]; + dbuf = a_Chain_dbuf_new(conn->datastr,(int)strlen(conn->datastr), 0); + a_Capi_ccc(OpSend, 1, BCK, conn->InfoSend, dbuf, NULL); + dFree(dbuf); + conn->Flags &= ~PENDING; + } + } +} + +/* + * Abort the connection for a given url, using its CCC. + */ +void a_Capi_conn_abort_by_url(const DilloUrl *url) +{ + int i; + + for (i = 0; i < DpiConnSize; ++i) { + if (a_Url_cmp(DpiConn[i]->url, url) == 0) { + capi_conn_t *conn = DpiConn[i]; + if (conn->InfoSend) { + a_Capi_ccc(OpAbort, 1, BCK, conn->InfoSend, NULL, NULL); + } + if (conn->InfoRecv) { + a_Capi_ccc(OpAbort, 2, BCK, conn->InfoRecv, NULL, NULL); + } + break; + } + } +} + +/* ------------------------------------------------------------------------- */ + +/* + * Safety test: only allow dpi-urls from dpi-generated pages. + */ +static int Capi_dpi_verify_request(DilloWeb *web) +{ + DilloUrl *referer; + int allow = FALSE; + + /* test POST and GET */ + if (dStrcasecmp(URL_SCHEME(web->url), "dpi") == 0 && + (strchr(URL_STR(web->url), '?') || URL_DATA_(web->url))) { + /* only allow dpi requests from dpi-generated urls */ + if (a_Nav_stack_size(web->bw)) { + referer = a_History_get_url(NAV_TOP(web->bw)); + if (dStrcasecmp(URL_SCHEME(referer), "dpi") == 0) { + allow = TRUE; + } + } + } else { + allow = TRUE; + } + + if (!allow) { + MSG("Capi_dpi_verify_request: Permission Denied!\n"); + MSG(" URL_STR : %s\n", URL_STR(web->url)); + if (URL_DATA_(web->url)) + MSG(" URL_DATA: %s\n", URL_DATA(web->url)); + } + return allow; +} + +/* + * If the url belongs to a dpi server, return its name. + */ +int Capi_url_uses_dpi(DilloUrl *url, char **server_ptr) +{ + char *p, *server = NULL, *url_str = URL_STR(url); + + if (dStrncasecmp(url_str, "dpi:/", 5) == 0) { + /* dpi prefix, get this server's name */ + if ((p = strchr(url_str + 5, '/')) != NULL) { + server = dStrndup(url_str + 5, (uint_t)(p - url_str - 5)); + } else { + server = dStrdup("?"); + } + if (strcmp(server, "bm") == 0) { + dFree(server); + server = dStrdup("bookmarks"); + } + + } else if (dStrncasecmp(url_str, "ftp:/", 5) == 0) { + server = dStrdup("ftp"); + + } else if (dStrncasecmp(url_str, "https:/", 7) == 0) { + server = dStrdup("https"); + } else if (dStrncasecmp(url_str, "file:", 5) == 0) { + server = dStrdup("file"); + } else if (dStrncasecmp(url_str, "data:", 5) == 0) { + server = dStrdup("datauri"); + } + + return ((*server_ptr = server) ? 1 : 0); +} + +/* + * Build the dpip command tag, according to URL and server. + * todo: make it PROXY-aware (AFAIS, it should be easy) + */ +static char *Capi_dpi_build_cmd(DilloWeb *web, char *server) +{ + char *cmd, *http_query; + + if (strcmp(server, "https") == 0) { + /* Let's be kind and make the HTTP query string for the dpi */ + http_query = a_Http_make_query_str(web->url, FALSE); + cmd = a_Dpip_build_cmd("cmd=%s url=%s query=%s", + "open_url", URL_STR(web->url), http_query); + dFree(http_query); + + } else if (strcmp(server, "downloads") == 0) { + /* let the downloads server get it */ + cmd = a_Dpip_build_cmd("cmd=%s url=%s destination=%s", + "download", URL_STR(web->url), web->filename); + + } else { + /* For everyone else, the url string is enough... */ + cmd = a_Dpip_build_cmd("cmd=%s url=%s", "open_url", URL_STR(web->url)); + } + return cmd; +} + +/* + * Most used function for requesting a URL. + * todo: clean up the ad-hoc bindings with an API that allows dynamic + * addition of new plugins. + * + * Return value: A primary key for identifying the client, + * 0 if the client is aborted in the process. + */ +int a_Capi_open_url(DilloWeb *web, CA_Callback_t Call, void *CbData) +{ + capi_conn_t *conn; + int buf_size, reload; + char *cmd, *server, *buf; + const char *scheme = URL_SCHEME(web->url); + int safe = 0, ret = 0, use_cache = 0; + + /* reload test */ + reload = (!a_Capi_get_buf(web->url, &buf, &buf_size) || + (URL_FLAGS(web->url) & URL_E2EReload)); + + if (web->flags & WEB_Download) { + /* donwload request: if cached save from cache, else + * for http, ftp or https, use the downloads dpi */ + if (a_Capi_get_buf(web->url, &buf, &buf_size)) { + if (web->filename && (web->stream = fopen(web->filename, "w"))) { + use_cache = 1; + } + } else { + if (!dStrcasecmp(scheme, "https") || + !dStrcasecmp(scheme, "http") || + !dStrcasecmp(scheme, "ftp")) { + server = "downloads"; + cmd = Capi_dpi_build_cmd(web, server); + a_Capi_dpi_send_cmd(web->url, web->bw, cmd, server, 1); + dFree(cmd); + } + } + + } else if (Capi_url_uses_dpi(web->url, &server)) { + /* dpi request */ + if ((safe = Capi_dpi_verify_request(web))) { + if (dStrcasecmp(scheme, "dpi") == 0) { + /* make "dpi:/" prefixed urls always reload. */ + a_Url_set_flags(web->url, URL_FLAGS(web->url) | URL_E2EReload); + reload = 1; + } + if (reload) { + a_Capi_conn_abort_by_url(web->url); + /* Send dpip command */ + cmd = Capi_dpi_build_cmd(web, server); + a_Capi_dpi_send_cmd(web->url, web->bw, cmd, server, 1); + dFree(cmd); + } + use_cache = 1; + } + dFree(server); + + } else if (!dStrcasecmp(scheme, "http")) { + /* http request */ + if (reload) { + a_Capi_conn_abort_by_url(web->url); + /* create a new connection and start the CCC operations */ + conn = Capi_conn_new(web->url, web->bw, "http", "none"); + a_Capi_ccc(OpStart, 1, BCK, a_Chain_new(), conn, web); + } + use_cache = 1; + + } else if (!dStrcasecmp(scheme, "about")) { + /* internal request */ + use_cache = 1; + } + + if (use_cache) { + ret = a_Cache_open_url(web, Call, CbData); + } else { + a_Web_free(web); + } + return ret; +} + +/* + * Get the cache's buffer for the URL, and its size. + * Return: 1 cached, 0 not cached. + */ +int a_Capi_get_buf(const DilloUrl *Url, char **PBuf, int *BufSize) +{ + return a_Cache_get_buf(Url, PBuf, BufSize); +} + +/* + * Send a dpi cmd. + * (For instance: add_bookmark, open_url, send_preferences, ...) + */ +int a_Capi_dpi_send_cmd(DilloUrl *url, void *bw, char *cmd, char *server, + int flags) +{ + capi_conn_t *conn; + DataBuf *dbuf; + + if (flags & 1) { + /* open a new connection to server */ + + /* Create a new connection data struct and add it to the list */ + conn = Capi_conn_new(url, bw, server, cmd); + /* start the CCC operations */ + a_Capi_ccc(OpStart, 1, BCK, a_Chain_new(), conn, server); + + } else { + /* Re-use an open connection */ + conn = Capi_conn_find(server); + if (conn) { + /* found */ + dbuf = a_Chain_dbuf_new(cmd, (int)strlen(cmd), 0); + a_Capi_ccc(OpSend, 1, BCK, conn->InfoSend, dbuf, NULL); + dFree(dbuf); + } else { + MSG(" ERROR: [a_Capi_dpi_send_cmd] No open connection found\n"); + } + } + + return 0; +} + +/* + * Remove a client from the cache client queue. + * force = also abort the CCC if this is the last client. + */ +void a_Capi_stop_client(int Key, int force) +{ + CacheClient_t *Client; + + if (force && (Client = a_Cache_client_get_if_unique(Key))) { + a_Capi_conn_abort_by_url(Client->Url); + } + a_Cache_stop_client(Key); +} + +/* + * CCC function for the CAPI module + */ +void a_Capi_ccc(int Op, int Branch, int Dir, ChainLink *Info, + void *Data1, void *Data2) +{ + capi_conn_t *conn; + + a_Chain_debug_msg("a_Capi_ccc", Op, Branch, Dir); + + if (Branch == 1) { + if (Dir == BCK) { + /* Command sending branch */ + switch (Op) { + case OpStart: + /* Data1 = conn; Data2 = {Web | server} */ + conn = Data1; + Capi_conn_ref(conn); + Info->LocalKey = conn; + conn->InfoSend = Info; + if (strcmp(conn->server, "http") == 0) { + a_Chain_link_new(Info, a_Capi_ccc, BCK, a_Http_ccc, 1, 1); + a_Chain_bcb(OpStart, Info, Data2, NULL); + } else { + a_Chain_link_new(Info, a_Capi_ccc, BCK, a_Dpi_ccc, 1, 1); + a_Chain_bcb(OpStart, Info, Data2, NULL); + } + break; + case OpSend: + /* Data1 = dbuf */ + a_Chain_bcb(OpSend, Info, Data1, NULL); + break; + case OpEnd: + conn = Info->LocalKey; + conn->InfoSend = NULL; + a_Chain_bcb(OpEnd, Info, NULL, NULL); + Capi_conn_unref(conn); + dFree(Info); + break; + case OpAbort: + conn = Info->LocalKey; + conn->InfoSend = NULL; + a_Chain_bcb(OpAbort, Info, NULL, NULL); + Capi_conn_unref(conn); + dFree(Info); + break; + default: + MSG_WARN("Unused CCC\n"); + break; + } + } else { /* FWD */ + /* Command sending branch (status) */ + switch (Op) { + case OpSend: + if (!Data2) { + MSG_WARN("Capi.c: Opsend [1F] Data2 = NULL\n"); + } else if (strcmp(Data2, "SockFD") == 0) { + /* start the receiving branch */ + capi_conn_t *conn = Info->LocalKey; + conn->SockFD = *(int*)Data1; + a_Capi_ccc(OpStart, 2, BCK, a_Chain_new(), Info->LocalKey, + conn->server); + } else if (strcmp(Data2, "DpidOK") == 0) { + /* resume pending dpi requests */ + Capi_conn_resume(); + } + break; + case OpAbort: + conn = Info->LocalKey; + conn->InfoSend = NULL; + /* remove the cache entry for this URL */ + a_Cache_entry_remove_by_url(conn->url); + if (Data2 && !strcmp(Data2, "DpidERROR")) + a_UIcmd_set_msg(conn->bw, "ERROR: can't start dpid daemon!"); + /* finish conn */ + Capi_conn_unref(conn); + dFree(Info); + break; + default: + MSG_WARN("Unused CCC\n"); + break; + } + } + + } else if (Branch == 2) { + if (Dir == BCK) { + /* Server listening branch (status) + * (Data1 = conn; Data2 = {"HttpFD" | "DpiFD"}) */ + switch (Op) { + case OpStart: + conn = Data1; + Capi_conn_ref(conn); + Info->LocalKey = conn; + conn->InfoRecv = Info; + a_Chain_link_new(Info, a_Capi_ccc, BCK, a_Dpi_ccc, 2, 2); + a_Chain_bcb(OpStart, Info, &conn->SockFD, Data2); + break; + case OpAbort: + conn = Info->LocalKey; + conn->InfoRecv = NULL; + a_Chain_bcb(OpAbort, Info, NULL, NULL); + Capi_conn_unref(conn); + dFree(Info); + break; + default: + MSG_WARN("Unused CCC\n"); + break; + } + } else { /* FWD */ + /* Server listening branch */ + switch (Op) { + case OpSend: + conn = Info->LocalKey; + if (strcmp(Data2, "send_page_2eof") == 0) { + /* Data1 = dbuf */ + DataBuf *dbuf = Data1; + a_Cache_process_dbuf(IORead, dbuf->Buf, dbuf->Size, conn->url); + } else if (strcmp(Data2, "send_status_message") == 0) { + a_UIcmd_set_msg(conn->bw, "%s", Data1); + } else if (strcmp(Data2, "chat") == 0) { + a_UIcmd_set_msg(conn->bw, "%s", Data1); + a_Bookmarks_chat_add(NULL, NULL, Data1); + } else if (strcmp(Data2, "dialog") == 0) { + a_Dpiapi_dialog(conn->bw, conn->server, Data1); + } else if (strcmp(Data2, "reload_request") == 0) { + a_Nav_reload(conn->bw); + } else if (strcmp(Data2, "start_send_page") == 0) { + /* prepare the cache to receive the data stream for this URL + * + * a_Capi_open_url() already added a new cache entry, + * and a client for it. + */ + } + break; + case OpEnd: + conn = Info->LocalKey; + conn->InfoRecv = NULL; + + a_Cache_process_dbuf(IOClose, NULL, 0, conn->url); + + if (conn->InfoSend) { + /* Propagate OpEnd to the sending branch too */ + a_Capi_ccc(OpEnd, 1, BCK, conn->InfoSend, NULL, NULL); + } + Capi_conn_unref(conn); + dFree(Info); + break; + default: + MSG_WARN("Unused CCC\n"); + break; + } + } + } +} diff --git a/src/capi.h b/src/capi.h new file mode 100644 index 00000000..e61d815b --- /dev/null +++ b/src/capi.h @@ -0,0 +1,29 @@ +#ifndef __CAPI_H__ +#define __CAPI_H__ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +#include "cache.h" +#include "web.hh" + +/* + * Function prototypes + */ +void a_Capi_init(void); +int a_Capi_open_url(DilloWeb *web, CA_Callback_t Call, void *CbData); +int a_Capi_get_buf(const DilloUrl *Url, char **PBuf, int *BufSize); +int a_Capi_dpi_send_cmd(DilloUrl *url, void *bw, char *cmd, char *server, + int flags); +void a_Capi_stop_client(int Key, int force); +void a_Capi_conn_abort_by_url(const DilloUrl *url); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __CAPI_H__ */ + diff --git a/src/chain.c b/src/chain.c new file mode 100644 index 00000000..0324687d --- /dev/null +++ b/src/chain.c @@ -0,0 +1,128 @@ +/* + * File: chain.c + * Concomitant control chain (CCC) + * Theory and code by Jorge Arellano Cid + * + * Copyright 2001, 2002 Jorge Arellano Cid <jcid@dillo.org> + * + * 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 3 of the License, or + * (at your option) any later version. + */ + +#include "msg.h" +#include "chain.h" +#include "../dlib/dlib.h" + +#define VERBOSE 0 + +/* + * Create and initialize a new chain-link + */ +ChainLink *a_Chain_new(void) +{ + return dNew0(ChainLink, 1); +} + +/* + * Create a new link from module A to module B. + * 'Direction' tells whether to make a forward or backward link. + * => The link from 'A' to 'B' has 'Direction' direction. + * => The main flow of information names the FWD direction. + * => AtoB_branch: branch on which 'B' receives communications from 'A' + * => BtoA_branch: branch on which 'A' receives communications from 'B' + */ +ChainLink *a_Chain_link_new(ChainLink *AInfo, ChainFunction_t AFunc, + int Direction, ChainFunction_t BFunc, + int AtoB_branch, int BtoA_branch) +{ + ChainLink *NewLink = a_Chain_new(); + ChainLink *OldLink = AInfo; + + if (Direction == BCK) { + NewLink->Fcb = AFunc; + NewLink->FcbInfo = AInfo; + NewLink->FcbBranch = BtoA_branch; + OldLink->Bcb = BFunc; + OldLink->BcbInfo = NewLink; + OldLink->BcbBranch = AtoB_branch; + + } else { /* FWD */ + NewLink->Bcb = AFunc; + NewLink->BcbInfo = AInfo; + NewLink->BcbBranch = BtoA_branch; + OldLink->Fcb = BFunc; + OldLink->FcbInfo = NewLink; + OldLink->FcbBranch = AtoB_branch; + } + + return NewLink; +} + +/* + * Unlink a previously used link. + * 'Direction' tells whether to unlink the forward or backward link. + */ +void a_Chain_unlink(ChainLink *Info, int Direction) +{ + if (Direction == FWD) { + Info->Fcb = NULL; + Info->FcbInfo = NULL; + Info->FcbBranch = 0; + } else { /* BCK */ + Info->Bcb = NULL; + Info->BcbInfo = NULL; + Info->BcbBranch = 0; + } +} + +/* + * Issue the forward callback of the 'Info' link + */ +int a_Chain_fcb(int Op, ChainLink *Info, void *Data1, void *Data2) +{ + if (Info->Fcb) { + Info->Fcb(Op, Info->FcbBranch, FWD, Info->FcbInfo, Data1, Data2); + return 1; + } + return 0; +} + +/* + * Issue the backward callback of the 'Info' link + */ +int a_Chain_bcb(int Op, ChainLink *Info, void *Data1, void *Data2) +{ + if (Info->Bcb) { + Info->Bcb(Op, Info->BcbBranch, BCK, Info->BcbInfo, Data1, Data2); + return 1; + } + return 0; +} + + +/* + * Allocate and initialize a new DataBuf structure + */ +DataBuf *a_Chain_dbuf_new(void *buf, int size, int code) +{ + DataBuf *dbuf = dNew(DataBuf, 1); + dbuf->Buf = buf; + dbuf->Size = size; + dbuf->Code = code; + return dbuf; +} + +/* + * Show some debugging info + */ +void a_Chain_debug_msg(char *FuncStr, int Op, int Branch, int Dir) +{ +#if VERBOSE + const char *StrOps[] = {"", "OpStart", "OpSend", + "OpStop", "OpEnd", "OpAbort"}; + MSG("%-*s: %-*s [%d%s]\n", + 12, FuncStr, 7, StrOps[Op], Branch, (Dir == 1) ? "F" : "B"); +#endif +} diff --git a/src/chain.h b/src/chain.h new file mode 100644 index 00000000..2d5f0aae --- /dev/null +++ b/src/chain.h @@ -0,0 +1,69 @@ +#ifndef __CHAIN_H__ +#define __CHAIN_H__ + +/* + * Concomitant control chain (CCC) + * Theory and code by Jorge Arellano Cid <jcid@dillo.org> + */ + + +/* + * Supported CCC operations + */ +#define OpStart 1 +#define OpSend 2 +#define OpStop 3 +#define OpEnd 4 +#define OpAbort 5 + + +/* + * Linking direction + */ +#define FWD 1 +#define BCK 2 + + +typedef struct _ChainLink ChainLink; +typedef struct _DataBuf DataBuf; +typedef void (*ChainFunction_t)(int Op, int Branch, int Dir, ChainLink *Info, + void *Data1, void *Data2); + +/* This is the main data structure for CCC nodes */ +struct _ChainLink { + void *LocalKey; + + ChainLink *FcbInfo; + ChainFunction_t Fcb; + int FcbBranch; + + ChainLink *BcbInfo; + ChainFunction_t Bcb; + int BcbBranch; +}; + +/* A convenience data structure for passing data chunks between nodes */ +struct _DataBuf { + char *Buf; + int Size; + int Code; +}; + + + +/* + * Function prototypes + */ +ChainLink *a_Chain_new(void); +ChainLink *a_Chain_link_new(ChainLink *AInfo, ChainFunction_t AFunc, + int Direction, ChainFunction_t BFunc, + int AtoB_branch, int BtoA_branch); +void a_Chain_unlink(ChainLink *Info, int Direction); +int a_Chain_fcb(int Op, ChainLink *Info, void *Data1, void *Data2); +int a_Chain_bcb(int Op, ChainLink *Info, void *Data1, void *Data2); + +DataBuf *a_Chain_dbuf_new(void *buf, int size, int code); +void a_Chain_debug_msg(char *FuncStr, int Op, int Branch, int Dir); + + +#endif /* __CHAIN_H__ */ diff --git a/src/chg b/src/chg new file mode 100755 index 00000000..32d525f7 --- /dev/null +++ b/src/chg @@ -0,0 +1,28 @@ +#!/bin/sh +# +# Shell script for name changing source code +# + +if [ ! $# = 3 ]; then + echo "Usage: chg <source> <old_word> <new_word>" + echo " (this script changes <source> directly)" + exit 1 +fi + +if [ ! -r $1 ]; then + echo "source file ->$1<- doesn't exist..." + exit 1 +fi + +if [ ! -r $1.BAK ]; then + echo "creating backup file: $1.BAK" + cp $1 $1.BAK +fi + +sed "s/$2/$3/g" $1 > out +#sed s/$2/$3/ $1 > out +rm $1 +mv out $1 +echo "done!" + + diff --git a/src/colors.c b/src/colors.c new file mode 100644 index 00000000..86db3b4f --- /dev/null +++ b/src/colors.c @@ -0,0 +1,366 @@ +/* + * File: colors.c + * + * Copyright (C) 2000-2005 Jorge Arellano Cid <jcid@dillo.org> + * + * 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 3 of the License, or + * (at your option) any later version. + */ + +#include <string.h> +#include <stdlib.h> +#include <ctype.h> +#include "colors.h" + +#define DEBUG_LEVEL 5 +#include "debug.h" +#include "msg.h" + +/* + * If EXTENDED_COLOR is defined, the extended set of named colors is supported. + * These colors're not standard but they're supported in most browsers. + * NOTE: The colors MUST be in alphabetical order and lower case because the + * code uses a binary search. + */ + +#define EXTENDED_COLOR + +static const struct key { + char *key; + int32_t val; +} color_keyword [] = { +#ifdef EXTENDED_COLOR + { "aliceblue", 0xf0f8ff}, + { "antiquewhite", 0xfaebd7}, +#endif + { "aqua", 0x00ffff}, +#ifdef EXTENDED_COLOR + { "aquamarine", 0x7fffd4}, + { "azure", 0xf0ffff}, + { "beige", 0xf5f5dc}, + { "bisque", 0xffe4c4}, +#endif + { "black", 0x000000}, +#ifdef EXTENDED_COLOR + { "blanchedalmond", 0xffebcd}, +#endif + {"blue", 0x0000ff}, +#ifdef EXTENDED_COLOR + { "blueviolet", 0x8a2be2}, + { "brown", 0xa52a2a}, + { "burlywood", 0xdeb887}, + { "cadetblue", 0x5f9ea0}, + { "chartreuse", 0x7fff00}, + { "chocolate", 0xd2691e}, + { "coral", 0xff7f50}, + { "cornflowerblue", 0x6495ed}, + { "cornsilk", 0xfff8dc}, + { "crimson", 0xdc1436}, + { "cyan", 0x00ffff}, + { "darkblue", 0x00008b}, + { "darkcyan", 0x008b8b}, + { "darkgoldenrod", 0xb8860b}, + { "darkgray", 0xa9a9a9}, + { "darkgreen", 0x006400}, + { "darkkhaki", 0xbdb76b}, + { "darkmagenta", 0x8b008b}, + { "darkolivegreen", 0x556b2f}, + { "darkorange", 0xff8c00}, + { "darkorchid", 0x9932cc}, + { "darkred", 0x8b0000}, + { "darksalmon", 0xe9967a}, + { "darkseagreen", 0x8fbc8f}, + { "darkslateblue", 0x483d8b}, + { "darkslategray", 0x2f4f4f}, + { "darkturquoise", 0x00ced1}, + { "darkviolet", 0x9400d3}, + { "deeppink", 0xff1493}, + { "deepskyblue", 0x00bfff}, + { "dimgray", 0x696969}, + { "dodgerblue", 0x1e90ff}, + { "firebrick", 0xb22222}, + { "floralwhite", 0xfffaf0}, + { "forestgreen", 0x228b22}, +#endif + { "fuchsia", 0xff00ff}, +#ifdef EXTENDED_COLOR + { "gainsboro", 0xdcdcdc}, + { "ghostwhite", 0xf8f8ff}, + { "gold", 0xffd700}, + { "goldenrod", 0xdaa520}, +#endif + { "gray", 0x808080}, + { "green", 0x008000}, +#ifdef EXTENDED_COLOR + { "greenyellow", 0xadff2f}, + { "honeydew", 0xf0fff0}, + { "hotpink", 0xff69b4}, + { "indianred", 0xcd5c5c}, + { "indigo", 0x4b0082}, + { "ivory", 0xfffff0}, + { "khaki", 0xf0e68c}, + { "lavender", 0xe6e6fa}, + { "lavenderblush", 0xfff0f5}, + { "lawngreen", 0x7cfc00}, + { "lemonchiffon", 0xfffacd}, + { "lightblue", 0xadd8e6}, + { "lightcoral", 0xf08080}, + { "lightcyan", 0xe0ffff}, + { "lightgoldenrodyellow", 0xfafad2}, + { "lightgreen", 0x90ee90}, + { "lightgrey", 0xd3d3d3}, + { "lightpink", 0xffb6c1}, + { "lightsalmon", 0xffa07a}, + { "lightseagreen", 0x20b2aa}, + { "lightskyblue", 0x87cefa}, + { "lightslategray", 0x778899}, + { "lightsteelblue", 0xb0c4de}, + { "lightyellow", 0xffffe0}, +#endif + { "lime", 0x00ff00}, +#ifdef EXTENDED_COLOR + { "limegreen", 0x32cd32}, + { "linen", 0xfaf0e6}, + { "magenta", 0xff00ff}, +#endif + { "maroon", 0x800000}, +#ifdef EXTENDED_COLOR + { "mediumaquamarine", 0x66cdaa}, + { "mediumblue", 0x0000cd}, + { "mediumorchid", 0xba55d3}, + { "mediumpurple", 0x9370db}, + { "mediumseagreen", 0x3cb371}, + { "mediumslateblue", 0x7b68ee}, + { "mediumspringgreen", 0x00fa9a}, + { "mediumturquoise", 0x48d1cc}, + { "mediumvioletred", 0xc71585}, + { "midnightblue", 0x191970}, + { "mintcream", 0xf5fffa}, + { "mistyrose", 0xffe4e1}, + { "moccasin", 0xffe4b5}, + { "navajowhite", 0xffdead}, +#endif + { "navy", 0x000080}, +#ifdef EXTENDED_COLOR + { "oldlace", 0xfdf5e6}, +#endif + { "olive", 0x808000}, +#ifdef EXTENDED_COLOR + { "olivedrab", 0x6b8e23}, + { "orange", 0xffa500}, + { "orangered", 0xff4500}, + { "orchid", 0xda70d6}, + { "palegoldenrod", 0xeee8aa}, + { "palegreen", 0x98fb98}, + { "paleturquoise", 0xafeeee}, + { "palevioletred", 0xdb7093}, + { "papayawhip", 0xffefd5}, + { "peachpuff", 0xffdab9}, + { "peru", 0xcd853f}, + { "pink", 0xffc0cb}, + { "plum", 0xdda0dd}, + { "powderblue", 0xb0e0e6}, +#endif + { "purple", 0x800080}, + { "red", 0xff0000}, +#ifdef EXTENDED_COLOR + { "rosybrown", 0xbc8f8f}, + { "royalblue", 0x4169e1}, + { "saddlebrown", 0x8b4513}, + { "salmon", 0xfa8072}, + { "sandybrown", 0xf4a460}, + { "seagreen", 0x2e8b57}, + { "seashell", 0xfff5ee}, + { "sienna", 0xa0522d}, +#endif + { "silver", 0xc0c0c0}, +#ifdef EXTENDED_COLOR + { "skyblue", 0x87ceeb}, + { "slateblue", 0x6a5acd}, + { "slategray", 0x708090}, + { "snow", 0xfffafa}, + { "springgreen", 0x00ff7f}, + { "steelblue", 0x4682b4}, + { "tan", 0xd2b48c}, +#endif + { "teal", 0x008080}, +#ifdef EXTENDED_COLOR + { "thistle", 0xd8bfd8}, + { "tomato", 0xff6347}, + { "turquoise", 0x40e0d0}, + { "violet", 0xee82ee}, + { "wheat", 0xf5deb3}, +#endif + { "white", 0xffffff}, +#ifdef EXTENDED_COLOR + { "whitesmoke", 0xf5f5f5}, +#endif + { "yellow", 0xffff00}, +#ifdef EXTENDED_COLOR + { "yellowgreen", 0x9acd32}, +#endif +}; + +#define NCOLORS (sizeof(color_keyword) / sizeof(struct key)) + +/* + * Parse a color in hex (RRGGBB) + * + * Return Value: + * parsed color if successful (err = 0), + * default_color on error (err = 1). + */ +static int32_t Color_parse_hex (const char *s, int32_t default_color, int *err) +{ + int32_t ret_color; + char *tail; + + *err = 1; + ret_color = strtol(s, &tail, 16); + if (tail - s == 6) + *err = 0; + else + ret_color = default_color; + + return ret_color; +} + +/* + * Parse the color info from a subtag. + * If subtag string begins with # or with 0x simply return the color number + * otherwise search one color from the set of named colors. + * + * Return Value: + * Parsed color if successful, + * default_color (+ error code) on error. + */ +int32_t a_Color_parse (const char *subtag, int32_t default_color, int *err) +{ + const char *cp; + int32_t ret_color; + int ret, low, mid, high, st = 1; + + /* skip leading spaces */ + for (cp = subtag; isspace(*cp); cp++); + + ret_color = default_color; + if (*cp == '#') { + ret_color = Color_parse_hex(cp + 1, default_color, &st); + + } else if (*cp == '0' && (cp[1] == 'x' || cp[1] == 'X') ) { + ret_color = Color_parse_hex(cp + 2, default_color, &st); + st = 2; + + } else { + /* Binary search */ + low = 0; + high = NCOLORS - 1; + while (low <= high) { + mid = (low + high) / 2; + if ((ret = dStrcasecmp(cp, color_keyword[mid].key)) < 0) + high = mid - 1; + else if (ret > 0) + low = mid + 1; + else { + ret_color = color_keyword[mid].val; + st = 0; + break; + } + } + + if (low > high) { + /* try for RRGGBB lacking the leading '#' */ + ret_color = Color_parse_hex(cp, default_color, &st); + st = 1; + } + } + + DEBUG_MSG(3, "subtag: %s\n", subtag); + DEBUG_MSG(3, "color : %X\n", ret_color); + + *err = st; + return ret_color; +} + +#if 0 +/* + * Return a "distance" measure (between [0, 10]) + */ +static int Color_distance(long c1, long c2) +{ + return (labs((c1 & 0x0000ff) - (c2 & 0x0000ff)) + + labs(((c1 & 0x00ff00) - (c2 & 0x00ff00)) >> 8) + + labs(((c1 & 0xff0000) - (c2 & 0xff0000)) >> 16)) / 75; +} +#endif + +/* + * Return: [0-3] + */ +static int Color_distance2(long c1, long c2) +{ + return (labs((c1 & 0x0000ff) - (c2 & 0x0000ff)) >= 0x000060) + + (labs((c1 & 0x00ff00) - (c2 & 0x00ff00)) >= 0x006000) + + (labs((c1 & 0xff0000) - (c2 & 0xff0000)) >= 0x600000); +} + +/* + * Return: [0-3] (requires less contrast than distance2) + */ +static int Color_distance3(long c1, long c2) +{ + return (labs((c1 & 0x0000ff) - (c2 & 0x0000ff)) >= 0x000040) + + (labs((c1 & 0x00ff00) - (c2 & 0x00ff00)) >= 0x004000) + + (labs((c1 & 0xff0000) - (c2 & 0xff0000)) >= 0x400000); +} + +/* + * Return a suitable "visited link" color + * Return value: + * if candidate has good contrast with C_txt, C_lnk and C_bg -> candidate + * else another color (from the internal list) + */ +int32_t a_Color_vc(int32_t candidate, int32_t C_txt, int32_t C_lnk, int32_t C_bg) +{ + /* candidate purple darkcyan darkmagenta olive */ + static int32_t v[] = {0x000000, 0x800080, 0x008b8b, 0x8b008b, 0x808000, + /* darkred coral black */ + 0x8b0000, 0xff7f50, 0x000000}; + int v_size = sizeof(v) / sizeof(v[0]); + int i, max_i, score, max_score, d_bg, d_txt, d_lnk; + + + /* set candidate in the list */ + v[0] = candidate; + + /* Try to get good overall and individual contrast */ + max_i = max_score = 0; + for (i = 0; i < v_size; ++i) { + _MSG("a_Color_vc: [%d]%.6x: %d %d %d\n", i, v[i], + Color_distance2(C_txt, v[i]), + Color_distance2(C_lnk, v[i]), + Color_distance2(C_bg, v[i])); + + /* Tuned with: slashdot.org, paulgraham.com, newsforge.com, + * linuxjournal.com + */ + d_txt = Color_distance2(C_txt, v[i]); + d_lnk = Color_distance2(C_lnk, v[i]); + d_bg = Color_distance2(C_bg, v[i]); + score = (d_bg >= 2 ? 4 : 2 * d_bg) + + (d_txt + d_lnk >= 2 ? 2 : d_txt + d_lnk) + + (Color_distance3(C_lnk, v[i]) >= 1 ? 1 : 0); + if (score >= 7) { + /* enough distance, use this color */ + max_i = i; + break; + } else if (score > max_score) { + /* keep track of the best candidate so far */ + max_score = score; + max_i = i; + } + } + return v[max_i]; +} diff --git a/src/colors.h b/src/colors.h new file mode 100644 index 00000000..99d1bc0f --- /dev/null +++ b/src/colors.h @@ -0,0 +1,15 @@ +#ifndef __COLORS_H__ +#define __COLORS_H__ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +int32_t a_Color_parse (const char *subtag, int32_t default_color, int *err); +int32_t a_Color_vc(int32_t candidate, int32_t c1, int32_t c2, int32_t c3); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __COLORS_H__ */ diff --git a/src/cookies.c b/src/cookies.c new file mode 100644 index 00000000..f6002842 --- /dev/null +++ b/src/cookies.c @@ -0,0 +1,332 @@ +/* + * File: cookies.c + * + * Copyright 2001 Lars Clausen <lrclause@cs.uiuc.edu> + * Jörgen Viksell <jorgen.viksell@telia.com> + * + * 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 3 of the License, or + * (at your option) any later version. + */ + +/* Handling of cookies takes place here. + * This implementation aims to follow RFC 2965: + * http://www.cis.ohio-state.edu/cs/Services/rfc/rfc-text/rfc2965.txt + */ + +#define DEBUG_LEVEL 10 +#include "debug.h" + + +#ifdef DISABLE_COOKIES + +/* + * Initialize the cookies module + */ +void a_Cookies_init(void) +{ + DEBUG_MSG(10, "Cookies: absolutely disabled at compilation time.\n"); +} + +#else + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/file.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <time.h> /* for time() and time_t */ +#include <ctype.h> + +#include "msg.h" +#include "IO/Url.h" +#include "list.h" +#include "cookies.h" +#include "capi.h" +#include "dpiapi.h" +#include "../dpip/dpip.h" + + +/* The maximum length of a line in the cookie file */ +#define LINE_MAXLEN 4096 + +typedef enum { + COOKIE_ACCEPT, + COOKIE_ACCEPT_SESSION, + COOKIE_DENY +} CookieControlAction; + +typedef struct { + CookieControlAction action; + char *domain; +} CookieControl; + +/* Variables for access control */ +static CookieControl *ccontrol = NULL; +static int num_ccontrol = 0; +static int num_ccontrol_max = 1; +static CookieControlAction default_action = COOKIE_DENY; + +static bool_t disabled; + +static FILE *Cookies_fopen(const char *file, char *init_str); +static CookieControlAction Cookies_control_check(const DilloUrl *url); +static CookieControlAction Cookies_control_check_domain(const char *domain); +static int Cookie_control_init(void); + +/* + * Return a file pointer. If the file doesn't exist, try to create it, + * with the optional 'init_str' as its content. + */ +static FILE *Cookies_fopen(const char *filename, char *init_str) +{ + FILE *F_in; + int fd; + + if ((F_in = fopen(filename, "r")) == NULL) { + /* Create the file */ + fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); + if (fd != -1) { + if (init_str) + write(fd, init_str, strlen(init_str)); + close(fd); + + DEBUG_MSG(10, "Cookies: Created file: %s\n", filename); + F_in = Cookies_fopen(filename, NULL); + } else { + DEBUG_MSG(10, "Cookies: Could not create file: %s!\n", filename); + } + } + + /* set close on exec */ + fcntl(fileno(F_in), F_SETFD, FD_CLOEXEC | fcntl(fileno(F_in), F_GETFD)); + + return F_in; +} + +/* + * Initialize the cookies module + * (The 'disabled' variable is writable only within a_Cookies_init) + */ +void a_Cookies_init(void) +{ + /* Default setting */ + disabled = TRUE; + + /* Read and parse the cookie control file (cookiesrc) */ + if (Cookie_control_init() != 0) { + DEBUG_MSG(10, "Disabling cookies.\n"); + return; + } + + DEBUG_MSG(10, "Enabling cookies as from cookiesrc...\n"); + disabled = FALSE; +} + +/* + * Flush cookies to disk and free all the memory allocated. + */ +void a_Cookies_freeall() +{ +} + +/* + * Set the value corresponding to the cookie string + */ +void a_Cookies_set(Dlist *cookie_strings, const DilloUrl *set_url) +{ + CookieControlAction action; + char *cmd, *cookie_string, numstr[16]; + const char *path; + int i; + + if (disabled) + return; + + action = Cookies_control_check(set_url); + if (action == COOKIE_DENY) { + DEBUG_MSG(5, "Cookies: denied SET for %s\n", URL_HOST_(set_url)); + return; + } + + for (i = 0; (cookie_string = dList_nth_data(cookie_strings, i)); ++i) { + path = URL_PATH_(set_url); + snprintf(numstr, 16, "%d", URL_PORT(set_url)); + cmd = a_Dpip_build_cmd("cmd=%s cookie=%s host=%s path=%s port=%s", + "set_cookie", cookie_string, URL_HOST_(set_url), + path ? path : "/", numstr); + + DEBUG_MSG(5, "Cookies.c: a_Cookies_set \n\t \"%s\" \n",cmd ); + a_Capi_dpi_send_cmd(NULL, NULL, cmd, "cookies", 1); + dFree(cmd); + } +} + +/* + * Return a string that contains all relevant cookies as headers. + */ +char *a_Cookies_get(const DilloUrl *request_url) +{ + char *cmd, *dpip_tag, *cookie, numstr[16]; + const char *path; + CookieControlAction action; + + cookie = dStrdup(""); + + if (disabled) + return cookie; + + action = Cookies_control_check(request_url); + if (action == COOKIE_DENY) { + DEBUG_MSG(5, "Cookies: denied GET for %s\n", URL_HOST_(request_url)); + return cookie; + } + path = URL_PATH_(request_url); + + snprintf(numstr, 16, "%d", URL_PORT(request_url)); + cmd = a_Dpip_build_cmd("cmd=%s scheme=%s host=%s path=%s port=%s", + "get_cookie", URL_SCHEME(request_url), + URL_HOST(request_url), path ? path : "/", numstr); + + /* Get the answer from cookies.dpi */ + dpip_tag = a_Dpi_send_blocking_cmd("cookies", cmd); + dFree(cmd); + + if (dpip_tag != NULL) { + cookie = a_Dpip_get_attr(dpip_tag, strlen(dpip_tag), "cookie"); + dFree(dpip_tag); + } + return cookie; +} + +/* ------------------------------------------------------------- + * Access control routines + * ------------------------------------------------------------- */ + + +/* + * Get the cookie control rules (from cookiesrc). + * Return value: + * 0 = Parsed OK, with cookies enabled + * 1 = Parsed OK, with cookies disabled + * 2 = Can't open the control file + */ +static int Cookie_control_init(void) +{ + CookieControl cc; + FILE *stream; + char *filename; + char line[LINE_MAXLEN]; + char domain[LINE_MAXLEN]; + char rule[LINE_MAXLEN]; + int i, j; + bool_t enabled = FALSE; + + /* Get a file pointer */ + filename = dStrconcat(dGethomedir(), "/.dillo/cookiesrc", NULL); + stream = Cookies_fopen(filename, "DEFAULT DENY\n"); + dFree(filename); + + if (!stream) + return 2; + + /* Get all lines in the file */ + while (!feof(stream)) { + line[0] = '\0'; + fgets(line, LINE_MAXLEN, stream); + + /* Remove leading and trailing whitespaces */ + dStrstrip(line); + + if (line[0] != '\0' && line[0] != '#') { + i = 0; + j = 0; + + /* Get the domain */ + while (!isspace(line[i])) + domain[j++] = line[i++]; + domain[j] = '\0'; + + /* Skip past whitespaces */ + i++; + while (isspace(line[i])) + i++; + + /* Get the rule */ + j = 0; + while (line[i] != '\0' && !isspace(line[i])) + rule[j++] = line[i++]; + rule[j] = '\0'; + + if (dStrcasecmp(rule, "ACCEPT") == 0) + cc.action = COOKIE_ACCEPT; + else if (dStrcasecmp(rule, "ACCEPT_SESSION") == 0) + cc.action = COOKIE_ACCEPT_SESSION; + else if (dStrcasecmp(rule, "DENY") == 0) + cc.action = COOKIE_DENY; + else { + MSG("Cookies: rule '%s' for domain '%s' is not recognised.\n", + rule, domain); + continue; + } + + cc.domain = dStrdup(domain); + if (dStrcasecmp(cc.domain, "DEFAULT") == 0) { + /* Set the default action */ + default_action = cc.action; + dFree(cc.domain); + } else { + a_List_add(ccontrol, num_ccontrol, num_ccontrol_max); + ccontrol[num_ccontrol++] = cc; + } + + if (cc.action != COOKIE_DENY) + enabled = TRUE; + } + } + + fclose(stream); + + return (enabled ? 0 : 1); +} + +/* + * Check the rules for an appropriate action for this domain + */ +static CookieControlAction Cookies_control_check_domain(const char *domain) +{ + int i, diff; + + for (i = 0; i < num_ccontrol; i++) { + if (ccontrol[i].domain[0] == '.') { + diff = strlen(domain) - strlen(ccontrol[i].domain); + if (diff >= 0) { + if (dStrcasecmp(domain + diff, ccontrol[i].domain) != 0) + continue; + } else { + continue; + } + } else { + if (dStrcasecmp(domain, ccontrol[i].domain) != 0) + continue; + } + + /* If we got here we have a match */ + return( ccontrol[i].action ); + } + + return default_action; +} + +/* + * Same as the above except it takes an URL + */ +static CookieControlAction Cookies_control_check(const DilloUrl *url) +{ + return Cookies_control_check_domain(URL_HOST(url)); +} + +#endif /* !DISABLE_COOKIES */ diff --git a/src/cookies.h b/src/cookies.h new file mode 100644 index 00000000..c792d633 --- /dev/null +++ b/src/cookies.h @@ -0,0 +1,24 @@ +#ifndef __COOKIES_H__ +#define __COOKIES_H__ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +#ifdef DISABLE_COOKIES +# define a_Cookies_get(url) dStrdup("") +# define a_Cookies_init() ; +# define a_Cookies_freeall() ; +#else + char *a_Cookies_get(const DilloUrl *request_url); + void a_Cookies_set(Dlist *cookie_string, const DilloUrl *set_url); + void a_Cookies_init( void ); + void a_Cookies_freeall( void ); +#endif + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* !__COOKIES_H__ */ diff --git a/src/debug.h b/src/debug.h new file mode 100644 index 00000000..ff8baf36 --- /dev/null +++ b/src/debug.h @@ -0,0 +1,149 @@ +#ifndef __DEBUG_H__ +#define __DEBUG_H__ + +/* + * Simple debug messages. Add: + * + * #define DEBUG_LEVEL <n> + * #include "debug.h" + * + * to the file you are working on, or let DEBUG_LEVEL undefined to + * disable all messages. A higher level denotes a greater importance + * of the message. + */ + +#include <unistd.h> +#include <stdio.h> + + +# ifdef DEBUG_LEVEL +# define DEBUG_MSG(level, ...) \ + D_STMT_START { \ + if (DEBUG_LEVEL && (level) >= DEBUG_LEVEL) \ + printf(__VA_ARGS__); \ + } D_STMT_END +# else +# define DEBUG_MSG(level, ...) +# endif /* DEBUG_LEVEL */ + + + +/* + * Following is experimental, and will be explained soon. + */ + +#ifdef DBG_RTFL + + +#define DBG_MSG(obj, aspect, prio, msg) \ + D_STMT_START { \ + printf ("[rtfl]%s:%d:%d:msg:%p:%s:%d:%s\n", \ + __FILE__, __LINE__, getpid(), obj, aspect, prio, msg); \ + fflush (stdout); \ + } D_STMT_END + +#define DBG_MSGF(obj, aspect, prio, fmt, ...) \ + D_STMT_START { \ + printf ("[rtfl]%s:%d:%d:msg:%p:%s:%d:" fmt "\n", \ + __FILE__, __LINE__, getpid(), obj, aspect, prio, __VA_ARGS__); \ + fflush (stdout); \ + } D_STMT_END + +#define DBG_MSG_START(obj) \ + D_STMT_START { \ + printf ("[rtfl]%s:%d:%d:msg-start:%p\n", \ + __FILE__, __LINE__, getpid(), obj); \ + fflush (stdout); \ + } D_STMT_END + +#define DBG_MSG_END(obj) \ + D_STMT_START { \ + printf ("[rtfl]%s:%d:%d:msg-end:%p\n", \ + __FILE__, __LINE__, getpid(), obj); \ + fflush (stdout); \ + } D_STMT_END + +#define DBG_OBJ_CREATE(obj, klass) \ + D_STMT_START { \ + printf ("[rtfl]%s:%d:%d:obj-create:%p:%s\n", \ + __FILE__, __LINE__, getpid(), obj, klass); \ + fflush (stdout); \ + } D_STMT_END + +#define DBG_OBJ_ASSOC(child, parent) \ + D_STMT_START { \ + printf ("[rtfl]%s:%d:%d:obj-assoc:%p:%p\n", \ + __FILE__, __LINE__, getpid(), child, parent); \ + fflush (stdout); \ + } D_STMT_END + +#define DBG_OBJ_SET_NUM(obj, var, val) \ + D_STMT_START { \ + printf ("[rtfl]%s:%d:%d:obj-set:%p:%s:%d\n", \ + __FILE__, __LINE__, getpid(), obj, var, val); \ + fflush (stdout); \ + } D_STMT_END + +#define DBG_OBJ_SET_STR(obj, var, val) \ + D_STMT_START { \ + printf ("[rtfl]%s:%d:%d:obj-set:%p:%s:%s\n", \ + __FILE__, __LINE__, getpid(), obj, var, val); \ + fflush (stdout); \ + } D_STMT_END + +#define DBG_OBJ_SET_PTR(obj, var, val) \ + D_STMT_START { \ + printf ("[rtfl]%s:%d:%d:obj-set:%p:%s:%p\n", \ + __FILE__, __LINE__, getpid(), obj, var, val); \ + fflush (stdout); \ + } D_STMT_END + +#define DBG_OBJ_ARRSET_NUM(obj, var, ind, val) \ + D_STMT_START { \ + printf ("[rtfl]%s:%d:%d:obj-set:%p:" var ":%d\n", \ + __FILE__, __LINE__, getpid(), obj, ind, val); \ + fflush (stdout); \ + } D_STMT_END + +#define DBG_OBJ_ARRSET_STR(obj, var, ind, val) \ + D_STMT_START { \ + printf ("[rtfl]%s:%d:%d:obj-set:%p:" var ":%s\n", \ + __FILE__, __LINE__, getpid(), obj, ind, val); \ + fflush (stdout); \ + } D_STMT_END + +#define DBG_OBJ_ARRSET_PTR(obj, var, ind, val) \ + D_STMT_START { \ + printf ("[rtfl]%s:%d:%d:obj-set:%p:" var ":%p\n", \ + __FILE__, __LINE__, getpid(), obj, ind, val); \ + fflush (stdout); \ + } D_STMT_END + +#define DBG_OBJ_COLOR(klass, color) \ + D_STMT_START { \ + printf ("[rtfl]%s:%d:%d:obj-color:%s:%s\n", \ + __FILE__, __LINE__, getpid(), klass, color); \ + fflush (stdout); \ + } D_STMT_END + +#else /* DBG_RTFL */ + +#define DBG_MSG(obj, aspect, prio, msg) +#define DBG_MSGF(obj, aspect, prio, fmt, ...) +#define DBG_MSG_START(obj) +#define DBG_MSG_END(obj) +#define DBG_OBJ_CREATE(obj, klass) +#define DBG_OBJ_ASSOC(child, parent) +#define DBG_OBJ_SET_NUM(obj, var, val) +#define DBG_OBJ_SET_STR(obj, var, val) +#define DBG_OBJ_SET_PTR(obj, var, val) +#define DBG_OBJ_ARRSET_NUM(obj, var, ind, val) +#define DBG_OBJ_ARRSET_STR(obj, var, ind, val) +#define DBG_OBJ_ARRSET_PTR(obj, var, ind, val) +#define DBG_OBJ_COLOR(klass, color) + +#endif /* DBG_RTFL */ + +#endif /* __DEBUG_H__ */ + + diff --git a/src/dialog.cc b/src/dialog.cc new file mode 100644 index 00000000..3b1badb2 --- /dev/null +++ b/src/dialog.cc @@ -0,0 +1,116 @@ +/* + * File: dialog.cc + * + * Copyright (C) 2005-2006 Jorge Arellano Cid <jcid@dillo.org> + * + * 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 3 of the License, or + * (at your option) any later version. + */ + +// UI dialogs + +#include <fltk/Window.h> +#include <fltk/ask.h> +#include <fltk/file_chooser.h> +#include <fltk/TextBuffer.h> +#include <fltk/ReturnButton.h> +#include <fltk/TextDisplay.h> + +#include "dialog.hh" +#include "misc.h" + +using namespace fltk; + +/* + * Callback for the text window dialog. + */ +void text_window_cb(Widget *, void *vwin) +{ + Window *window = (Window*)vwin; + + window->destroy(); +} + +/* + * Display a message in a popup window. + */ +void a_Dialog_msg(const char *msg) +{ + message("%s", msg); +} + +/* + * Offer a three choice dialog. + * The option string that begins with "*" is the default. + * + * Return: 0, 1 or 2 (esc = 2, window close = 2) + */ +int a_Dialog_choice3(const char *msg, + const char *b0, const char *b1, const char *b2) +{ + return choice(msg, b0, b1, b2); +} + +/* + * Dialog for one line of Input with a message. + */ +const char *a_Dialog_input(const char *msg) +{ + return input("%s", "", msg); +} + +/* + * Show the save file dialog. + * + * Return: pointer to chosen filename, or NULL on Cancel. + */ +const char *a_Dialog_save_file(const char *msg, + const char *pattern, const char *fname) +{ + return file_chooser(msg, pattern, fname); +} + +//#include <fltk/FileIcon.h> +/* + * Show the open file dialog. + * + * Return: pointer to chosen filename, or NULL on Cancel. + */ +char *a_Dialog_open_file(const char *msg, + const char *pattern, const char *fname) +{ + const char *fc_name; +/* + static int icons_loaded = 0; + if (!icons_loaded) + FileIcon::load_system_icons(); +*/ + fc_name = file_chooser(msg, pattern, fname); + return (fc_name) ? a_Misc_escape_chars(fc_name, "% ") : NULL; +} + +/* + * Show a new window with the provided text + */ +void a_Dialog_text_window(const char *txt, const char *title) +{ + int wh = 500, ww = 480, bh = 30; + TextBuffer *text_buf = new TextBuffer(); + text_buf->text(txt); + + Window *window = new Window(ww, wh, title ? title : "Untitled"); + window->begin(); + + TextDisplay *td = new TextDisplay(0,0,ww, wh-bh); + td->buffer(text_buf); + + ReturnButton *b = new ReturnButton (0, wh-bh, ww, bh, "Close"); + b->callback(text_window_cb, window); + + window->resizable(window); + window->end(); + window->show(); +} + diff --git a/src/dialog.hh b/src/dialog.hh new file mode 100644 index 00000000..9b927832 --- /dev/null +++ b/src/dialog.hh @@ -0,0 +1,22 @@ +#ifndef __DIALOG_HH__ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +void a_Dialog_msg(const char *msg); +int a_Dialog_choice3(const char *msg, + const char *b0, const char *b1, const char *b2); +const char *a_Dialog_input(const char *msg); +const char *a_Dialog_save_file(const char *msg, + const char *pattern, const char *fname); +char *a_Dialog_open_file(const char *msg, + const char *pattern, const char *fname); +void a_Dialog_text_window(const char *txt, const char *title); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif // __DIALOG_HH__ diff --git a/src/dicache.c b/src/dicache.c new file mode 100644 index 00000000..eac01ec2 --- /dev/null +++ b/src/dicache.c @@ -0,0 +1,451 @@ +/* + * File: dicache.c + * + * Copyright 2000-2005 Jorge Arellano Cid <jcid@dillo.org> + * + * 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 3 of the License, or + * (at your option) any later version. + */ + +#include <sys/time.h> /* for libc5 compatibility */ +#include <string.h> /* for memset */ +#include <stdio.h> +#include <stdlib.h> + +#include "image.hh" +#include "web.hh" +#include "dicache.h" +#include "cache.h" +#include "prefs.h" + +typedef struct _DICacheNode DICacheNode; + +struct _DICacheNode { + int valid; /* flag */ + DilloUrl *url; /* primary "Key" for this dicache entry */ + DICacheEntry *first; /* pointer to the first dicache entry in this list */ +}; + +/* + * List of DICacheNode. One node per URL. Each node may have several + * versions of the same image in a linked list. + */ +static Dlist *CachedIMGs = NULL; + +static int dicache_size_total; /* invariant: dicache_size_total is + * the sum of the image sizes (3*w*h) + * of all the images in the dicache. */ + +/* + * Compare two dicache nodes + */ +static int Dicache_node_cmp(const void *v1, const void *v2) +{ + const DICacheNode *n1 = v1, *n2 = v2; + + return a_Url_cmp(n1->url, n2->url); +} + +/* + * Compare function for searching a node by Url + */ +static int Dicache_node_by_url_cmp(const void *v1, const void *v2) +{ + const DICacheNode *node = v1; + const DilloUrl *url = v2; + + return a_Url_cmp(node->url, url); +} + +/* + * Initialize dicache data + */ +void a_Dicache_init(void) +{ + CachedIMGs = dList_new(256); + dicache_size_total = 0; +} + +/* + * Create, and initialize a new, empty, dicache entry + */ +static DICacheEntry *Dicache_entry_new(void) +{ + DICacheEntry *entry = dNew(DICacheEntry, 1); + + entry->width = 0; + entry->height = 0; + entry->type = DILLO_IMG_TYPE_NOTSET; + entry->cmap = NULL; + entry->linebuf = NULL; + entry->v_imgbuf = NULL; + entry->RefCount = 1; + entry->TotalSize = 0; + entry->Y = 0; + entry->BitVec = NULL; + entry->State = DIC_Empty; + entry->version = 0; + entry->next = NULL; + + return entry; +} + +/* + * Add a new entry in the dicache + * (a single node (URL) may have several entries) + */ +DICacheEntry *a_Dicache_add_entry(const DilloUrl *Url) +{ + DICacheEntry *entry; + DICacheNode *node; + + entry = Dicache_entry_new(); + + if ((node = dList_find_sorted(CachedIMGs, Url, Dicache_node_by_url_cmp))) { + /* this URL is already in CachedIMGs, add entry at the END of the list */ + DICacheEntry *ptr = node->first; + + node->valid = 1; + for ( ; ptr->next; ptr = ptr->next); + ptr->next = entry; + entry->version = ptr->version+1; + entry->url = node->url; + + } else { /* no node yet, so create one */ + DICacheNode *node = dNew(DICacheNode, 1); + + node->url = a_Url_dup(Url); + entry->url = node->url; + node->first = entry; + node->valid = 1; + dList_insert_sorted(CachedIMGs, node, Dicache_node_cmp); + } + + return entry; +} + +/* + * Search an entry in the dicache (given the Url). + * Return value: a pointer to the entry of the _newest_ (i.e. highest) + * version if found; NULL otherwise. + */ +DICacheEntry *a_Dicache_get_entry(const DilloUrl *Url) +{ + DICacheNode *node; + DICacheEntry *entry; + + node = dList_find_sorted(CachedIMGs, Url, Dicache_node_by_url_cmp); + + if (!node || !node->valid) + return NULL; + + for (entry = node->first; (entry && entry->next); entry = entry->next); + + return entry; +} + +/* + * Search a particular version of a URL in the Dicache. + * Return value: a pointer to the entry if found; NULL otherwise. + */ +static DICacheEntry *Dicache_get_entry_version(const DilloUrl *Url, + int version) +{ + DICacheNode *node; + DICacheEntry *entry; + + node = dList_find_sorted(CachedIMGs, Url, Dicache_node_by_url_cmp); + entry = (node) ? node->first : NULL; + + while (entry && entry->version != version) + entry = entry->next; + + return entry; +} + +/* + * Actually free a dicache entry, given the URL and the version number. + */ +static void Dicache_remove(const DilloUrl *Url, int version) +{ + DICacheNode *node; + DICacheEntry *entry, *prev; + + node = dList_find_sorted(CachedIMGs, Url, Dicache_node_by_url_cmp); + prev = entry = (node) ? node->first : NULL; + + while (entry && (entry->version != version) ) { + prev = entry; + entry = entry->next; + } + + if (entry) { + /* Eliminate this dicache entry */ + dFree(entry->cmap); + dFree(entry->linebuf); + a_Bitvec_free(entry->BitVec); + a_Image_imgbuf_unref(entry->v_imgbuf); + dicache_size_total -= entry->TotalSize; + + if (node->first == entry) { + if (!entry->next) { + /* last entry with this URL. Remove the node as well */ + dList_remove(CachedIMGs, node); + a_Url_free(node->url); + dFree(node); + } else + node->first = entry->next; + } else { + prev->next = entry->next; + } + dFree(entry); + } +} + +/* + * Unrefs the counter of a dicache entry, and _if_ no DwImage is acessing + * this buffer, then we call Dicache_free to do the dirty job. + */ +void a_Dicache_unref(const DilloUrl *Url, int version) +{ + DICacheEntry *entry; + + if ((entry = Dicache_get_entry_version(Url, version))) { + /*if (--entry->RefCount == 0 && (entry->next || !prefs.use_dicache)) {*/ + if (--entry->RefCount == 0) { + Dicache_remove(Url, version); + } + } +} + +/* + * Refs the counter of a dicache entry. + */ + +DICacheEntry* a_Dicache_ref(const DilloUrl *Url, int version) +{ + DICacheEntry *entry; + + if ((entry = Dicache_get_entry_version(Url, version))) { + ++entry->RefCount; + } + return entry; +} + +/* + * Invalidate this entry. This is used for the reloading mechanism. + * Can't erase current versions, but a_Dicache_get_entry must return NULL. + */ +void a_Dicache_invalidate_entry(const DilloUrl *Url) +{ + DICacheNode *node; + + node = dList_find_sorted(CachedIMGs, Url, Dicache_node_by_url_cmp); + if (node) + node->valid = 0; +} + + +/* ------------------------------------------------------------------------- */ + +/* + * This function is a cache client; (but feeds its clients from dicache) + */ +void a_Dicache_callback(int Op, CacheClient_t *Client) +{ + /* todo: Handle Op = CA_Abort (to show what was got) --Jcid */ + uint_t i; + DilloWeb *Web = Client->Web; + DilloImage *Image = Web->Image; + DICacheEntry *DicEntry = a_Dicache_get_entry(Web->url); + + dReturn_if_fail ( DicEntry != NULL ); + + /* when the data stream is not an image 'v_imgbuf' keeps NULL */ + if (Op == CA_Send && DicEntry->v_imgbuf) { + if (Image->height == 0 && DicEntry->State >= DIC_SetParms) { + /* Set parms */ + a_Image_set_parms( + Image, DicEntry->v_imgbuf, DicEntry->url, + DicEntry->version, DicEntry->width, DicEntry->height, + DicEntry->type); + } + if (DicEntry->State == DIC_Write) { + for (i = 0; i < DicEntry->height; ++i) + if (a_Bitvec_get_bit(DicEntry->BitVec, (int)i) && + !a_Bitvec_get_bit(Image->BitVec, (int)i) ) + a_Image_write(Image, DicEntry->v_imgbuf, + DicEntry->linebuf, i, FALSE); + } + } else if (Op == CA_Close || Op == CA_Abort) { + a_Image_close(Web->Image); + a_Bw_close_client(Web->bw, Client->Key); + } +} + +/* ------------------------------------------------------------------------- */ + +/* + * Set image's width, height & type + * (By now, we'll use the image information despite the html tags --Jcid) + */ +void a_Dicache_set_parms(DilloUrl *url, int version, DilloImage *Image, + uint_t width, uint_t height, DilloImgType type) +{ + DICacheEntry *DicEntry; + size_t Size = width * height * 3; + + dReturn_if_fail ( Image != NULL && width && height ); + /* Find the DicEntry for this Image */ + DicEntry = Dicache_get_entry_version(url, version); + dReturn_if_fail ( DicEntry != NULL ); + + /* Initialize the DicEntry */ + DicEntry->linebuf = dNew(uchar_t, width * 3); + dReturn_if_fail ( DicEntry->linebuf != NULL ); + + /* BUG: there's just one image-type now */ + #define I_RGB 0 + DicEntry->v_imgbuf = a_Image_imgbuf_new(Image->dw, I_RGB, width, height); + + /* This extra reference activates the dicache ALWAYS. + * Extra code is necessary in Imgbuf to be able to free it */ + //a_Image_imgbuf_ref(DicEntry->v_imgbuf); + + DicEntry->TotalSize = Size; + DicEntry->width = width; + DicEntry->height = height; + DicEntry->type = type; + DicEntry->BitVec = a_Bitvec_new((int)height); + DicEntry->State = DIC_SetParms; + + dicache_size_total += Size; + + /* Allocate and initialize this image */ + a_Image_set_parms(Image, DicEntry->v_imgbuf, url, version, + width, height, type); +} + +/* + * Implement the set_cmap method for the Image + */ +void a_Dicache_set_cmap(DilloUrl *url, int version, DilloImage *Image, + const uchar_t *cmap, uint_t num_colors, + int num_colors_max, int bg_index) +{ + DICacheEntry *DicEntry = Dicache_get_entry_version(url, version); + + dReturn_if_fail ( DicEntry != NULL ); + + dFree(DicEntry->cmap); + DicEntry->cmap = dNew0(uchar_t, 3 * num_colors_max); + memcpy(DicEntry->cmap, cmap, 3 * num_colors); + if (bg_index >= 0 && (uint_t)bg_index < num_colors) { + DicEntry->cmap[bg_index * 3] = (Image->bg_color >> 16) & 0xff; + DicEntry->cmap[bg_index * 3 + 1] = (Image->bg_color >> 8) & 0xff; + DicEntry->cmap[bg_index * 3 + 2] = (Image->bg_color) & 0xff; + } + + a_Image_set_cmap(Image, DicEntry->cmap); + DicEntry->State = DIC_SetCmap; +} + +/* + * Implement the write method + * (Write a scan line into the Dicache entry) + * buf: row buffer + * Y : row number + * x : horizontal offset? (always zero) + */ +void a_Dicache_write(DilloImage *Image, DilloUrl *url, int version, + const uchar_t *buf, int x, uint_t Y) +{ + DICacheEntry *DicEntry; + + dReturn_if_fail ( Image != NULL ); + DicEntry = Dicache_get_entry_version(url, version); + dReturn_if_fail ( DicEntry != NULL ); + dReturn_if_fail ( DicEntry->width > 0 && DicEntry->height > 0 ); + + a_Image_write(Image, DicEntry->v_imgbuf, buf, Y, TRUE); + DicEntry->Y = Y; + a_Bitvec_set_bit(DicEntry->BitVec, (int)Y); + DicEntry->State = DIC_Write; +} + +/* + * Implement the close method of the decoding process + */ +void a_Dicache_close(DilloUrl *url, int version, CacheClient_t *Client) +{ + DilloWeb *Web = Client->Web; + DICacheEntry *DicEntry = Dicache_get_entry_version(url, version); + + dReturn_if_fail ( DicEntry != NULL ); + + DicEntry->State = DIC_Close; + dFree(DicEntry->cmap); + DicEntry->cmap = NULL; + dFree(DicEntry->linebuf); + DicEntry->linebuf = NULL; + a_Image_close(Web->Image); + a_Bw_close_client(Web->bw, Client->Key); +} + +/* + * Free the imgbuf (RGB data) of unused entries. + */ +void a_Dicache_cleanup(void) +{ + int i; + DICacheNode *node; + DICacheEntry *entry; + + for (i = 0; i < dList_length(CachedIMGs); ++i) { + node = dList_nth_data(CachedIMGs, i); + /* iterate each entry of this node */ + for (entry = node->first; entry; entry = entry->next) { + if (entry->v_imgbuf && + a_Image_imgbuf_last_reference(entry->v_imgbuf)) { + /* free this unused entry */ + if (entry->next) { + Dicache_remove(node->url, entry->version); + } else { + Dicache_remove(node->url, entry->version); + --i; + break; + } + } + } + } +} + +/* ------------------------------------------------------------------------- */ + +/* + * Deallocate memory used by dicache module + * (Call this one at exit time) + */ +void a_Dicache_freeall(void) +{ + DICacheNode *node; + DICacheEntry *entry; + + /* Remove every dicache node and its entries */ + while ((node = dList_nth_data(CachedIMGs, 0))) { + while ((entry = node->first)) { + node->first = entry->next; + dFree(entry->cmap); + dFree(entry->linebuf); + a_Bitvec_free(entry->BitVec); + a_Image_imgbuf_unref(entry->v_imgbuf); + dicache_size_total -= entry->TotalSize; + } + dList_remove(CachedIMGs, node); + a_Url_free(node->url); + dFree(node); + } + dList_free(CachedIMGs); +} diff --git a/src/dicache.h b/src/dicache.h new file mode 100644 index 00000000..9afa5045 --- /dev/null +++ b/src/dicache.h @@ -0,0 +1,70 @@ +#ifndef __DICACHE_H__ +#define __DICACHE_H__ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +#include "bitvec.h" +#include "image.hh" +#include "cache.h" + +/* These will reflect the entry's "state" */ +typedef enum { + DIC_Empty, /* Just created the entry */ + DIC_SetParms, /* Parameters set */ + DIC_SetCmap, /* Color map set */ + DIC_Write, /* Feeding the entry */ + DIC_Close, /* Whole image got! */ + DIC_Abort /* Image transfer aborted */ +} DicEntryState; + +typedef struct _DICacheEntry DICacheEntry; + +struct _DICacheEntry { + DilloUrl *url; /* Image URL for this entry */ + uint_t width, height; /* As taken from image data */ + DilloImgType type; /* Image type */ + uchar_t *cmap; /* Color map */ + uchar_t *linebuf; /* Decompressed RGB buffer for one line */ + void *v_imgbuf; /* Void pointer to an Imgbuf object */ + size_t TotalSize; /* Amount of memory the image takes up */ + int Y; /* Current decoding row */ + bitvec_t *BitVec; /* Bit vector for decoded rows */ + DicEntryState State; /* Current status for this entry */ + int RefCount; /* Reference Counter */ + int version; /* Version number, used for different + versions of the same URL image */ + + DICacheEntry *next; /* Link to the next "newer" version */ +}; + + +void a_Dicache_init (void); + +DICacheEntry *a_Dicache_get_entry(const DilloUrl *Url); +DICacheEntry *a_Dicache_add_entry(const DilloUrl *Url); + +void a_Dicache_callback(int Op, CacheClient_t *Client); + +void a_Dicache_set_parms(DilloUrl *url, int version, DilloImage *Image, + uint_t width, uint_t height, DilloImgType type); +void a_Dicache_set_cmap(DilloUrl *url, int version, DilloImage *Image, + const uchar_t *cmap, uint_t num_colors, + int num_colors_max, int bg_index); +void a_Dicache_write(DilloImage *Image, DilloUrl *url, int version, + const uchar_t *buf, int x, uint_t Y); +void a_Dicache_close(DilloUrl *url, int version, CacheClient_t *Client); + +void a_Dicache_invalidate_entry(const DilloUrl *Url); +DICacheEntry* a_Dicache_ref(const DilloUrl *Url, int version); +void a_Dicache_unref(const DilloUrl *Url, int version); +void a_Dicache_cleanup(void); +void a_Dicache_freeall(void); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* __DICACHE_H__ */ diff --git a/src/dillo.cc b/src/dillo.cc new file mode 100644 index 00000000..bd8ceeb8 --- /dev/null +++ b/src/dillo.cc @@ -0,0 +1,108 @@ +/* + * Dillo web browser + * + * Copyright 1999-2006 Jorge Arellano Cid <jcid@dillo.org> + * + * 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 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <stdio.h> +#include <unistd.h> + +#include <fltk/Window.h> +#include <fltk/run.h> + +#include "dir.h" +#include "uicmd.hh" + +#include "msg.h" +#include "bw.h" +#include "bookmark.h" +#include "misc.h" +#include "nav.h" + +#include "dns.h" +#include "web.hh" +#include "IO/Url.h" +#include "IO/mime.h" +#include "capi.h" +#include "dicache.h" +#include "cookies.h" + + +/* + * Given a command line argument, build a DilloUrl for it. + */ +static DilloUrl *Dillo_make_start_url(char *str) +{ + char *url_str, *p; + DilloUrl *start_url; + int is_file = FALSE; + + /* Relative path to a local file? */ + p = (*str == '/') ? dStrdup(str) : dStrconcat(a_Dir_get_owd(),"/",str,NULL); + + if (access(p, F_OK) == 0) { + /* absolute path may have non-URL characters */ + url_str = a_Misc_escape_chars(p, "% "); + is_file = TRUE; + } else { + /* Not a file, filter URL string */ + url_str = a_Url_string_strip_delimiters(str); + } + dFree(p); + + if (is_file) { + start_url = a_Url_new(url_str + 1, "file:/", 0, 0, 0); + } else { + start_url = a_Url_new(url_str, NULL, 0, 0, 0); + } + dFree(url_str); + + return start_url; +} + +/* + * MAIN + */ +int main(int argc, char **argv) +{ + // Initialize internal modules + a_Dir_init(); + a_Prefs_init(); + a_Dpi_init(); + a_Dns_init(); + a_Web_init(); + a_Http_init(); + a_Mime_init(); + a_Capi_init(); + a_Dicache_init(); + a_Bw_init(); + a_Cookies_init(); + + // Create a new UI/bw pair + BrowserWindow *bw = a_UIcmd_browser_window_new(0, 0); + + if (argc == 2) { + DilloUrl *url = Dillo_make_start_url(argv[1]); + a_UIcmd_open_urlstr(bw, URL_STR(url)); + a_Url_free(url); + } else { + /* Send startup screen */ + //a_Nav_push(bw, prefs.start_page); + } + + return fltk::run(); +} diff --git a/src/dir.c b/src/dir.c new file mode 100644 index 00000000..53497895 --- /dev/null +++ b/src/dir.c @@ -0,0 +1,48 @@ +/* + * File: dir.c + * + * Copyright 2006 Jorge Arellano Cid <jcid@dillo.org> + * + * 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 3 of the License, or + * (at your option) any later version. + */ + +#include <unistd.h> + +#include "../dlib/dlib.h" + + +/* + * Local data + */ +/* Dillo works from an unmounted directory (/tmp). */ +static char *OldWorkingDirectory = NULL; + +/* + * Change current working directory to "/tmp". + */ +void a_Dir_init(void) +{ + dFree(OldWorkingDirectory); + OldWorkingDirectory = dGetcwd(); + chdir("/tmp"); +} + +/* + * Return the initial current working directory in a string. + */ +char *a_Dir_get_owd(void) +{ + return OldWorkingDirectory; +} + +/* + * Free memory + */ +void a_Dir_free(void) +{ + dFree(OldWorkingDirectory); +} + diff --git a/src/dir.h b/src/dir.h new file mode 100644 index 00000000..580122f0 --- /dev/null +++ b/src/dir.h @@ -0,0 +1,19 @@ +#ifndef __DIR_H__ +#define __DIR_H__ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +void a_Dir_init(void); +char *a_Dir_get_owd(void); +void a_Dir_free(void); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __DIR_H__ */ + diff --git a/src/dns.c b/src/dns.c new file mode 100644 index 00000000..2813f54f --- /dev/null +++ b/src/dns.c @@ -0,0 +1,535 @@ +/* + * File: dns.c + * + * Copyright (C) 1999-2006 Jorge Arellano Cid <jcid@dillo.org> + * + * 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 3 of the License, or + * (at your option) any later version. + */ + +/* + * Non blocking pthread-handled Dns scheme + */ + +#include <pthread.h> + +#include <netdb.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <signal.h> +#include <string.h> + +#include "msg.h" +#include "dns.h" +#include "list.h" +#include "timeout.hh" + +#define DEBUG_LEVEL 5 +#include "debug.h" + + +/* + * Uncomment the following line for debugging or gprof profiling. + */ +/* #undef D_DNS_THREADED */ + +/* + * Uncomment the following line for libc5 optimization + */ +/* #define LIBC5 */ + + +/* Maximum dns resolving threads */ +#ifdef D_DNS_THREADED +# define D_DNS_MAX_SERVERS 4 +#else +# define D_DNS_MAX_SERVERS 1 +#endif + + +typedef struct { + int channel; /* Index of this channel [0 based] */ + bool_t in_use; /* boolean to tell if server is doing a lookup */ + bool_t ip_ready; /* boolean: is IP lookup done? */ + Dlist *addr_list; /* IP address */ + char *hostname; /* Adress to resolve */ + int status; /* errno code for resolving function */ +#ifdef D_DNS_THREADED + pthread_t th1; /* Thread id */ +#endif +} DnsServer; + +typedef struct { + char *hostname; /* host name for cache */ + Dlist *addr_list; /* addresses of host */ +} GDnsCache; + +typedef struct { + int channel; /* -2 if waiting, otherwise index to dns_server[] */ + char *hostname; /* The one we're resolving */ + DnsCallback_t cb_func; /* callback function */ + void *cb_data; /* extra data for the callback function */ +} GDnsQueue; + + +/* + * Forward declarations + */ +static void Dns_timeout_client(void *data); + +/* + * Local Data + */ +static DnsServer dns_server[D_DNS_MAX_SERVERS]; +static int num_servers; +static GDnsCache *dns_cache; +static int dns_cache_size, dns_cache_size_max; +static GDnsQueue *dns_queue; +static int dns_queue_size, dns_queue_size_max; +static bool_t ipv6_enabled; + + +/* ---------------------------------------------------------------------- + * Dns queue functions + */ +static void Dns_queue_add(int channel, const char *hostname, + DnsCallback_t cb_func, void *cb_data) +{ + a_List_add(dns_queue, dns_queue_size, dns_queue_size_max); + dns_queue[dns_queue_size].channel = channel; + dns_queue[dns_queue_size].hostname = dStrdup(hostname); + dns_queue[dns_queue_size].cb_func = cb_func; + dns_queue[dns_queue_size].cb_data = cb_data; + dns_queue_size++; +} + +/* + * Find hostname index in dns_queue + * (if found, returns queue index; -1 if not) + */ +static int Dns_queue_find(const char *hostname) +{ + int i; + + for (i = 0; i < dns_queue_size; i++) + if (!strcmp(hostname, dns_queue[i].hostname)) + return i; + + return -1; +} + +/* + * Given an index, remove an entry from the dns_queue + */ +static void Dns_queue_remove(int index) +{ + int i; + + DEBUG_MSG(2, "Dns_queue_remove: deleting client [%d] [queue_size=%d]\n", + index, dns_queue_size); + + if (index < dns_queue_size) { + dFree(dns_queue[index].hostname); + --dns_queue_size; /* you'll find out why ;-) */ + for (i = index; i < dns_queue_size; i++) + dns_queue[i] = dns_queue[i + 1]; + } +} + +/* + * Debug function + * +void Dns_queue_print() +{ + int i; + + MSG("Queue: ["); + for (i = 0; i < dns_queue_size; i++) + MSG("%d:%s ", dns_queue[i].channel, dns_queue[i].hostname); + MSG("]\n"); +} + */ + +/* + * Add an IP/hostname pair to Dns-cache + */ +static void Dns_cache_add(char *hostname, Dlist *addr_list) +{ + a_List_add(dns_cache, dns_cache_size, dns_cache_size_max); + dns_cache[dns_cache_size].hostname = dStrdup(hostname); + dns_cache[dns_cache_size].addr_list = addr_list; + ++dns_cache_size; + DEBUG_MSG(1, "Cache objects: %d\n", dns_cache_size); +} + + +/* + * Initializer function + */ +void a_Dns_init(void) +{ + int i; + +#ifdef D_DNS_THREADED + DEBUG_MSG(5, "dillo_dns_init: Here we go! (threaded)\n"); +#else + DEBUG_MSG(5, "dillo_dns_init: Here we go! (not threaded)\n"); +#endif + + dns_queue_size = 0; + dns_queue_size_max = 16; + dns_queue = dNew(GDnsQueue, dns_queue_size_max); + + dns_cache_size = 0; + dns_cache_size_max = 16; + dns_cache = dNew(GDnsCache, dns_cache_size_max); + + num_servers = D_DNS_MAX_SERVERS; + + /* Initialize servers data */ + for (i = 0; i < num_servers; ++i) { + dns_server[i].channel = i; + dns_server[i].in_use = FALSE; + dns_server[i].ip_ready = FALSE; + dns_server[i].addr_list = NULL; + dns_server[i].hostname = NULL; + dns_server[i].status = 0; +#ifdef D_DNS_THREADED + dns_server[i].th1 = (pthread_t) -1; +#endif + } + + /* IPv6 test */ + ipv6_enabled = FALSE; +#ifdef ENABLE_IPV6 + { + /* If the IPv6 address family is not available there is no point + wasting time trying to connect to v6 addresses. */ + int fd = socket(AF_INET6, SOCK_STREAM, 0); + if (fd >= 0) { + close(fd); + ipv6_enabled = TRUE; + } + } +#endif +} + +/* + * Allocate a host structure and add it to the list + */ +static void Dns_note_hosts(Dlist *list, int af, struct hostent *host) +{ + int i; + + if (host->h_length > DILLO_ADDR_MAX) + return; + for (i = 0; host->h_addr_list[i]; i++) { + DilloHost *dh = dNew0(DilloHost, 1); + dh->af = af; + dh->alen = host->h_length; + memcpy(&dh->data[0], host->h_addr_list[i], (size_t)host->h_length); + dList_append(list, dh); + } +} + +#ifdef D_DNS_THREADED +/* + * Server function (runs on its own thread) + */ +static void *Dns_server(void *data) +{ + struct hostent *host; + int channel = VOIDP2INT(data); +#ifdef LIBC5 + int h_err; + char buff[1024]; + struct hostent sh; +#endif + Dlist *hosts = dList_new(2); + + DEBUG_MSG(3, "Dns_server: starting...\n ch: %d host: %s\n", + channel, dns_server[channel].hostname); + +#ifdef ENABLE_IPV6 + if (ipv6_enabled) { + host = gethostbyname2(dns_server[channel].hostname, AF_INET6); + if (host) { + Dns_note_hosts(hosts, AF_INET6, host); + } + } +#endif + +#ifdef LIBC5 + host = gethostbyname_r(dns_server[channel].hostname, &sh, buff, + sizeof(buff), &h_err); +#else + host = gethostbyname(dns_server[channel].hostname); +#endif + + if (!host) { +#ifdef LIBC5 + dns_server[channel].status = h_err; +#else + dns_server[channel].status = h_errno; + if (h_errno == HOST_NOT_FOUND) + MSG("DNS error: HOST_NOT_FOUND\n"); + else if (h_errno == TRY_AGAIN) + MSG("DNS error: TRY_AGAIN\n"); + else if (h_errno == NO_RECOVERY) + MSG("DNS error: NO_RECOVERY\n"); + else if (h_errno == NO_ADDRESS) + MSG("DNS error: NO_ADDRESS\n"); +#endif + } else { + dns_server[channel].status = 0; + Dns_note_hosts(hosts, AF_INET, host); + } + if (dList_length(hosts) > 0) { + dns_server[channel].status = 0; + } else { + dList_free(hosts); + hosts = NULL; + } + + /* tell our findings */ + DEBUG_MSG(5, "Dns_server [%d]: %s is %p\n", channel, + dns_server[channel].hostname, hosts); + dns_server[channel].addr_list = hosts; + dns_server[channel].ip_ready = TRUE; + + return NULL; /* (avoids a compiler warning) */ +} +#endif + +#ifndef D_DNS_THREADED +/* + * Blocking server-function (it doesn't use threads) + */ +static void Dns_blocking_server(void) +{ + int channel = 0; + struct hostent *host = NULL; + dList *hosts = dList_new(2); +#ifdef LIBC5 + int h_err; +#endif + + DEBUG_MSG(3, "Dns_blocking_server: starting...\n"); + DEBUG_MSG(3, "Dns_blocking_server: dns_server[%d].hostname = %s\n", + channel, dns_server[channel].hostname); + +#ifdef ENABLE_IPV6 + if (ipv6_enabled) { + host = gethostbyname2(dns_server[channel].hostname, AF_INET6); + if (host) { + Dns_note_hosts(hosts, AF_INET6, host); + } + } +#endif + +#ifdef LIBC5 + host = gethostbyname_r(dns_server[channel].hostname, &sh, buff, + sizeof(buff), &h_err); +#else + host = gethostbyname(dns_server[channel].hostname); +#endif + + if (!host) { +#ifdef LIBC5 + dns_server[channel].status = h_err; +#else + dns_server[channel].status = h_errno; +#endif + } else { + Dns_note_hosts(hosts, AF_INET, host); + } + if (dList_length(hosts) > 0) { + /* at least one entry on the list is ok */ + dns_server[channel].status = 0; + } else { + dList_free(hosts); + hosts = NULL; + } + + /* write IP to server data channel */ + DEBUG_MSG(3, "Dns_blocking_server: IP of %s is %p\n", + dns_server[channel].hostname, hosts); + dns_server[channel].addr_list = hosts; + dns_server[channel].ip_ready = TRUE; + + DEBUG_MSG(3, "Dns_blocking_server: leaving...\n"); +} +#endif + +/* + * Request function (spawn a server and let it handle the request) + */ +static void Dns_server_req(int channel, const char *hostname) +{ +#ifdef D_DNS_THREADED + static pthread_attr_t thrATTR; + static int thrATTRInitialized = 0; +#endif + + dns_server[channel].in_use = TRUE; + dns_server[channel].ip_ready = FALSE; + + dFree(dns_server[channel].hostname); + dns_server[channel].hostname = dStrdup(hostname); + + /* Let's set a timeout client to poll the server channel (5 times/sec) */ + a_Timeout_add(0.2,Dns_timeout_client,(void*)(dns_server[channel].channel)); + +#ifdef D_DNS_THREADED + /* set the thread attribute to the detached state */ + if (!thrATTRInitialized) { + pthread_attr_init(&thrATTR); + pthread_attr_setdetachstate(&thrATTR, PTHREAD_CREATE_DETACHED); + thrATTRInitialized = 1; + } + /* Spawn thread */ + pthread_create(&dns_server[channel].th1, &thrATTR, Dns_server, + INT2VOIDP(dns_server[channel].channel)); +#else + Dns_blocking_server(); +#endif +} + +/* + * Return the IP for the given hostname using a callback. + * Side effect: a thread is spawned when hostname is not cached. + */ +void a_Dns_resolve(const char *hostname, DnsCallback_t cb_func, void *cb_data) +{ + int i, channel; + + if (!hostname) + return; + + /* check for cache hit. */ + for (i = 0; i < dns_cache_size; i++) + if (!strcmp(hostname, dns_cache[i].hostname)) + break; + + if (i < dns_cache_size) { + /* already resolved, call the Callback inmediately. */ + cb_func(0, dns_cache[i].addr_list, cb_data); + + } else if ((i = Dns_queue_find(hostname)) != -1) { + /* hit in queue, but answer hasn't come back yet. */ + Dns_queue_add(dns_queue[i].channel, hostname, cb_func, cb_data); + + } else { + /* Never requested before -- we must resolve it! */ + + /* Find a channel we can send the request to */ + for (channel = 0; channel < num_servers; channel++) + if (!dns_server[channel].in_use) + break; + if (channel < num_servers) { + /* Found a free channel! */ + Dns_queue_add(channel, hostname, cb_func, cb_data); + Dns_server_req(channel, hostname); + } else { + /* We'll have to wait for a thread to finish... */ + Dns_queue_add(-2, hostname, cb_func, cb_data); + } + } +} + +/* + * Give answer to all queued callbacks on this channel + */ +static void Dns_serve_channel(int channel) +{ + int i; + DnsServer *srv = &dns_server[channel]; + + for (i = 0; i < dns_queue_size; i++) { + if (dns_queue[i].channel == channel) { + dns_queue[i].cb_func(srv->status, srv->addr_list, + dns_queue[i].cb_data); + Dns_queue_remove(i); + --i; + } + } + /* set current channel free */ + srv->in_use = FALSE; +} + +/* + * Assign free channels to waiting clients (-2) + */ +static void Dns_assign_channels(void) +{ + int ch, i, j; + + for (ch = 0; ch < num_servers; ++ch) { + if (dns_server[ch].in_use == FALSE) { + /* Find the next query in the queue (we're a FIFO) */ + for (i = 0; i < dns_queue_size; i++) + if (dns_queue[i].channel == -2) + break; + + if (i < dns_queue_size) { + /* assign this channel to every queued request + * with the same hostname*/ + for (j = i; j < dns_queue_size; j++) + if (dns_queue[j].channel == -2 && + !strcmp(dns_queue[j].hostname, dns_queue[i].hostname)) + dns_queue[j].channel = ch; + Dns_server_req(ch, dns_queue[i].hostname); + } else + return; + } + } +} + +/* + * This is a timeout function that + * reads the DNS results and resumes the stopped jobs. + */ +static void Dns_timeout_client(void *data) +{ + int channel = (int)data; + DnsServer *srv = &dns_server[channel]; + + if (srv->ip_ready) { + if (srv->addr_list != NULL) { + /* DNS succeeded, let's cache it */ + Dns_cache_add(srv->hostname, srv->addr_list); + } + Dns_serve_channel(channel); + Dns_assign_channels(); + a_Timeout_remove(); /* Done! */ + + } else { + /* IP not already resolved, keep on trying... */ + a_Timeout_repeat(0.2, Dns_timeout_client, data); + } +} + + +/* + * Dns memory-deallocation + * (Call this one at exit time) + * The Dns_queue is deallocated at execution time (no need to do that here) + * 'dns_cache' is the only one that grows dinamically + */ +void a_Dns_freeall(void) +{ + int i; + + for ( i = 0; i < dns_cache_size; ++i ){ + dFree(dns_cache[i].hostname); + } + dFree(dns_cache); +} + diff --git a/src/dns.h b/src/dns.h new file mode 100644 index 00000000..13392eba --- /dev/null +++ b/src/dns.h @@ -0,0 +1,31 @@ +#ifndef __DNS_H__ +#define __DNS_H__ + +#include "chain.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +typedef void (*DnsCallback_t)(int Status, Dlist *addr_list, void *data); + +void a_Dns_init (void); +void a_Dns_freeall(void); +void a_Dns_resolve(const char *hostname, DnsCallback_t cb_func, void *cb_data); + +#define DILLO_ADDR_MAX 16 + +typedef struct _DilloHost +{ + int af; + int alen; + char data[DILLO_ADDR_MAX]; +} DilloHost; + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __DNS_H__ */ diff --git a/src/dpiapi.c b/src/dpiapi.c new file mode 100644 index 00000000..382cf122 --- /dev/null +++ b/src/dpiapi.c @@ -0,0 +1,82 @@ +/* + * File: dpiapi.c + * + * Copyright (C) 2004 Jorge Arellano Cid <jcid@dillo.org> + * + * 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 3 of the License, or + * (at your option) any later version. + */ + +/* Support for dpi/dpip from Dillo's side */ + +#include "msg.h" +#include "bw.h" +#include "capi.h" +#include "dpiapi.h" /* for prototypes */ +#include "../dpip/dpip.h" + + +//---------------------------------------------------------------------------- +// Dialog interface +// + +/* This variable can be eliminated as a parameter with a cleaner API. */ +static char *dialog_server = NULL; + + +/* + * Generic callback function for dpip dialogs. + */ +//static void Dpiapi_dialog_answer_cb(BrowserWindow *bw) +//{ +// DialogAnswer *answer = bw->question_dialog_answer; +// char *cmd, numstr[16]; +// +// /* make dpip tag with the answer */ +// snprintf(numstr, 16, "%d", answer->alt_num); +// cmd = a_Dpip_build_cmd("cmd=%s to_cmd=%s msg=%s", +// "answer", "dialog", numstr); +// +// /* Send answer */ +// a_Capi_dpi_send_cmd(NULL, bw, cmd, dialog_server, 0); +// +// /* cleanup */ +// bw->question_dialog_data = NULL; +// dFree(answer->tthis); +// bw->question_dialog_answer = NULL; +//} + +/* + * Process a dpip "dialog" command from any dpi. + */ +void a_Dpiapi_dialog(BrowserWindow *bw, char *server, char *dpip_tag) +{ + char *question, *alt1, *alt2, *alt3, *alt4, *alt5; + size_t dpip_tag_len; + + MSG("a_Dpiapi_dialog:\n"); + MSG(" dpip_tag: %s\n", dpip_tag); + + /* set the module scoped variable */ + dialog_server = server; + + /* other options can be parsed the same way */ + dpip_tag_len = strlen(dpip_tag); + question = a_Dpip_get_attr(dpip_tag, dpip_tag_len, "msg"); + alt1 = a_Dpip_get_attr(dpip_tag, dpip_tag_len, "alt1"); + alt2 = a_Dpip_get_attr(dpip_tag, dpip_tag_len, "alt2"); + alt3 = a_Dpip_get_attr(dpip_tag, dpip_tag_len, "alt3"); + alt4 = a_Dpip_get_attr(dpip_tag, dpip_tag_len, "alt4"); + alt5 = a_Dpip_get_attr(dpip_tag, dpip_tag_len, "alt5"); + + //a_Dialog_question5( + // bw, question, TRUE, + // alt1, alt2, alt3, alt4, alt5, + // Dpiapi_dialog_answer_cb); + + dFree(alt1); dFree(alt2); dFree(alt3); dFree(alt4); dFree(alt5); + dFree(question); +} + diff --git a/src/dpiapi.h b/src/dpiapi.h new file mode 100644 index 00000000..26823026 --- /dev/null +++ b/src/dpiapi.h @@ -0,0 +1,3 @@ + +void a_Dpiapi_dialog(BrowserWindow *bw, char *server, char *dpip_tag); + diff --git a/src/form.cc b/src/form.cc new file mode 100644 index 00000000..f170cbef --- /dev/null +++ b/src/form.cc @@ -0,0 +1,98 @@ +#include "form.hh" +#include "html.hh" + +namespace form { + +using namespace dw::core::ui; + +Form::ResourceDecorator::ResourceDecorator (const char *name) +{ + this->name = strdup (name); +} + +Form::ResourceDecorator::~ResourceDecorator () +{ + delete name; +} + +Form::TextResourceDecorator::TextResourceDecorator (const char *name, + TextResource *resource): + Form::ResourceDecorator (name) +{ + this->resource = resource; +} + +const char *Form::TextResourceDecorator::getValue () +{ + return resource->getText (); +} + +Form::RadioButtonResourceDecorator::RadioButtonResourceDecorator + (const char *name, RadioButtonResource *resource, const char **values): + Form::ResourceDecorator (name) +{ + this->resource = resource; + + int n = 0; + while (values[n]) + n++; + this->values = new const char*[n]; + for(int i = 0; i < n; i++) + this->values[i] = strdup (values[i]); + values[n] = 0; +} + +Form::RadioButtonResourceDecorator::~RadioButtonResourceDecorator () +{ + for(int i = 0; values[i]; i++) + delete values[i]; + delete values; +} + +const char *Form::RadioButtonResourceDecorator::getValue () +{ + RadioButtonResource::GroupIterator *it; + int i; + for (it = resource->groupIterator (), i = 0; it->hasNext (); i++) { + RadioButtonResource *resource = it->getNext (); + if(resource->isActivated ()) { + it->unref (); + return values[i]; + } + } + + it->unref (); + return NULL; +} + + +Form::Form (void *p) +{ + ext_data = p; + resources = new container::typed::List <ResourceDecorator> (true); +} + +Form::~Form () +{ + delete resources; +} + +void Form::clicked (ButtonResource *resource, int buttonNo) +{ +/* + for (container::typed::Iterator <ResourceDecorator> it = + resources->iterator (); + it.hasNext (); ) { + ResourceDecorator *resource = it.getNext (); + const char *value = resource->getValue (); + if (value) + printf ("%s = %s\n", resource->getName (), value); + } +*/ + printf ("Form::clicked:: Button was clicked\n"); + + // Let html.cc handle the event + a_Html_form_event_handler(ext_data, this, (Resource*)resource); +} + +} // namespace form diff --git a/src/form.hh b/src/form.hh new file mode 100644 index 00000000..9ea47bb0 --- /dev/null +++ b/src/form.hh @@ -0,0 +1,87 @@ +#ifndef __FORM_HH__ +#define __FORM_HH__ + +#include "dw/core.hh" +#include "dw/ui.hh" + +namespace form { + +/** + * \brief Handles HTML form data. + * + * Add resources by calling the respective add...Resource method. Furtermore, + * this class impelements dw::core::ui::ButtonResource::ClickedReceiver, the + * form data is printed to stdout, when the "clicked" signal is received. + */ +class Form: public dw::core::ui::ButtonResource::ClickedReceiver +{ +private: + /** + * \brief Decorates instances of dw::core::ui::Resource. + * + * This is the abstract base class, sub classes have to be defined to + * decorate specific sub interfaces of dw::core::ui::Resource. + */ + class ResourceDecorator: public object::Object + { + private: + const char *name; + + protected: + ResourceDecorator (const char *name); + ~ResourceDecorator (); + + public: + inline const char *getName () { return name; } + virtual const char *getValue () = 0; + }; + + /** + * \brief Decorates instances of dw::core::ui::TextResource. + */ + class TextResourceDecorator: public ResourceDecorator + { + private: + dw::core::ui::TextResource *resource; + + public: + TextResourceDecorator (const char *name, + dw::core::ui::TextResource *resource); + const char *getValue (); + }; + + /** + * \brief Decorates instances of dw::core::ui::RadioButtonResource. + * + * This class has to be instanciated only once for a group of radio + * buttons. + */ + class RadioButtonResourceDecorator: public ResourceDecorator + { + private: + dw::core::ui::RadioButtonResource *resource; + const char **values; + + public: + RadioButtonResourceDecorator (const char *name, + dw::core::ui::RadioButtonResource + *resource, + const char **values); + ~RadioButtonResourceDecorator (); + const char *getValue (); + }; + + container::typed::List <ResourceDecorator> *resources; + + void *ext_data; // external data pointer + +public: + Form (void *p); + ~Form (); + void clicked (dw::core::ui::ButtonResource *resource, int buttonNo); + +}; + +} // namespace form + +#endif // __FORM_HH__ diff --git a/src/gif.c b/src/gif.c new file mode 100644 index 00000000..9806cbb1 --- /dev/null +++ b/src/gif.c @@ -0,0 +1,1054 @@ +/* + * File: gif.c + * + * Copyright (C) 1997 Raph Levien <raph@acm.org> + * Copyright (C) 2000-2002 Jorge Arellano Cid <jcid@dillo.org> + * + * 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 3 of the License, or + * (at your option) any later version. + */ + +/* + * The GIF decoder for dillo. It is responsible for decoding GIF data + * and transferring it to the dicache. + */ + + +/* Notes 13 Oct 1997 --RLL + * + * Today, just for the hell of it, I implemented a new decoder from + * scratch. It's oriented around pushing bytes, while the old decoder + * was based around reads which may suspend. There were basically + * three motivations. + * + * 1. To increase the speed. + * + * 2. To fix some bugs I had seen, most likely due to suspension. + * + * 3. To make sure that the code had no buffer overruns or the like. + * + * 4. So that the code could be released under a freer license. + * + * Let's see how we did on speed. I used a large image for testing + * (fvwm95-2.gif). + * + * The old decoder spent a total of about 1.04 seconds decoding the + * image. Another .58 seconds went into Image_line, almost + * entirely conversion from colormap to RGB. + * + * The new decoder spent a total of 0.46 seconds decoding the image. + * However, the time for Image_line went up to 1.01 seconds. + * Thus, even though the decoder seems to be about twice as fast, + * the net gain is pretty minimal. Could this be because of cache + * effects? + * + * Lessons learned: The first, which I keep learning over and over, is + * not to try to optimize too much. It doesn't work. Just keep things + * simple. + * + * Second, it seems that the colormap to RGB conversion is really a + * significant part of the overall time. It's possible that going + * directly to 16 bits would help, but that's optimization again :) + */ + + +/* todo: + * + Make sure to handle error cases gracefully (including aborting the + * connection, if necessary). + */ + +#include <config.h> +#ifdef ENABLE_GIF + +#include <stdio.h> /* for sprintf */ +#include <string.h> /* for memcpy and memmove */ + +#include "msg.h" +#include "image.hh" +#include "web.hh" +#include "cache.h" +#include "dicache.h" +#include "prefs.h" + +#define DEBUG_LEVEL 6 +#include "debug.h" + +#define INTERLACE 0x40 +#define LOCALCOLORMAP 0x80 + +#define LM_to_uint(a,b) ((((uchar_t)b)<<8)|((uchar_t)a)) + +#define MAXCOLORMAPSIZE 256 +#define MAX_LWZ_BITS 12 + + +typedef struct _DilloGif { + DilloImage *Image; + DilloUrl *url; + int version; + + int state; + size_t Start_Ofs; + uint_t Flags; + + uchar_t input_code_size; + uchar_t *linebuf; + int pass; + + uint_t y; + + /* state for lwz_read_byte */ + int code_size; + + /* The original GifScreen from giftopnm */ + uint_t Width; + uint_t Height; + size_t ColorMap_ofs; + uint_t ColorResolution; + uint_t NumColors; + int Background; + uint_t spill_line_index; +#if 0 + uint_t AspectRatio; /* AspectRatio (not used) */ +#endif + + /* Gif89 extensions */ + int transparent; +#if 0 + /* None are used: */ + int delayTime; + int inputFlag; + int disposal; +#endif + + /* state for the new push-oriented decoder */ + int packet_size; /* The amount of the data block left to process */ + uint_t window; + int bits_in_window; + uint_t last_code; /* Last "compressed" code in the look up table */ + uint_t line_index; + uchar_t **spill_lines; + int num_spill_lines_max; + int length[(1 << MAX_LWZ_BITS) + 1]; + int code_and_byte[(1 << MAX_LWZ_BITS) + 1]; +} DilloGif; + +/* Some invariants: + * + * last_code <= code_mask + * + * code_and_byte is stored packed: (code << 8) | byte + */ + + +/* + * Forward declarations + */ +static void Gif_write(DilloGif *gif, void *Buf, uint_t BufSize); +static void Gif_close(DilloGif *gif, CacheClient_t *Client); +static size_t Gif_process_bytes(DilloGif *gif, const uchar_t *buf, + int bufsize, void *Buf); +static DilloGif *Gif_new(DilloImage *Image, DilloUrl *url, int version); +static void Gif_callback(int Op, CacheClient_t *Client); + +/* exported function */ +void *a_Gif_image(const char *Type, void *Ptr, CA_Callback_t *Call, + void **Data); + + +/* + * MIME handler for "image/gif" type + * (Sets Gif_callback as cache-client) + */ +void *a_Gif_image(const char *Type, void *Ptr, CA_Callback_t *Call, + void **Data) +{ + DilloWeb *web = Ptr; + DICacheEntry *DicEntry; + + if (!web->Image) + web->Image = a_Image_new(0, 0, NULL, prefs.bg_color); + /* todo: get the backgound color from the parent widget -- Livio. */ + + /* Add an extra reference to the Image (for dicache usage) */ + a_Image_ref(web->Image); + + DicEntry = a_Dicache_get_entry(web->url); + if (!DicEntry) { + /* Let's create an entry for this image... */ + DicEntry = a_Dicache_add_entry(web->url); + + /* ... and let the decoder feed it! */ + *Data = Gif_new(web->Image, DicEntry->url, DicEntry->version); + *Call = (CA_Callback_t) Gif_callback; + } else { + /* Let's feed our client from the dicache */ + a_Dicache_ref(DicEntry->url, DicEntry->version); + *Data = web->Image; + *Call = (CA_Callback_t) a_Dicache_callback; + } + return (web->Image->dw); +} + +/* + * Create a new gif structure for decoding a gif into a RGB buffer + */ +static DilloGif *Gif_new(DilloImage *Image, DilloUrl *url, int version) +{ + DilloGif *gif = dMalloc(sizeof(DilloGif)); + + gif->Image = Image; + gif->url = url; + gif->version = version; + + gif->Flags = 0; + gif->state = 0; + gif->Start_Ofs = 0; + gif->linebuf = NULL; + gif->Background = -1; + gif->transparent = -1; + gif->num_spill_lines_max = 0; + gif->spill_lines = NULL; + gif->window = 0; + gif->packet_size = 0; + gif->ColorMap_ofs = 0; + + return gif; +} + +/* + * This function is a cache client, it receives data from the cache + * and dispatches it to the appropriate gif-processing functions + */ +static void Gif_callback(int Op, CacheClient_t *Client) +{ + if (Op) + Gif_close(Client->CbData, Client); + else + Gif_write(Client->CbData, Client->Buf, Client->BufSize); +} + +/* + * Receive and process new chunks of GIF image data + */ +static void Gif_write(DilloGif *gif, void *Buf, uint_t BufSize) +{ + uchar_t *buf; + int bufsize, bytes_consumed; + + /* Sanity checks */ + if (!Buf || !gif->Image || BufSize == 0) + return; + + buf = ((uchar_t *) Buf) + gif->Start_Ofs; + bufsize = BufSize - gif->Start_Ofs; + + DEBUG_MSG(5, "Gif_write: %u bytes\n", BufSize); + + /* Process the bytes in the input buffer. */ + bytes_consumed = Gif_process_bytes(gif, buf, bufsize, Buf); + + if (bytes_consumed < 1) + return; + gif->Start_Ofs += bytes_consumed; + + DEBUG_MSG(5, "exit Gif_write, bufsize=%ld\n", (long)bufsize); +} + +/* + * Finish the decoding process (and free the memory) + */ +static void Gif_close(DilloGif *gif, CacheClient_t *Client) +{ + int i; + + DEBUG_MSG(5, "destroy gif %p\n", gif); + + a_Dicache_close(gif->url, gif->version, Client); + + dFree(gif->linebuf); + + if (gif->spill_lines != NULL) { + for (i = 0; i < gif->num_spill_lines_max; i++) + dFree(gif->spill_lines[i]); + dFree(gif->spill_lines); + } + dFree(gif); +} + + +/* --- GIF Extensions ----------------------------------------------------- */ + +/* + * This reads a sequence of GIF data blocks.. and ignores them! + * Buf points to the first data block. + * + * Return Value + * 0 = There wasn't enough bytes read yet to read the whole datablock + * otherwise the size of the data blocks + */ +static inline size_t Gif_data_blocks(const uchar_t *Buf, size_t BSize) +{ + size_t Size = 0; + + if (BSize < 1) + return 0; + while (Buf[0]) { + if (BSize <= (size_t)(Buf[0] + 1)) + return 0; + Size += Buf[0] + 1; + BSize -= Buf[0] + 1; + Buf += Buf[0] + 1; + } + return Size + 1; +} + +/* + * This is a GIF extension. We ignore it with this routine. + * Buffer points to just after the extension label. + * + * Return Value + * 0 -- block not processed + * otherwise the size of the extension label. + */ +static inline size_t Gif_do_generic_ext(const uchar_t *Buf, size_t BSize) +{ + size_t Size = Buf[0] + 1, DSize; + + /* The Block size (the first byte) is supposed to be a specific size + * for each extension... we don't check. + */ + + if (Buf[0] > BSize) + return 0; + DSize = Gif_data_blocks(Buf + Size, BSize - Size); + if (!DSize) + return 0; + Size += DSize; + return Size <= BSize ? Size : 0; +} + +/* + * ? + */ +static inline size_t + Gif_do_gc_ext(DilloGif *gif, const uchar_t *Buf, size_t BSize) +{ + /* Graphic Control Extension */ + size_t Size = Buf[0] + 2; + uint_t Flags; + + if (Size > BSize) + return 0; + Buf++; + Flags = Buf[0]; + + /* The packed fields */ +#if 0 + gif->disposal = (Buf[0] >> 2) & 0x7; + gif->inputFlag = (Buf[0] >> 1) & 0x1; + + /* Delay time */ + gif->delayTime = LM_to_uint(Buf[1], Buf[2]); +#endif + + /* Transparent color index, may not be valid (unless flag is set) */ + if ((Flags & 0x1)) { + gif->transparent = Buf[3]; + } + return Size; +} + +#define App_Ext (0xff) +#define Cmt_Ext (0xfe) +#define GC_Ext (0xf9) +#define Txt_Ext (0x01) + +/* + * ? + * Return value: + * TRUE when the extension is over + */ +static size_t Gif_do_extension(DilloGif *gif, uint_t Label, + const uchar_t *buf, + size_t BSize) +{ + switch (Label) { + case GC_Ext: /* Graphics extension */ + return Gif_do_gc_ext(gif, buf, BSize); + + case Cmt_Ext: /* Comment extension */ + return Gif_data_blocks(buf, BSize); + + case Txt_Ext: /* Plain text Extension */ + /* This extension allows (rcm thinks) the image to be rendered as text. + */ + case App_Ext: /* Application Extension */ + default: + return Gif_do_generic_ext(buf, BSize); /*Ignore Extension */ + } +} + +/* --- General Image Decoder ----------------------------------------------- */ +/* Here begins the new push-oriented decoder. */ + +/* + * ? + */ +static void Gif_lwz_init(DilloGif *gif) +{ + gif->num_spill_lines_max = 1; + gif->spill_lines = dMalloc(sizeof(uchar_t *) * gif->num_spill_lines_max); + + gif->spill_lines[0] = dMalloc(gif->Width); + gif->bits_in_window = 0; + + /* First code in table = clear_code +1 + * Last code in table = first code in table + * clear_code = (1<< input code size) + */ + gif->last_code = (1 << gif->input_code_size) + 1; + memset(gif->code_and_byte, 0, + (1 + gif->last_code) * sizeof(gif->code_and_byte[0])); + gif->code_size = gif->input_code_size + 1; + gif->line_index = 0; +} + +/* + * Send the image line to the dicache, also handling the interlacing. + */ +static void Gif_emit_line(DilloGif *gif, const uchar_t *linebuf) +{ + a_Dicache_write(gif->Image, gif->url, gif->version, linebuf, 0, gif->y); + if (gif->Flags & INTERLACE) { + switch (gif->pass) { + case 0: + case 1: + gif->y += 8; + break; + case 2: + gif->y += 4; + break; + case 3: + gif->y += 2; + break; + } + if (gif->y >= gif->Height) { + gif->pass++; + switch (gif->pass) { + case 1: + gif->y = 4; + break; + case 2: + gif->y = 2; + break; + case 3: + gif->y = 1; + break; + default: + /* arriving here is an error in the input image. */ + gif->y = 0; + break; + } + } + } else { + if (gif->y < gif->Height) + gif->y++; + } +} + +/* + * I apologize for the large size of this routine and the goto error + * construct - I almost _never_ do that. I offer the excuse of + * optimizing for speed. + * + * RCM -- busted these down into smaller subroutines... still very hard to + * read. + */ + + +/* + * Decode the packetized lwz bytes + */ +static void Gif_literal(DilloGif *gif, uint_t code) +{ + gif->linebuf[gif->line_index++] = code; + if (gif->line_index >= gif->Width) { + Gif_emit_line(gif, gif->linebuf); + gif->line_index = 0; + } + gif->length[gif->last_code + 1] = 2; + gif->code_and_byte[gif->last_code + 1] = (code << 8); + gif->code_and_byte[gif->last_code] |= code; +} + +/* + * ? + */ +/* Profiling reveals over half the GIF time is spent here: */ +static void Gif_sequence(DilloGif *gif, uint_t code) +{ + uint_t o_index, o_size, orig_code; + uint_t sequence_length = gif->length[code]; + uint_t line_index = gif->line_index; + int num_spill_lines; + int spill_line_index = gif->spill_line_index; + uchar_t *last_byte_ptr, *obuf; + + gif->length[gif->last_code + 1] = sequence_length + 1; + gif->code_and_byte[gif->last_code + 1] = (code << 8); + + /* We're going to traverse the sequence backwards. Thus, + * we need to allocate spill lines if the sequence won't + * fit entirely within the present scan line. */ + if (line_index + sequence_length <= gif->Width) { + num_spill_lines = 0; + obuf = gif->linebuf; + o_index = line_index + sequence_length; + o_size = sequence_length - 1; + } else { + num_spill_lines = (line_index + sequence_length - 1) / + gif->Width; + o_index = (line_index + sequence_length - 1) % gif->Width + 1; + if (num_spill_lines > gif->num_spill_lines_max) { + /* Allocate more spill lines. */ + spill_line_index = gif->num_spill_lines_max; + gif->num_spill_lines_max = num_spill_lines << 1; + gif->spill_lines = dRealloc(gif->spill_lines, + gif->num_spill_lines_max * + sizeof(uchar_t *)); + + for (; spill_line_index < gif->num_spill_lines_max; + spill_line_index++) + gif->spill_lines[spill_line_index] = + dMalloc(gif->Width); + } + spill_line_index = num_spill_lines - 1; + obuf = gif->spill_lines[spill_line_index]; + o_size = o_index; + } + gif->line_index = o_index; /* for afterwards */ + + /* for fixing up later if last_code == code */ + orig_code = code; + last_byte_ptr = obuf + o_index - 1; + + /* spill lines are allocated, and we are clear to + * write. This loop does not write the first byte of + * the sequence, however (last byte traversed). */ + while (sequence_length > 1) { + sequence_length -= o_size; + /* Write o_size bytes to + * obuf[o_index - o_size..o_index). */ + for (; o_size > 0 && o_index > 0; o_size--) { + uint_t code_and_byte = gif->code_and_byte[code]; + + DEBUG_MSG(5, "%d ", gif->code_and_byte[code] & 255); + + obuf[--o_index] = code_and_byte & 255; + code = code_and_byte >> 8; + } + /* Prepare for writing to next line. */ + if (o_index == 0) { + if (spill_line_index > 0) { + spill_line_index--; + obuf = gif->spill_lines[spill_line_index]; + o_size = gif->Width; + } else { + obuf = gif->linebuf; + o_size = sequence_length - 1; + } + o_index = gif->Width; + } + } + /* Ok, now we write the first byte of the sequence. */ + /* We are sure that the code is literal. */ + DEBUG_MSG(5, "%d", code); + obuf[--o_index] = code; + gif->code_and_byte[gif->last_code] |= code; + + /* Fix up the output if the original code was last_code. */ + if (orig_code == gif->last_code) { + *last_byte_ptr = code; + DEBUG_MSG(5, " fixed (%d)!", code); + } + DEBUG_MSG(5, "\n"); + + /* Output any full lines. */ + if (gif->line_index >= gif->Width) { + Gif_emit_line(gif, gif->linebuf); + gif->line_index = 0; + } + if (num_spill_lines) { + if (gif->line_index) + Gif_emit_line(gif, gif->linebuf); + for (spill_line_index = 0; + spill_line_index < num_spill_lines - (gif->line_index ? 1 : 0); + spill_line_index++) + Gif_emit_line(gif, gif->spill_lines[spill_line_index]); + } + if (num_spill_lines) { + /* Swap the last spill line with the gif line, using + * linebuf as the swap temporary. */ + uchar_t *linebuf = gif->spill_lines[num_spill_lines - 1]; + + gif->spill_lines[num_spill_lines - 1] = gif->linebuf; + gif->linebuf = linebuf; + } + gif->spill_line_index = spill_line_index; +} + +/* + * ? + * + * Return Value: + * 2 -- quit + * 1 -- new last code needs to be done + * 0 -- okay, but reset the code table + * < 0 on error + * -1 if the decompression code was not in the lookup table + */ +static int Gif_process_code(DilloGif *gif, uint_t code, uint_t clear_code) +{ + + /* A short table describing what to do with the code: + * code < clear_code : This is uncompressed, raw data + * code== clear_code : Reset the decompression table + * code== clear_code+1: End of data stream + * code > clear_code+1: Compressed code; look up in table + */ + if (code < clear_code) { + /* a literal code. */ + DEBUG_MSG(5, "literal\n"); + Gif_literal(gif, code); + return 1; + } else if (code >= clear_code + 2) { + /* a sequence code. */ + if (code > gif->last_code) + return -1; + Gif_sequence(gif, code); + return 1; + } else if (code == clear_code) { + /* clear code. Resets the whole table */ + DEBUG_MSG(5, "clear\n"); + return 0; + } else { + /* end code. */ + DEBUG_MSG(5, "end\n"); + return 2; + } +} + +/* + * ? + */ +static int Gif_decode(DilloGif *gif, const uchar_t *buf, size_t bsize) +{ + /* + * Data block processing. The image stuff is a series of data blocks. + * Each data block is 1 to 256 bytes long. The first byte is the length + * of the data block. 0 == the last data block. + */ + size_t bufsize, packet_size; + uint_t clear_code; + uint_t window; + int bits_in_window; + uint_t code; + int code_size; + uint_t code_mask; + + bufsize = bsize; + + /* Want to get all inner loop state into local variables. */ + packet_size = gif->packet_size; + window = gif->window; + bits_in_window = gif->bits_in_window; + code_size = gif->code_size; + code_mask = (1 << code_size) - 1; + clear_code = 1 << gif->input_code_size; + + /* If packet size == 0, we are at the start of a data block. + * The first byte of the data block indicates how big it is (0 == last + * datablock) + * packet size is set to this size; it indicates how much of the data block + * we have left to process. + */ + while (bufsize > 0) { + /* lwz_bytes is the number of remaining lwz bytes in the packet. */ + int lwz_bytes = MIN(packet_size, bufsize); + + bufsize -= lwz_bytes; + packet_size -= lwz_bytes; + for (; lwz_bytes > 0; lwz_bytes--) { + /* printf ("%d ", *buf) would print the depacketized lwz stream. */ + + /* Push the byte onto the "end" of the window (MSB). The low order + * bits always come first in the LZW stream. */ + window = (window >> 8) | (*buf++ << 24); + bits_in_window += 8; + + while (bits_in_window >= code_size) { + /* Extract the code. The code is code_size (3 to 12) bits long, + * at the start of the window */ + code = (window >> (32 - bits_in_window)) & code_mask; + + DEBUG_MSG(5, "code = %d, ", code); + + bits_in_window -= code_size; + switch (Gif_process_code(gif, code, clear_code)) { + case 1: /* Increment last code */ + gif->last_code++; + /*gif->code_and_byte[gif->last_code+1]=0; */ + + if ((gif->last_code & code_mask) == 0) { + if (gif->last_code == (1 << MAX_LWZ_BITS)) + gif->last_code--; + else { + code_size++; + code_mask = (1 << code_size) - 1; + } + } + break; + + case 0: /* Reset codes size and mask */ + gif->last_code = clear_code + 1; + code_size = gif->input_code_size + 1; + code_mask = (1 << code_size) - 1; + break; + + case 2: /* End code... consume remaining data chunks..? */ + goto error; /* Could clean up better? */ + default: + printf("dillo_gif_decode: error!\n"); + goto error; + } + } + } + + /* We reach here if + * a) We have reached the end of the data block; + * b) we ran out of data before reaching the end of the data block + */ + if (bufsize <= 0) + break; /* We are out of data; */ + + /* Start of new data block */ + bufsize--; + if (!(packet_size = *buf++)) { + /* This is the "block terminator" -- the last data block */ + gif->state = 999; /* BUG: should Go back to getting GIF blocks. */ + break; + } + } + + gif->packet_size = packet_size; + gif->window = window; + gif->bits_in_window = bits_in_window; + gif->code_size = code_size; + return bsize - bufsize; + + error: + gif->state = 999; + return bsize - bufsize; +} + +/* + * ? + */ +static int Gif_check_sig(DilloGif *gif, const uchar_t *ibuf, int ibsize) +{ + /* at beginning of file - read magic number */ + if (ibsize < 6) + return 0; + if (memcmp(ibuf, "GIF", 3) != 0) { + gif->state = 999; + return 6; + } + if (memcmp(ibuf + 3, "87a", 3) != 0 && + memcmp(ibuf + 3, "89a", 3) != 0) { + gif->state = 999; + return 6; + } + gif->state = 1; + return 6; +} + +/* Read the color map + * + * Implements, from the spec: + * Global Color Table + * Local Color Table + */ +static inline size_t + Gif_do_color_table(DilloGif *gif, void *Buf, + const uchar_t *buf, size_t bsize, size_t CT_Size) +{ + size_t Size = 3 * (1 << (1 + CT_Size)); + + if (Size > bsize) + return 0; + + gif->ColorMap_ofs = (ulong_t) buf - (ulong_t) Buf; + gif->NumColors = (1 << (1 + CT_Size)); + return Size; +} + +/* + * This implements, from the spec: + * <Logical Screen> ::= Logical Screen Descriptor [Global Color Table] + */ +static size_t Gif_get_descriptor(DilloGif *gif, void *Buf, + const uchar_t *buf, int bsize) +{ + + /* screen descriptor */ + size_t Size = 7, /* Size of descriptor */ + mysize; /* Size of color table */ + uchar_t Flags; + + if (bsize < 7) + return 0; + Flags = buf[4]; + + if (Flags & LOCALCOLORMAP) { + mysize = Gif_do_color_table( + gif, Buf, buf + 7, (size_t)bsize - 7, Flags & (size_t)0x7); + if (!mysize) + return 0; + Size += mysize; /* Size of the color table that follows */ + gif->Background = buf[5]; + } + /* gif->Width = LM_to_uint(buf[0], buf[1]); + gif->Height = LM_to_uint(buf[2], buf[3]); */ + gif->ColorResolution = (((buf[4] & 0x70) >> 3) + 1); + /* gif->AspectRatio = buf[6]; */ + + return Size; +} + +/* + * This implements, from the spec: + * <Table-Based Image> ::= Image Descriptor [Local Color Table] Image Data + * + * ('Buf' points to just after the Image separator) + * we should probably just check that the local stuff is consistent + * with the stuff at the header. For now, we punt... + */ +static size_t Gif_do_img_desc(DilloGif *gif, void *Buf, + const uchar_t *buf, size_t bsize) +{ + uchar_t Flags; + size_t Size = 9 + 1; /* image descriptor size + first byte of image data */ + + if (bsize < 10) + return 0; + + gif->Width = LM_to_uint(buf[4], buf[5]); + gif->Height = LM_to_uint(buf[6], buf[7]); + gif->linebuf = dMalloc(gif->Width); + + a_Dicache_set_parms(gif->url, gif->version, gif->Image, + gif->Width, gif->Height, DILLO_IMG_TYPE_INDEXED); + + Flags = buf[8]; + + gif->Flags |= Flags & INTERLACE; + gif->pass = 0; + bsize -= 9; + buf += 9; + if (Flags & LOCALCOLORMAP) { + size_t LSize = Gif_do_color_table( + gif, Buf, buf, bsize, Flags & (size_t)0x7); + + if (!LSize) + return 0; + Size += LSize; + buf += LSize; + bsize -= LSize; + } + /* Finally, get the first byte of the LZW image data */ + if (bsize < 1) + return 0; + gif->input_code_size = *buf++; + if (gif->input_code_size > 8) { + gif->state = 999; + return Size; + } + gif->y = 0; + Gif_lwz_init(gif); + gif->spill_line_index = 0; + gif->state = 3; /*Process the lzw data next */ + if (gif->Image && gif->ColorMap_ofs) { + a_Dicache_set_cmap(gif->url, gif->version, gif->Image, + (uchar_t *) Buf + gif->ColorMap_ofs, + gif->NumColors, 256, gif->transparent); + } + return Size; +} + +/* --- Top level data block processors ------------------------------------ */ +#define Img_Desc (0x2c) +#define Trailer (0x3B) +#define Ext_Id (0x21) + +/* + * This identifies which kind of GIF blocks are next, and processes them. + * It returns if there isn't enough data to process the next blocks, or if + * the next block is the lzw data (which is streamed differently) + * + * This implements, from the spec, <Data>* Trailer + * <Data> ::= <Graphic Block> | <Special-Purpose Block> + * <Special-Purpose Block> ::= Application Extension | Comment Extension + * <Graphic Block> ::= [Graphic Control Extension] <Graphic-Rendering Block> + * <Graphic-Rendering Block> ::= <Table-Based Image> | Plain Text Extension + * + * <Data>* --> GIF_Block + * <Data> --> while (...) + * <Special-Purpose Block> --> Gif_do_extension + * Graphic Control Extension --> Gif_do_extension + * Plain Text Extension --> Gif_do_extension + * <Table-Based Image> --> Gif_do_img_desc + * + * Return Value + * 0 if not enough data is present, otherwise the number of bytes + * "consumed" + */ +static size_t GIF_Block(DilloGif * gif, void *Buf, + const uchar_t *buf, size_t bsize) +{ + size_t Size = 0, mysize; + uchar_t C; + + if (bsize < 1) + return 0; + while (gif->state == 2) { + if (bsize < 1) + return Size; + bsize--; + switch (*buf++) { + case Ext_Id: + /* get the extension type */ + if (bsize < 2) + return Size; + + /* Have the extension block intepreted. */ + C = *buf++; + bsize--; + mysize = Gif_do_extension(gif, C, buf, bsize); + + if (!mysize) + /* Not all of the extension is there.. quit until more data + * arrives */ + return Size; + + bsize -= mysize; + buf += mysize; + + /* Increment the amount consumed by the extension introducer + * and id, and extension block size */ + Size += mysize + 2; + /* Do more GIF Blocks */ + continue; + + case Img_Desc: /* Image descriptor */ + mysize = Gif_do_img_desc(gif, Buf, buf, bsize); + if (!mysize) + return Size; + + /* Increment the amount consumed by the Image Separator and the + * Resultant blocks */ + Size += 1 + mysize; + return Size; + + case Trailer: + gif->state = 999; /* BUG: should close the rest of the file */ + return Size + 1; + break; /* GIF terminator */ + + default: /* Unknown */ + /* gripe and complain */ + MSG ("gif.c::GIF_Block: Error, 0x%x found\n", *(buf-1)); + gif->state = 999; + return Size + 1; + } + } + return Size; +} + + +/* + * Process some bytes from the input gif stream. It's a state machine. + * + * From the GIF spec: + * <GIF Data Stream> ::= Header <Logical Screen> <Data>* Trailer + * + * <GIF Data Stream> --> Gif_process_bytes + * Header --> State 0 + * <Logical Screen> --> State 1 + * <Data>* --> State 2 + * Trailer --> State > 3 + * + * State == 3 is special... this is inside of <Data> but all of the stuff in + * there has been gotten and set up. So we stream it outside. + */ +static size_t Gif_process_bytes(DilloGif *gif, const uchar_t *ibuf, + int bufsize, void *Buf) +{ + int tmp_bufsize = bufsize; + size_t mysize; + + switch (gif->state) { + case 0: + mysize = Gif_check_sig(gif, ibuf, tmp_bufsize); + if (!mysize) + break; + tmp_bufsize -= mysize; + ibuf += mysize; + if (gif->state != 1) + break; + + case 1: + mysize = Gif_get_descriptor(gif, Buf, ibuf, tmp_bufsize); + if (!mysize) + break; + tmp_bufsize -= mysize; + ibuf += mysize; + gif->state = 2; + + case 2: + /* Ok, this loop construction looks weird. It implements the <Data>* of + * the GIF grammar. All sorts of stuff is allocated to set up for the + * decode part (state ==2) and then there is the actual decode part (3) + */ + mysize = GIF_Block(gif, Buf, ibuf, (size_t)tmp_bufsize); + if (!mysize) + break; + tmp_bufsize -= mysize; + ibuf += mysize; + if (gif->state != 3) + break; + + case 3: + /* get an image byte */ + /* The users sees all of this stuff */ + mysize = Gif_decode(gif, ibuf, (size_t)tmp_bufsize); + if (mysize == 0) + break; + ibuf += mysize; + tmp_bufsize -= mysize; + + default: + /* error - just consume all input */ + tmp_bufsize = 0; + break; + } + + DEBUG_MSG(5, "Gif_process_bytes: final state %d, %ld bytes consumed\n", + gif->state, (long)(bufsize - tmp_bufsize)); + + return bufsize - tmp_bufsize; +} + +#endif /* ENABLE_GIF */ diff --git a/src/history.c b/src/history.c new file mode 100644 index 00000000..7819e58e --- /dev/null +++ b/src/history.c @@ -0,0 +1,125 @@ +/* + * File: history.c + * + * Copyright (C) 2001, 2002 Jorge Arellano Cid <jcid@dillo.org> + * + * 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 3 of the License, or + * (at your option) any later version. + */ + +/* + * Linear history (it also provides indexes for the navigation stack) + */ + +#include "list.h" +#include "history.h" + + +typedef struct { + DilloUrl *url; + char *title; +} H_Item; + + +/* Global history list */ +static H_Item *history = NULL; +static int history_size = 0; /* [1 based] */ +static int history_size_max = 16; + + +/* + * Add a new H_Item at the end of the history list + * (taking care of not making a duplicate entry) + */ +int a_History_add_url(DilloUrl *url) +{ + int i, idx; + + for (i = 0; i < history_size; ++i) + if (a_Url_cmp(history[i].url, url) == 0) + return i; + + idx = history_size; + a_List_add(history, history_size, history_size_max); + history[idx].url = a_Url_dup(url); + history[idx].title = NULL; + ++history_size; + return idx; +} + +/* + * Set the page-title for a given URL (by idx) + * (this is known when the first chunks of HTML data arrive) + */ +int a_History_set_title(int idx, const char *title) +{ + dReturn_val_if_fail(idx >= 0 && idx < history_size, 0); + + dFree(history[idx].title); + history[idx].title = dStrdup(title); + return 1; +} + +/* + * Return the DilloUrl camp (by index) + */ +DilloUrl *a_History_get_url(int idx) +{ + dReturn_val_if_fail(idx >= 0 && idx < history_size, NULL); + + return history[idx].url; +} + +/* + * Return the title camp (by index) + * ('force' returns URL_STR when there's no title) + */ +const char *a_History_get_title(int idx, int force) +{ + dReturn_val_if_fail(idx >= 0 && idx < history_size, NULL); + + if (history[idx].title) + return history[idx].title; + else if (force) + return URL_STR(history[idx].url); + else + return NULL; +} + +/* + * Return the title camp (by url) + * ('force' returns URL_STR when there's no title) + */ +const char *a_History_get_title_by_url(DilloUrl *url, int force) +{ + int i; + + dReturn_val_if_fail(url != NULL, NULL); + + for (i = 0; i < history_size; ++i) + if (a_Url_cmp(url, history[i].url) == 0) + break; + + if (i < history_size && history[i].title) + return history[i].title; + else if (force) + return URL_STR_(url); + return NULL; +} + + +/* + * Free all the memory used by this module + */ +void a_History_free() +{ + int i; + + for (i = 0; i < history_size; ++i) { + a_Url_free(history[i].url); + dFree(history[i].title); + } + dFree(history); +} diff --git a/src/history.h b/src/history.h new file mode 100644 index 00000000..a6f9f13f --- /dev/null +++ b/src/history.h @@ -0,0 +1,24 @@ + +#ifndef __DILLO_HISTORY_H__ +#define __DILLO_HISTORY_H__ + +#include "url.h" + + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +int a_History_add_url(DilloUrl *url); +int a_History_set_title(int idx, const char *title); +DilloUrl *a_History_get_url(int idx); +const char *a_History_get_title(int idx, int force); +const char *a_History_get_title_by_url(DilloUrl *url, int force); +void a_History_free(void); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __DILLO_HISTORY_H__ */ diff --git a/src/html.cc b/src/html.cc new file mode 100644 index 00000000..4661525a --- /dev/null +++ b/src/html.cc @@ -0,0 +1,5123 @@ +/* + * File: html.cc + * + * Copyright (C) 2005 Jorge Arellano Cid <jcid@dillo.org> + * + * 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 3 of the License, or + * (at your option) any later version. + */ + +/* + * Dillo HTML parsing routines + */ + +/* Undefine if you want to unroll tables. For instance for PDAs */ +#define USE_TABLES + +/* Define to 1 to ignore white space immediately after an open tag, + * and immediately before a close tag. */ +#define SGML_SPCDEL 0 + + +#include <ctype.h> /* for isspace and tolower */ +#include <string.h> /* for memcpy and memmove */ +#include <stdlib.h> +#include <stdio.h> /* for sprintf */ +#include <math.h> /* for rint */ +#include <errno.h> + +#include <fltk/utf.h> /* for utf8encode */ + +#define DEBUG_LEVEL 10 +#include "debug.h" + +#include "msg.h" +#include "binaryconst.h" +#include "colors.h" + +#include "uicmd.hh" + +#define dillo_dbg_rendering 0 + +#include "history.h" +#include "nav.h" +#include "menu.hh" +#include "prefs.h" +#include "misc.h" +#include "capi.h" + +#include "html.hh" +#include "dw/textblock.hh" +#include "dw/bullet.hh" +#include "dw/table.hh" +#include "dw/tablecell.hh" +#include "dw/listitem.hh" +#include "dw/image.hh" +#include "dw/ruler.hh" + + +using namespace dw; +using namespace dw::core; +using namespace dw::core::ui; +using namespace dw::core::style; + +typedef void (*TagOpenFunct) (DilloHtml *Html, char *Tag, int Tagsize); +typedef void (*TagCloseFunct) (DilloHtml *Html, int TagIdx); + +#define TAB_SIZE 8 + +// Dw to Textblock +#define DW2TB(dw) ((Textblock*)dw) +// "html struct" to "Layout" +#define HT2LT(html) ((Layout*)html->bw->render_layout) +// "Image" to "Dw Widget" +#define IM2DW(Image) ((Widget*)Image->dw) +// Top of the parsing stack +#define S_TOP(html) (html->stack->getRef(html->stack->size()-1)) + +/* + * Exported function with C linkage. + */ +extern "C" { +void *a_Html_text(const char *type, void *P, CA_Callback_t *Call,void **Data); +} + +/* + * Forward declarations + */ +static const char *Html_get_attr(DilloHtml *html, + const char *tag, + int tagsize, + const char *attrname); +static const char *Html_get_attr2(DilloHtml *html, + const char *tag, + int tagsize, + const char *attrname, + int tag_parsing_flags); +static char *Html_get_attr_wdef(DilloHtml *html, + const char *tag, + int tagsize, + const char *attrname, + const char *def); +static void Html_add_widget(DilloHtml *html, Widget *widget, + char *width_str, char *height_str, + StyleAttrs *style_attrs); +static int Html_write_raw(DilloHtml *html, char *buf, int bufsize, int Eof); +static void Html_write(DilloHtml *html, char *Buf, int BufSize, int Eof); +static void Html_close(DilloHtml *html, int ClientKey); +static void Html_callback(int Op, CacheClient_t *Client); +static DilloHtml *Html_new(BrowserWindow *bw, const DilloUrl *url); +static void Html_tag_open_input(DilloHtml *html, char *tag, int tagsize); +static void Html_add_input(DilloHtmlForm *form, + DilloHtmlInputType type, + Widget *widget, + Embed *embed, + const char *name, + const char *init_str, + DilloHtmlSelect *select, + bool_t init_val); +//static void Html_reset_form(GtkWidget *reset, DilloHtmlLB *html_lb); +static int Html_tag_index(const char *tag); + + +/* + * Local Data + */ + +/* The following array of font sizes has to be _strictly_ crescent */ +static const int FontSizes[] = {8, 10, 12, 14, 18, 24}; +static const int FontSizesNum = 6; +static const int FontSizesBase = 2; + +/* Parsing table structure */ +typedef struct { + char *name; /* element name */ + unsigned char Flags; /* flags (explained near the table data) */ + char EndTag; /* Is it Required, Optional or Forbidden */ + uchar_t TagLevel; /* Used to heuristically parse bad HTML */ + TagOpenFunct open; /* Open function */ + TagCloseFunct close; /* Close function */ +} TagInfo; +extern const TagInfo Tags[]; + +/* + * Return the line number of the tag being processed by the parser. + */ +static int Html_get_line_number(DilloHtml *html) +{ + int i, ofs, line; + const char *p = html->Start_Buf; + + dReturn_val_if_fail(p != NULL, -1); + + ofs = html->CurrTagOfs; + line = html->OldTagLine; + for (i = html->OldTagOfs; i < ofs; ++i) + if (p[i] == '\n') + ++line; + html->OldTagOfs = html->CurrTagOfs; + html->OldTagLine = line; + return line; +} + +/* + * Collect HTML error strings inside the linkblock. + */ +static void Html_msg(DilloHtml *html, const char *format, ... ) +{ + va_list argp; + + dStr_sprintfa(html->bw->page_bugs, + "HTML warning: line %d, ", + Html_get_line_number(html)); + va_start(argp, format); + dStr_vsprintfa(html->bw->page_bugs, format, argp); + va_end(argp); + a_UIcmd_set_bug_prog(html->bw, ++html->bw->num_page_bugs); +} + +/* + * Wrapper for a_Url_new that adds an error detection message. + * (if use_base_url is TRUE, html->linkblock->base_url is used) + */ +static DilloUrl *Html_url_new(DilloHtml *html, + const char *url_str, const char *base_url, + int flags, int32_t posx, int32_t posy, + int use_base_url) +{ + DilloUrl *url; + int n_ic, n_ic_spc; + + url = a_Url_new( + url_str, + (use_base_url) ? base_url : URL_STR_(html->linkblock->base_url), + flags, posx, posy); + if ((n_ic = URL_ILLEGAL_CHARS(url)) != 0) { + const char *suffix = (n_ic) > 1 ? "s" : ""; + n_ic_spc = URL_ILLEGAL_CHARS_SPC(url); + if (n_ic == n_ic_spc) { + MSG_HTML("URL has %d illegal character%s [%d space%s]\n", + n_ic, suffix, n_ic_spc, suffix); + } else if (n_ic_spc == 0) { + MSG_HTML("URL has %d illegal character%s [%d in (00-1F or 7F)]\n", + n_ic, suffix, n_ic); + } else { + MSG_HTML("URL has %d illegal character%s " + "[%d space%s and %d in (00-1F or 7F)]\n", + n_ic, suffix, n_ic_spc, n_ic_spc ? "s" : "", n_ic-n_ic_spc); + } + } + return url; +} + +/* + * Set callback function and callback data for "html/text" MIME type. + */ +void *a_Html_text(const char *Type, void *P, CA_Callback_t *Call, void **Data) +{ + DilloWeb *web = (DilloWeb*)P; + DilloHtml *html = Html_new(web->bw, web->url); + + *Data = (void *) html; + *Call = (CA_Callback_t) Html_callback; + + return (void*) html->dw; +} + +bool DilloHtmlLB::HtmlLinkReceiver::enter (Widget *widget, int link, + int x, int y) +{ + BrowserWindow *bw = this->lb->bw; + + MSG(" ** "); + if (link == -1 && x == -1 && y == -1) { + _MSG(" Link LEAVE notify...\n"); + a_UIcmd_set_msg(bw, ""); + } else { + _MSG(" Link ENTER notify...\n"); + a_UIcmd_set_msg(bw, "%s", URL_STR(this->lb->links->get(link))); + } + return true; +} + +bool DilloHtmlLB::HtmlLinkReceiver::press (Widget *widget, int link, + int x, int y, EventButton *event) +{ + int ret = false; + + _MSG("pressed button %d\n", event->button); + if (event->button == 3) { + a_UIcmd_page_popup(lb->bw, lb->links->get(link), + lb->bw->num_page_bugs ? lb->bw->page_bugs->str:NULL); + //a_UIcmd_link_popup(lb->bw, lb->links->get(link)); + //a_UIcmd_bugmeter_popup(lb->bw); + ret = true; + } + return ret; +} + +bool DilloHtmlLB::HtmlLinkReceiver::click (Widget *widget, int link, + int x, int y, EventButton *event) +{ + DilloUrl *url = lb->links->get(link); + _MSG("clicked on URL %d: %s\n", link, a_Url_str (url)); + + + if (x != -1) { + char data[64]; + snprintf(data, 64, "?%d,%d", x, y); + a_Url_set_ismap_coords(url, data); + } + + if (event->button == 1) { + a_Nav_push(lb->bw, url); + } else if (event->button == 2) { + a_Nav_push_nw(lb->bw, url); + } else { + return false; + } + + /* Change the link color to "visited" as visual feedback */ + for (Widget *w = widget; w; w = w->getParent()) { + _MSG(" ->%s\n", w->getClassName()); + if (w->instanceOf(dw::Textblock::CLASS_ID)) { + ((Textblock*)w)->changeLinkColor (link, lb->visited_color); + break; + } + } + + return true; +} + + +/* + * We'll make the linkblock first to get it out of the way. + */ +static DilloHtmlLB *Html_lb_new(BrowserWindow *bw, const DilloUrl *url) +{ + DilloHtmlLB *html_lb = dNew(DilloHtmlLB, 1); + + html_lb->bw = bw; + html_lb->base_url = a_Url_dup(url); + html_lb->linkReceiver = new DilloHtmlLB::HtmlLinkReceiver (html_lb); + + html_lb->forms = new misc::SimpleVector <DilloHtmlForm> (1); + + html_lb->links = new misc::SimpleVector <DilloUrl*> (64); + + //a_Dw_image_map_list_init(&html_lb->maps); + + html_lb->link_color = prefs.link_color; + html_lb->visited_color = prefs.visited_color; + + return html_lb; +} + +/* + * Free the memory used by the linkblock + */ +static void Html_lb_free(void *lb) +{ + int i, j, k; + DilloHtmlForm *form; + DilloHtmlInput *input_j; + DilloHtmlLB *html_lb = (DilloHtmlLB*)lb; + + DEBUG_MSG(3, "Html_lb_free\n"); + + delete html_lb->linkReceiver; + a_Url_free(html_lb->base_url); + + for (i = 0; i < html_lb->forms->size(); i++) { + form = html_lb->forms->getRef(i); + a_Url_free(form->action); + for (j = 0; j < form->inputs->size(); j++) { + input_j = form->inputs->getRef(j); + dFree(input_j->name); + dFree(input_j->init_str); + + if (input_j->type == DILLO_HTML_INPUT_SELECT || + input_j->type == DILLO_HTML_INPUT_SEL_LIST) { + for (k = 0; k < input_j->select->num_options; k++) { + dFree(input_j->select->options[k].value); + } + dFree(input_j->select->options); + dFree(input_j->select); + } + } + delete(form->inputs); + delete(form->form_receiver); + } + delete(html_lb->forms); + + for (i = 0; i < html_lb->links->size(); i++) + if (html_lb->links->get(i)) + a_Url_free(html_lb->links->get(i)); + delete (html_lb->links); + + //a_Dw_image_map_list_free(&html_lb->maps); + + dFree(html_lb); +} + + +/* + * Set the URL data for image maps. + */ +//static void Html_set_link_coordinates(DilloHtmlLB *lb, +// int link, int x, int y) +//{ +// char data[64]; +// +// if (x != -1) { +// snprintf(data, 64, "?%d,%d", x, y); +// a_Url_set_ismap_coords(lb->links->get(link), data); +// } +//} + +///* +// * Handle the status function generated by the dw scroller, +// * and show the url in the browser status-bar. +// */ +//static void Html_handle_status(Widget *widget, int link, int x, int y, +// DilloHtmlLB *lb) +//{ +// DilloUrl *url; +// +// url = (link == -1) ? NULL : lb->links->get(link); +// if (url) { +// Html_set_link_coordinates(lb, link, x, y); +// a_UIcmd_set_msg(lb->bw, "%s", +// URL_ALT_(url) ? URL_ALT_(url) : URL_STR_(url)); +// lb->bw->status_is_link = 1; +// +// } else { +// if (lb->bw->status_is_link) +// a_UIcmd_set_msg(lb->bw, ""); +// } +//} + +///* +// * Activate a link ("link_clicked" callback of the page) +// */ +//static bool_t Html_link_clicked(Widget *widget, int link, int x, int y, +// EventButton *event, DilloHtmlLB *lb) +//{ +// Html_set_link_coordinates(lb, link, x, y); +// if (event->button == 1) +// a_Nav_push(lb->bw, lb->links->get(link)); +// else if (event->button == 2) { +// a_Nav_push_nw(lb->bw, lb->links->get(link)); +// } else { +// return FALSE; +// } +// +// if (widget->instanceOf (Textblock::CLASS_ID)) +// ((Textblock*)widget)->changeLinkColor (link, lb->visited_color); +// +// return TRUE; +//} + +/* + * Popup the image menu ("button_press_event" callback of image) + */ +static bool_t Html_image_menu(Widget *widget, + EventButton *event, + BrowserWindow *bw) +{ +// Image *image = (Image*)widget; +//:AL +// if (event->button == 3 && image->url) { +// a_Menu_popup_set_url(bw, image->url); +// a_Menu_popup_clear_url2(bw->menu_popup.over_image); +// +// gtk_menu_popup(GTK_MENU(bw->menu_popup.over_image), NULL, NULL, +// NULL, NULL, event->button, ((DwMouseEvent*)event)->time); +// return TRUE; +// } + + return FALSE; +} + +/* + * Connect all signals of a textblock or an image. + */ +static void Html_connect_signals(DilloHtml *html, Widget *widget) +{ + widget->connectLink (html->linkblock->linkReceiver); +} + + +/* + * Create a new link in the linkblock, set it as the url's parent + * and return the index. + */ +static int Html_set_new_link(DilloHtml *html, DilloUrl **url) +{ + int nl = html->linkblock->links->size(); + html->linkblock->links->increase(); + html->linkblock->links->set(nl, (*url) ? *url : NULL); + return nl; +} + + +/* + * Allocate and insert form information into the Html linkblock + */ +static int Html_form_new(DilloHtmlLB *html_lb, + DilloHtmlMethod method, + const DilloUrl *action, + DilloHtmlEnc enc) +{ + DilloHtmlForm *form; + + html_lb->forms->increase(); + form = html_lb->forms->getRef (html_lb->forms->size() - 1); + form->method = method; + form->action = a_Url_dup(action); + form->enc = enc; + form->inputs = new misc::SimpleVector <DilloHtmlInput> (4); + form->num_entry_fields = 0; + form->num_submit_buttons = 0; + form->form_receiver = new form::Form(html_lb); + + _MSG("Html_form_new: action=%s nform=%d\n", action, nf); + return html_lb->forms->size(); +} + + +/* + * Change one toplevel attribute. var should be an identifier. val is + * only evaluated once, so you can safely use a function call for it. + */ +#define HTML_SET_TOP_ATTR(html, var, val) \ + do { \ + StyleAttrs style_attrs; \ + Style *old_style; \ + \ + old_style = S_TOP(html)->style; \ + style_attrs = *old_style; \ + style_attrs.var = (val); \ + S_TOP(html)->style = \ + Style::create (HT2LT(html), &style_attrs); \ + old_style->unref (); \ + } while (FALSE) + + + +/* + * Set the font at the top of the stack. BImask specifies which + * attributes in BI should be changed. + */ +static void Html_set_top_font(DilloHtml *html, char *name, int size, + int BI, int BImask) +{ + FontAttrs font_attrs; + + font_attrs = *S_TOP(html)->style->font; + if (name) + font_attrs.name = name; + if (size) + font_attrs.size = size; + if (BImask & 1) + font_attrs.weight = (BI & 1) ? 700 : 400; + if (BImask & 2) + font_attrs.style = (BI & 2) ? + (prefs.use_oblique ? + FONT_STYLE_OBLIQUE : FONT_STYLE_ITALIC) : + FONT_STYLE_NORMAL; + + HTML_SET_TOP_ATTR (html, font, + Font::create (HT2LT(html), &font_attrs)); +} + +/* + * Evaluates the ALIGN attribute (left|center|right|justify) and + * sets the style at the top of the stack. + */ +static void Html_tag_set_align_attr(DilloHtml *html, char *tag, int tagsize) +{ + const char *align, *charattr; + + if ((align = Html_get_attr(html, tag, tagsize, "align"))) { + if (dStrcasecmp (align, "left") == 0) + HTML_SET_TOP_ATTR (html, textAlign, TEXT_ALIGN_LEFT); + else if (dStrcasecmp (align, "right") == 0) + HTML_SET_TOP_ATTR (html, textAlign, TEXT_ALIGN_RIGHT); + else if (dStrcasecmp (align, "center") == 0) + HTML_SET_TOP_ATTR (html, textAlign, TEXT_ALIGN_CENTER); + else if (dStrcasecmp (align, "justify") == 0) + HTML_SET_TOP_ATTR (html, textAlign, TEXT_ALIGN_JUSTIFY); + else if (dStrcasecmp (align, "char") == 0) { + /* todo: Actually not supported for <p> etc. */ + HTML_SET_TOP_ATTR (html, textAlign, TEXT_ALIGN_STRING); + if ((charattr = Html_get_attr(html, tag, tagsize, "char"))) { + if (charattr[0] == 0) + /* todo: ALIGN=" ", and even ALIGN="&32;" will reult in + * an empty string (don't know whether the latter is + * correct, has to be clarified with the specs), so + * that for empty strings, " " is assumed. */ + HTML_SET_TOP_ATTR (html, textAlignChar, ' '); + else + HTML_SET_TOP_ATTR (html, textAlignChar, charattr[0]); + } else + /* todo: Examine LANG attr of <html>. */ + HTML_SET_TOP_ATTR (html, textAlignChar, '.'); + } + } +} + +/* + * Evaluates the VALIGN attribute (top|bottom|middle|baseline) and + * sets the style in style_attrs. Returns TRUE when set. + */ +static bool_t Html_tag_set_valign_attr(DilloHtml *html, char *tag, + int tagsize, StyleAttrs *style_attrs) +{ + const char *attr; + + if ((attr = Html_get_attr(html, tag, tagsize, "valign"))) { + if (dStrcasecmp (attr, "top") == 0) + style_attrs->valign = VALIGN_TOP; + else if (dStrcasecmp (attr, "bottom") == 0) + style_attrs->valign = VALIGN_BOTTOM; + else if (dStrcasecmp (attr, "baseline") == 0) + style_attrs->valign = VALIGN_BASELINE; + else + style_attrs->valign = VALIGN_MIDDLE; + return TRUE; + } else + return FALSE; +} + + +/* + * Add a new DwPage into the current DwPage, for indentation. + * left and right are the horizontal indentation amounts, space is the + * vertical space around the block. + */ +static void Html_add_indented_widget(DilloHtml *html, Widget *textblock, + int left, int right, int space) +{ + StyleAttrs style_attrs; + Style *style; + + style_attrs = *S_TOP(html)->style; + + style_attrs.margin.setVal (0); + style_attrs.borderWidth.setVal (0); + style_attrs.padding.setVal(0); + + /* Activate this for debugging */ +#if 0 + style_attrs.borderWidth.setVal (1); + style_attrs.setBorderColor ( + Color::createShaded (HT2LT(html), style_attrs.color->getColor()); + style_attrs.setBorderStyle (BORDER_DASHED); +#endif + + style_attrs.margin.left = left; + style_attrs.margin.right = right; + style = Style::create (HT2LT(html), &style_attrs); + + DW2TB(html->dw)->addParbreak (space, style); + DW2TB(html->dw)->addWidget (textblock, style); + DW2TB(html->dw)->addParbreak (space, style); + S_TOP(html)->textblock = html->dw = textblock; + S_TOP(html)->hand_over_break = TRUE; + style->unref (); + + /* Handle it when the user clicks on a link */ + Html_connect_signals(html, textblock); +} + +/* + * Create and add a new indented DwPage to the current DwPage + */ +static void Html_add_indented(DilloHtml *html, int left, int right, int space) +{ + Textblock *textblock = new Textblock (false); + Html_add_indented_widget (html, textblock, left, right, space); +} + +/* + * Given a font_size, this will return the correct 'level'. + * (or the closest, if the exact level isn't found). + */ +static int Html_fontsize_to_level(int fontsize) +{ + int i, level; + double normalized_size = fontsize / prefs.font_factor, + approximation = FontSizes[FontSizesNum-1] + 1; + + for (i = level = 0; i < FontSizesNum; i++) + if (approximation >= fabs(normalized_size - FontSizes[i])) { + approximation = fabs(normalized_size - FontSizes[i]); + level = i; + } else { + break; + } + + return level; +} + +/* + * Given a level of a font, this will return the correct 'size'. + */ +static int Html_level_to_fontsize(int level) +{ + level = MAX(0, level); + level = MIN(FontSizesNum - 1, level); + + return (int)rint(FontSizes[level]*prefs.font_factor); +} + +/* + * Miscelaneous initializations for a DwPage + */ +static void Html_set_dwpage(DilloHtml *html) +{ + Widget *widget; + Textblock *textblock; + StyleAttrs style_attrs; + FontAttrs font_attrs; + + dReturn_if_fail (html->dw == NULL); + + widget = textblock = new Textblock (false); + html->dw = html->stack->getRef(0)->textblock = widget; + + /* Create a dummy font, attribute, and tag for the bottom of the stack. */ + font_attrs.name = prefs.vw_fontname; /* Helvetica */ + font_attrs.size = Html_level_to_fontsize(FontSizesBase); + font_attrs.weight = 400; + font_attrs.style = FONT_STYLE_NORMAL; + + style_attrs.initValues (); + style_attrs.font = Font::create (HT2LT(html), &font_attrs); + style_attrs.color = Color::createSimple (HT2LT(html), prefs.text_color); + html->stack->getRef(0)->style = Style::create (HT2LT(html), &style_attrs); + + html->stack->getRef(0)->table_cell_style = NULL; + + /* Handle it when the user clicks on a link */ + Html_connect_signals(html, widget); + + html->bw->num_page_bugs = 0; + dStr_truncate(html->bw->page_bugs, 0); + +// gtk_signal_connect_while_alive ( +// GTK_OBJECT(GTK_BIN(html->bw->render_main_scroll)->child), +// "button_press_event", GTK_SIGNAL_FUNC(Html_page_menu), +// html->bw, GTK_OBJECT (page)); +// +// /* Destroy the linkblock when the DwPage is destroyed */ +// gtk_signal_connect_object(GTK_OBJECT(page), "destroy", +// GTK_SIGNAL_FUNC(Html_lb_free), +// html->linkblock); +} + +/* + * Create and initialize a new DilloHtml structure + */ +static DilloHtml *Html_new(BrowserWindow *bw, const DilloUrl *url) +{ + DilloHtml *html; + + html = dNew(DilloHtml, 1); + + html->Start_Buf = NULL; + html->Start_Ofs = 0; + html->CurrTagOfs = 0; + html->OldTagOfs = 0; + html->OldTagLine = 1; + + html->DocType = DT_NONE; /* assume Tag Soup 0.0! :-) */ + html->DocTypeVersion = 0.0f; + + html->dw = NULL; + html->bw = bw; + html->linkblock = Html_lb_new(bw, url); + + html->stack = new misc::SimpleVector <DilloHtmlState> (16); + html->stack->increase(); + html->stack->getRef(0)->tag_name = dStrdup("none"); + html->stack->getRef(0)->style = NULL; + html->stack->getRef(0)->table_cell_style = NULL; + html->stack->getRef(0)->parse_mode = DILLO_HTML_PARSE_MODE_INIT; + html->stack->getRef(0)->table_mode = DILLO_HTML_TABLE_MODE_NONE; + html->stack->getRef(0)->cell_text_align_set = FALSE; + html->stack->getRef(0)->list_type = HTML_LIST_NONE; + html->stack->getRef(0)->list_number = 0; + html->stack->getRef(0)->tag_idx = -1; /* MUST not be used */ + html->stack->getRef(0)->textblock = NULL; + html->stack->getRef(0)->table = NULL; + html->stack->getRef(0)->ref_list_item = NULL; + html->stack->getRef(0)->current_bg_color = prefs.bg_color; + html->stack->getRef(0)->hand_over_break = FALSE; + + html->Stash = dStr_new(""); + html->StashSpace = FALSE; + + html->SPCBuf = NULL; + + html->pre_column = 0; + html->PreFirstChar = FALSE; + html->PrevWasCR = FALSE; + html->PrevWasOpenTag = FALSE; + html->SPCPending = FALSE; + html->InVisitedLink = FALSE; + html->ReqTagClose = FALSE; + html->CloseOneTag = FALSE; + html->TagSoup = TRUE; + html->NameVal = NULL; + + html->Num_HTML = html->Num_HEAD = html->Num_BODY = html->Num_TITLE = 0; + + html->InFlags = IN_NONE; + + html->attr_data = dStr_sized_new(1024); + + Html_set_dwpage(html); + + return html; +} + +/* + * Initialize the stash buffer + */ +static void Html_stash_init(DilloHtml *html) +{ + S_TOP(html)->parse_mode = DILLO_HTML_PARSE_MODE_STASH; + html->StashSpace = FALSE; + dStr_truncate(html->Stash, 0); +} + +/* Entities list from the HTML 4.01 DTD */ +typedef struct { + char *entity; + int isocode; +} Ent_t; + +#define NumEnt 252 +static const Ent_t Entities[NumEnt] = { + {"AElig",0306}, {"Aacute",0301}, {"Acirc",0302}, {"Agrave",0300}, + {"Alpha",01621},{"Aring",0305}, {"Atilde",0303}, {"Auml",0304}, + {"Beta",01622}, {"Ccedil",0307}, {"Chi",01647}, {"Dagger",020041}, + {"Delta",01624},{"ETH",0320}, {"Eacute",0311}, {"Ecirc",0312}, + {"Egrave",0310},{"Epsilon",01625},{"Eta",01627}, {"Euml",0313}, + {"Gamma",01623},{"Iacute",0315}, {"Icirc",0316}, {"Igrave",0314}, + {"Iota",01631}, {"Iuml",0317}, {"Kappa",01632}, {"Lambda",01633}, + {"Mu",01634}, {"Ntilde",0321}, {"Nu",01635}, {"OElig",0522}, + {"Oacute",0323},{"Ocirc",0324}, {"Ograve",0322}, {"Omega",01651}, + {"Omicron",01637},{"Oslash",0330},{"Otilde",0325},{"Ouml",0326}, + {"Phi",01646}, {"Pi",01640}, {"Prime",020063},{"Psi",01650}, + {"Rho",01641}, {"Scaron",0540}, {"Sigma",01643}, {"THORN",0336}, + {"Tau",01644}, {"Theta",01630}, {"Uacute",0332}, {"Ucirc",0333}, + {"Ugrave",0331},{"Upsilon",01645},{"Uuml",0334}, {"Xi",01636}, + {"Yacute",0335},{"Yuml",0570}, {"Zeta",01626}, {"aacute",0341}, + {"acirc",0342}, {"acute",0264}, {"aelig",0346}, {"agrave",0340}, + {"alefsym",020465},{"alpha",01661},{"amp",38}, {"and",021047}, + {"ang",021040}, {"aring",0345}, {"asymp",021110},{"atilde",0343}, + {"auml",0344}, {"bdquo",020036},{"beta",01662}, {"brvbar",0246}, + {"bull",020042},{"cap",021051}, {"ccedil",0347}, {"cedil",0270}, + {"cent",0242}, {"chi",01707}, {"circ",01306}, {"clubs",023143}, + {"cong",021105},{"copy",0251}, {"crarr",020665},{"cup",021052}, + {"curren",0244},{"dArr",020723}, {"dagger",020040},{"darr",020623}, + {"deg",0260}, {"delta",01664}, {"diams",023146},{"divide",0367}, + {"eacute",0351},{"ecirc",0352}, {"egrave",0350}, {"empty",021005}, + {"emsp",020003},{"ensp",020002}, {"epsilon",01665},{"equiv",021141}, + {"eta",01667}, {"eth",0360}, {"euml",0353}, {"euro",020254}, + {"exist",021003},{"fnof",0622}, {"forall",021000},{"frac12",0275}, + {"frac14",0274},{"frac34",0276}, {"frasl",020104},{"gamma",01663}, + {"ge",021145}, {"gt",62}, {"hArr",020724}, {"harr",020624}, + {"hearts",023145},{"hellip",020046},{"iacute",0355},{"icirc",0356}, + {"iexcl",0241}, {"igrave",0354}, {"image",020421},{"infin",021036}, + {"int",021053}, {"iota",01671}, {"iquest",0277}, {"isin",021010}, + {"iuml",0357}, {"kappa",01672}, {"lArr",020720}, {"lambda",01673}, + {"lang",021451},{"laquo",0253}, {"larr",020620}, {"lceil",021410}, + {"ldquo",020034},{"le",021144}, {"lfloor",021412},{"lowast",021027}, + {"loz",022712}, {"lrm",020016}, {"lsaquo",020071},{"lsquo",020030}, + {"lt",60}, {"macr",0257}, {"mdash",020024},{"micro",0265}, + {"middot",0267},{"minus",021022},{"mu",01674}, {"nabla",021007}, + {"nbsp",32}, {"ndash",020023},{"ne",021140}, {"ni",021013}, + {"not",0254}, {"notin",021011},{"nsub",021204}, {"ntilde",0361}, + {"nu",01675}, {"oacute",0363}, {"ocirc",0364}, {"oelig",0523}, + {"ograve",0362},{"oline",020076},{"omega",01711}, {"omicron",01677}, + {"oplus",021225},{"or",021050}, {"ordf",0252}, {"ordm",0272}, + {"oslash",0370},{"otilde",0365}, {"otimes",021227},{"ouml",0366}, + {"para",0266}, {"part",021002}, {"permil",020060},{"perp",021245}, + {"phi",01706}, {"pi",01700}, {"piv",01726}, {"plusmn",0261}, + {"pound",0243}, {"prime",020062},{"prod",021017}, {"prop",021035}, + {"psi",01710}, {"quot",34}, {"rArr",020722}, {"radic",021032}, + {"rang",021452},{"raquo",0273}, {"rarr",020622}, {"rceil",021411}, + {"rdquo",020035},{"real",020434},{"reg",0256}, {"rfloor",021413}, + {"rho",01701}, {"rlm",020017}, {"rsaquo",020072},{"rsquo",020031}, + {"sbquo",020032},{"scaron",0541},{"sdot",021305}, {"sect",0247}, + {"shy",0255}, {"sigma",01703}, {"sigmaf",01702},{"sim",021074}, + {"spades",023140},{"sub",021202},{"sube",021206}, {"sum",021021}, + {"sup",021203}, {"sup1",0271}, {"sup2",0262}, {"sup3",0263}, + {"supe",021207},{"szlig",0337}, {"tau",01704}, {"there4",021064}, + {"theta",01670},{"thetasym",01721},{"thinsp",020011},{"thorn",0376}, + {"tilde",01334},{"times",0327}, {"trade",020442},{"uArr",020721}, + {"uacute",0372},{"uarr",020621}, {"ucirc",0373}, {"ugrave",0371}, + {"uml",0250}, {"upsih",01722}, {"upsilon",01705},{"uuml",0374}, + {"weierp",020430},{"xi",01676}, {"yacute",0375}, {"yen",0245}, + {"yuml",0377}, {"zeta",01666}, {"zwj",020015}, {"zwnj",020014} +}; + + +/* + * Comparison function for binary search + */ +static int Html_entity_comp(const void *a, const void *b) +{ + return strcmp(((Ent_t *)a)->entity, ((Ent_t *)b)->entity); +} + +/* + * Binary search of 'key' in entity list + */ +static int Html_entity_search(char *key) +{ + Ent_t *res, EntKey; + + EntKey.entity = key; + res = (Ent_t*) bsearch(&EntKey, Entities, NumEnt, + sizeof(Ent_t), Html_entity_comp); + if (res) + return (res - Entities); + return -1; +} + +/* + * This is M$ non-standard "smart quotes" (w1252). Now even deprecated by them! + * + * SGML for HTML4.01 defines c >= 128 and c <= 159 as UNUSED. + * TODO: Probably I should remove this hack, and add a HTML warning. --Jcid + */ +static int Html_ms_stupid_quotes_2ucs(int isocode) +{ + int ret; + switch (isocode) { + case 145: + case 146: ret = '\''; break; + case 147: + case 148: ret = '"'; break; + case 149: ret = 176; break; + case 150: + case 151: ret = '-'; break; + default: ret = isocode; break; + } + return ret; +} + +/* + * Given an entity, return the UCS character code. + * Returns a negative value (error code) if not a valid entity. + * + * The first character *token is assumed to be == '&' + * + * For valid entities, *entsize is set to the length of the parsed entity. + */ +static int Html_parse_entity(DilloHtml *html, const char *token, + int toksize, int *entsize) +{ + int isocode, i; + char *tok, *s, c; + + token++; + tok = s = toksize ? dStrndup(token, (uint_t)toksize) : dStrdup(token); + + isocode = -1; + + if (*s == '#') { + /* numeric character reference */ + errno = 0; + if (*++s == 'x' || *s == 'X') { + if (isxdigit(*++s)) { + /* strtol with base 16 accepts leading "0x" - we don't */ + if (*s == '0' && s[1] == 'x') { + s++; + isocode = 0; + } else { + isocode = strtol(s, &s, 16); + } + } + } else if (isdigit(*s)) { + isocode = strtol(s, &s, 10); + } + + if (!isocode || errno || isocode > 0xffff) { + /* this catches null bytes, errors and codes >= 0xFFFF */ + MSG_HTML("numeric character reference out of range\n"); + isocode = -2; + } + + if (isocode != -1) { + if (*s == ';') + s++; + else if (prefs.show_extra_warnings) + MSG_HTML("numeric character reference without trailing ';'\n"); + } + + } else if (isalpha(*s)) { + /* character entity reference */ + while (isalnum(*++s) || strchr(":_.-", *s)); + c = *s; + *s = 0; + + if ((i = Html_entity_search(tok)) == -1) { + if ((html->DocType == DT_HTML && html->DocTypeVersion == 4.01f) || + html->DocType == DT_XHTML) + MSG_HTML("undefined character entity '%s'\n", tok); + isocode = -3; + } else + isocode = Entities[i].isocode; + + if (c == ';') + s++; + else if (prefs.show_extra_warnings) + MSG_HTML("character entity reference without trailing ';'\n"); + } + + *entsize = s-tok+1; + dFree(tok); + + if (isocode >= 145 && isocode <= 151) { + /* TODO: remove this hack. */ + isocode = Html_ms_stupid_quotes_2ucs(isocode); + } else if (isocode == -1 && prefs.show_extra_warnings) + MSG_HTML("literal '&'\n"); + + return isocode; +} + +/* + * Convert all the entities in a token to utf8 encoding. Takes + * a token and its length, and returns a newly allocated string. + */ +static char * + Html_parse_entities(DilloHtml *html, char *token, int toksize) +{ + char *esc_set = "&\xE2\xC2"; + char *new_str, buf[4]; + int i, j, k, n, isocode, entsize; + + new_str = dStrndup(token, toksize); + if (new_str[strcspn(new_str, esc_set)] == 0) + return new_str; + + for (i = j = 0; i < toksize; i++) { + if (token[i] == '&' && + (isocode = Html_parse_entity(html, token+i, + toksize-i, &entsize)) >= 0) { + if (isocode >= 128) { + /* multibyte encoding */ + n = utf8encode(isocode, buf); + for (k = 0; k < n; ++k) + new_str[j++] = buf[k]; + } else { + new_str[j++] = (char) isocode; + } + i += entsize-1; + } else { + new_str[j++] = token[i]; + } + } + new_str[j] = '\0'; + return new_str; +} + +/* + * Parse spaces + */ +static void Html_process_space(DilloHtml *html, char *space, int spacesize) +{ + int i, offset; + DilloHtmlParseMode parse_mode = S_TOP(html)->parse_mode; + + if (parse_mode == DILLO_HTML_PARSE_MODE_STASH) { + html->StashSpace = (html->Stash->len > 0); + html->SPCPending = FALSE; + + } else if (parse_mode == DILLO_HTML_PARSE_MODE_VERBATIM) { + char *Pword = dStrndup(space, spacesize); + dStr_append(html->Stash, Pword); + dFree(Pword); + html->SPCPending = FALSE; + + } else if (parse_mode == DILLO_HTML_PARSE_MODE_PRE) { + /* re-scan the string for characters that cause line breaks */ + for (i = 0; i < spacesize; i++) { + /* Support for "\r", "\n" and "\r\n" line breaks (skips the first) */ + if (!html->PreFirstChar && + (space[i] == '\r' || (space[i] == '\n' && !html->PrevWasCR))) { + DW2TB(html->dw)->addLinebreak (S_TOP(html)->style); + html->pre_column = 0; + } + html->PreFirstChar = FALSE; + + /* cr and lf should not be rendered -- they appear as a break */ + switch (space[i]) { + case '\r': + case '\n': + break; + case '\t': + if (prefs.show_extra_warnings) + MSG_HTML("TAB character inside <PRE>\n"); + offset = TAB_SIZE - html->pre_column % TAB_SIZE; + DW2TB(html->dw)->addText (dStrnfill(offset, ' '), + S_TOP(html)->style); + html->pre_column += offset; + break; + default: + DW2TB(html->dw)->addText (dStrndup(space + i, 1), + S_TOP(html)->style); + html->pre_column++; + break; + } + + html->PrevWasCR = (space[i] == '\r'); + } + html->SPCPending = FALSE; + + } else { + if (SGML_SPCDEL && html->PrevWasOpenTag) { + /* SGML_SPCDEL ignores white space inmediately after an open tag */ + html->SPCPending = FALSE; + } else { + dFree(html->SPCBuf); + html->SPCBuf = dStrndup(space, spacesize); + html->SPCPending = TRUE; + } + + if (parse_mode == DILLO_HTML_PARSE_MODE_STASH_AND_BODY) + html->StashSpace = (html->Stash->len > 0); + } +} + +/* + * Handles putting the word into its proper place + * > STASH and VERBATIM --> html->Stash + * > otherwise it goes through addText() + * + * Entities are parsed (or not) according to parse_mode. + */ +static void Html_process_word(DilloHtml *html, char *word, int size) +{ + int i, j, start; + char *Pword; + DilloHtmlParseMode parse_mode = S_TOP(html)->parse_mode; + + if (parse_mode == DILLO_HTML_PARSE_MODE_STASH || + parse_mode == DILLO_HTML_PARSE_MODE_STASH_AND_BODY) { + if (html->StashSpace) { + dStr_append_c(html->Stash, ' '); + html->StashSpace = FALSE; + } + Pword = Html_parse_entities(html, word, size); + dStr_append(html->Stash, Pword); + dFree(Pword); + + } else if (parse_mode == DILLO_HTML_PARSE_MODE_VERBATIM) { + /* word goes in untouched, it is not processed here. */ + Pword = dStrndup(word, size); + dStr_append(html->Stash, Pword); + dFree(Pword); + } + + if (parse_mode == DILLO_HTML_PARSE_MODE_STASH || + parse_mode == DILLO_HTML_PARSE_MODE_VERBATIM) { + /* skip until the closing instructions */ + + } else if (parse_mode == DILLO_HTML_PARSE_MODE_PRE) { + /* all this overhead is to catch white-space entities */ + Pword = Html_parse_entities(html, word, size); + for (start = i = 0; Pword[i]; start = i) + if (isspace(Pword[i])) { + while (Pword[++i] && isspace(Pword[i])); + Html_process_space(html, Pword + start, i - start); + } else { + while (Pword[++i] && !isspace(Pword[i])); + DW2TB(html->dw)->addText( + dStrndup(Pword + start, i - start), + S_TOP(html)->style); + html->pre_column += i - start; + html->PreFirstChar = FALSE; + } + dFree(Pword); + + } else { + /* add pending space if present */ + if (html->SPCPending && (!SGML_SPCDEL || !html->PrevWasOpenTag)) + /* SGML_SPCDEL ignores space after an open tag */ + DW2TB(html->dw)->addSpace (S_TOP(html)->style); + + /* Collapse white-space entities inside the word (except ) */ + Pword = Html_parse_entities(html, word, size); + for (i = 0; Pword[i]; ++i) + if (strchr("\t\f\n\r", Pword[i])) + for (j = i; (Pword[j] = Pword[j+1]); ++j); + + DW2TB(html->dw)->addText(Pword, S_TOP(html)->style); + } + + html->PrevWasOpenTag = FALSE; + html->SPCPending = FALSE; +} + +/* + * Does the tag in tagstr (e.g. "p") match the tag in the tag, tagsize + * structure, with the initial < skipped over (e.g. "P align=center>") + */ +static bool_t Html_match_tag(const char *tagstr, char *tag, int tagsize) +{ + int i; + + for (i = 0; i < tagsize && tagstr[i] != '\0'; i++) { + if (tolower(tagstr[i]) != tolower(tag[i])) + return FALSE; + } + /* The test for '/' is for xml compatibility: "empty/>" will be matched. */ + if (i < tagsize && (isspace(tag[i]) || tag[i] == '>' || tag[i] == '/')) + return TRUE; + return FALSE; +} + +/* + * This function is called after popping the stack, to + * handle nested DwPage widgets. + */ +static void Html_eventually_pop_dw(DilloHtml *html, bool_t hand_over_break) +{ + if (html->dw != S_TOP(html)->textblock) { + if (hand_over_break) + DW2TB(html->dw)->handOverBreak (S_TOP(html)->style); + DW2TB(html->dw)->flush (); + html->dw = S_TOP(html)->textblock; + } +} + +/* + * Push the tag (copying attributes from the top of the stack) + */ +static void Html_push_tag(DilloHtml *html, int tag_idx) +{ + char *tagstr; + int n_items; + + /* Save the element's name (no parameters) into tagstr. */ + tagstr = dStrdup(Tags[tag_idx].name); + + n_items = html->stack->size (); + html->stack->increase (); + /* We'll copy the former stack item and just change the tag and its index + * instead of copying all fields except for tag. --Jcid */ + *html->stack->getRef(n_items) = *html->stack->getRef(n_items - 1); + html->stack->getRef(n_items)->tag_name = tagstr; + html->stack->getRef(n_items)->tag_idx = tag_idx; + /* proper memory management, may be unref'd later */ + (S_TOP(html)->style)->ref (); + if (S_TOP(html)->table_cell_style) + (S_TOP(html)->table_cell_style)->ref (); + html->dw = S_TOP(html)->textblock; +} + +/* + * Push the tag (used to force en element with optional open into the stack) + * Note: now it's the same as Html_push_tag(), but things may change... + */ +static void Html_force_push_tag(DilloHtml *html, int tag_idx) +{ + Html_push_tag(html, tag_idx); +} + +/* + * Pop the top tag in the stack + */ +static void Html_real_pop_tag(DilloHtml *html) +{ + bool_t hand_over_break; + + (S_TOP(html)->style)->unref (); + if (S_TOP(html)->table_cell_style) + (S_TOP(html)->table_cell_style)->unref (); + dFree(S_TOP(html)->tag_name); + hand_over_break = S_TOP(html)->hand_over_break; + html->stack->setSize (html->stack->size() - 1); + Html_eventually_pop_dw(html, hand_over_break); +} + +/* + * Default close function for tags. + * (conditional cleanup of the stack) + * There're several ways of doing it. Considering the HTML 4.01 spec + * which defines optional close tags, and the will to deliver useful diagnose + * messages for bad-formed HTML, it'll go as follows: + * 1.- Search the stack for the first tag that requires a close tag. + * 2.- If it matches, clean all the optional-close tags in between. + * 3.- Cleanup the matching tag. (on error, give a warning message) + * + * If 'w3c_mode' is NOT enabled: + * 1.- Search the stack for a matching tag based on tag level. + * 2.- If it exists, clean all the tags in between. + * 3.- Cleanup the matching tag. (on error, give a warning message) + */ +static void Html_tag_cleanup_at_close(DilloHtml *html, int TagIdx) +{ + int w3c_mode = !prefs.w3c_plus_heuristics; + int stack_idx, cmp = 1; + int new_idx = TagIdx; + + if (html->CloseOneTag) { + Html_real_pop_tag(html); + html->CloseOneTag = FALSE; + return; + } + + /* Look for the candidate tag to close */ + stack_idx = html->stack->size() - 1; + while (stack_idx && + (cmp = (new_idx != html->stack->getRef(stack_idx)->tag_idx)) && + ((w3c_mode && + Tags[html->stack->getRef(stack_idx)->tag_idx].EndTag == 'O') || + (!w3c_mode && + Tags[html->stack->getRef(stack_idx)->tag_idx].TagLevel < + Tags[new_idx].TagLevel))) { + --stack_idx; + } + + /* clean, up to the matching tag */ + if (cmp == 0 && stack_idx > 0) { + /* There's a valid matching tag in the stack */ + while (html->stack->size() > stack_idx) { + int toptag_idx = S_TOP(html)->tag_idx; + /* Warn when we decide to close an open tag (for !w3c_mode) */ + if (html->stack->size() > stack_idx + 1 && + Tags[toptag_idx].EndTag != 'O') + MSG_HTML(" - forcing close of open tag: <%s>\n", + Tags[toptag_idx].name); + + /* Close this and only this tag */ + html->CloseOneTag = TRUE; + Tags[toptag_idx].close (html, toptag_idx); + } + + } else { + MSG_HTML("unexpected closing tag: </%s>. -- expected </%s>\n", + Tags[new_idx].name, html->stack->getRef(stack_idx)->tag_name); + } +} + +/* + * Cleanup (conditional), and Pop the tag (if it matches) + */ +static void Html_pop_tag(DilloHtml *html, int TagIdx) +{ + Html_tag_cleanup_at_close(html, TagIdx); +} + +/* + * Some parsing routines. + */ + +/* + * Used by Html_parse_length + */ +static Length Html_parse_length_or_multi_length (const char *attr, + char **endptr) +{ + Length l; + double v; + char *end; + + v = strtod (attr, &end); + switch (*end) { + case '%': + end++; + l = createPerLength (v / 100); + break; + + case '*': + end++; + l = createRelLength (v); + break; +/* + The "px" suffix seems not allowed by HTML4.01 SPEC. + case 'p': + if (end[1] == 'x') + end += 2; +*/ + default: + l = createAbsLength ((int)v); + break; + } + + if (endptr) + *endptr = end; + return l; +} + + +/* + * Returns a length or a percentage, or UNDEF_LENGTH in case + * of an error, or if attr is NULL. + */ +static Length Html_parse_length (DilloHtml *html, const char *attr) +{ + Length l; + char *end; + + l = Html_parse_length_or_multi_length (attr, &end); + if (isRelLength (l)) + /* not allowed as &Length; */ + return LENGTH_AUTO; + else { + /* allow only whitespaces */ + if (*end && !isspace (*end)) { + MSG_HTML("Garbage after length: %s\n", attr); + return LENGTH_AUTO; + } + } + + _MSG("Html_parse_length: \"%s\" %d\n", attr, absLengthVal(l)); + return l; +} + +/* + * Parse a color attribute. + * Return value: parsed color, or default_color (+ error msg) on error. + */ +static int32_t + Html_color_parse(DilloHtml *html, const char *subtag, int32_t default_color) +{ + int err = 1; + int32_t color = a_Color_parse(subtag, default_color, &err); + + if (err) { + MSG_HTML("color is not in \"#RRGGBB\" format\n"); + } + return color; +} + +/* + * Check that 'val' is composed of characters inside [A-Za-z0-9:_.-] + * Note: ID can't have entities, but this check is enough (no '&'). + * Return value: 1 if OK, 0 otherwise. + */ +static int + Html_check_name_val(DilloHtml *html, const char *val, const char *attrname) +{ + int i; + + for (i = 0; val[i]; ++i) + if (!(isalnum(val[i]) || strchr(":_.-", val[i]))) + break; + + if (val[i] || !isalpha(val[0])) + MSG_HTML("'%s' value is not of the form " + "[A-Za-z][A-Za-z0-9:_.-]*\n", attrname); + + return !(val[i]); +} + +/* + * Handle DOCTYPE declaration + * + * Follows the convention that HTML 4.01 + * doctypes which include a full w3c DTD url are treated as + * standards-compliant, but 4.01 without the url and HTML 4.0 and + * earlier are not. XHTML doctypes are always standards-compliant + * whether or not an url is present. + * + * Note: I'm not sure about this convention. The W3C validator + * recognizes the "HTML Level" with or without the URL. The convention + * comes from mozilla (see URLs below), but Dillo doesn't have the same + * rendering modes, so it may be better to chose another behaviour. --Jcid + * + * http://www.mozilla.org/docs/web-developer/quirks/doctypes.html + * http://lists.auriga.wearlab.de/pipermail/dillo-dev/2004-October/002300.html + * + * This is not a full DOCTYPE parser, just enough for what Dillo uses. + */ +static void Html_parse_doctype(DilloHtml *html, char *tag, int tagsize) +{ + char *HTML_sig = "<!DOCTYPE HTML PUBLIC "; + char *HTML20 = "-//IETF//DTD HTML//EN"; + char *HTML32 = "-//W3C//DTD HTML 3.2"; + char *HTML40 = "-//W3C//DTD HTML 4.0"; + char *HTML401 = "-//W3C//DTD HTML 4.01"; + char *HTML401_url = "http://www.w3.org/TR/html4/"; + char *XHTML1 = "-//W3C//DTD XHTML 1.0"; + char *XHTML1_url = "http://www.w3.org/TR/xhtml1/DTD/"; + char *XHTML11 = "-//W3C//DTD XHTML 1.1"; + char *XHTML11_url = "http://www.w3.org/TR/xhtml11/DTD/"; + + int i, quote; + char *p, *ntag = dStrndup(tag, tagsize); + + /* Tag sanitization: Collapse whitespace between tokens + * and replace '\n' and '\r' with ' ' inside quoted strings. */ + for (i = 0, p = ntag; *p; ++p) { + if (isspace(*p)) { + for (ntag[i++] = ' '; isspace(p[1]); ++p); + } else if ((quote = *p) == '"' || *p == '\'') { + for (ntag[i++] = *p++; (ntag[i++] = *p) && *p != quote; ++p) { + if (*p == '\n' || *p == '\r') + ntag[i - 1] = ' '; + p += (p[0] == '\r' && p[1] == '\n') ? 1 : 0; + } + } else { + ntag[i++] = *p; + } + if (!*p) + break; + } + ntag[i] = 0; + + _MSG("New: {%s}\n", ntag); + + /* The default DT_NONE type is TagSoup */ + if (!dStrncasecmp(ntag, HTML_sig, strlen(HTML_sig))) { + p = ntag + strlen(HTML_sig) + 1; + if (!strncmp(p, HTML401, strlen(HTML401)) && + dStristr(p + strlen(HTML401), HTML401_url)) { + html->DocType = DT_HTML; + html->DocTypeVersion = 4.01f; + } else if (!strncmp(p, XHTML1, strlen(XHTML1)) && + dStristr(p + strlen(XHTML1), XHTML1_url)) { + html->DocType = DT_XHTML; + html->DocTypeVersion = 1.0f; + } else if (!strncmp(p, XHTML11, strlen(XHTML11)) && + dStristr(p + strlen(XHTML11), XHTML11_url)) { + html->DocType = DT_XHTML; + html->DocTypeVersion = 1.1f; + } else if (!strncmp(p, HTML40, strlen(HTML40))) { + html->DocType = DT_HTML; + html->DocTypeVersion = 4.0f; + } else if (!strncmp(p, HTML32, strlen(HTML32))) { + html->DocType = DT_HTML; + html->DocTypeVersion = 3.2f; + } else if (!strncmp(p, HTML20, strlen(HTML20))) { + html->DocType = DT_HTML; + html->DocTypeVersion = 2.0f; + } + } + + dFree(ntag); +} + +/* + * Handle open HTML element + */ +static void Html_tag_open_html(DilloHtml *html, char *tag, int tagsize) +{ + if (!(html->InFlags & IN_HTML)) + html->InFlags |= IN_HTML; + ++html->Num_HTML; + + if (html->Num_HTML > 1) { + MSG_HTML("HTML element was already open\n"); + } +} + +/* + * Handle close HTML element + */ +static void Html_tag_close_html(DilloHtml *html, int TagIdx) +{ + /* todo: may add some checks here */ + if (html->Num_HTML == 1) { + /* beware of pages with multiple HTML close tags... :-P */ + html->InFlags &= ~IN_HTML; + } + Html_pop_tag(html, TagIdx); +} + +/* + * Handle open HEAD element + */ +static void Html_tag_open_head(DilloHtml *html, char *tag, int tagsize) +{ + if (html->InFlags & IN_BODY) { + MSG_HTML("HEAD element must go before the BODY section\n"); + html->ReqTagClose = TRUE; + return; + } + + if (!(html->InFlags & IN_HEAD)) + html->InFlags |= IN_HEAD; + ++html->Num_HEAD; + + if (html->Num_HEAD > 1) { + MSG_HTML("HEAD element was already open\n"); + } +} + +/* + * Handle close HEAD element + * Note: as a side effect of Html_test_section() this function is called + * twice when the head element is closed implicitly. + */ +static void Html_tag_close_head(DilloHtml *html, int TagIdx) +{ + if (html->InFlags & IN_HEAD) { + if (html->Num_TITLE == 0) + MSG_HTML("HEAD section lacks the TITLE element\n"); + + html->InFlags &= ~IN_HEAD; + } + Html_pop_tag(html, TagIdx); +} + +/* + * Handle open TITLE + * calls stash init, where the title string will be stored + */ +static void Html_tag_open_title(DilloHtml *html, char *tag, int tagsize) +{ + ++html->Num_TITLE; + Html_stash_init(html); +} + +/* + * Handle close TITLE + * set page-title in the browser window and in the history. + */ +static void Html_tag_close_title(DilloHtml *html, int TagIdx) +{ + if (html->InFlags & IN_HEAD) { + /* title is only valid inside HEAD */ + a_UIcmd_set_page_title(html->linkblock->bw, html->Stash->str); + a_History_set_title(NAV_TOP(html->linkblock->bw), html->Stash->str); + } else { + MSG_HTML("the TITLE element must be inside the HEAD section\n"); + } + Html_pop_tag(html, TagIdx); +} + +/* + * Handle open SCRIPT + * initializes stash, where the embedded code will be stored. + * MODE_VERBATIM is used because MODE_STASH catches entities. + */ +static void Html_tag_open_script(DilloHtml *html, char *tag, int tagsize) +{ + Html_stash_init(html); + S_TOP(html)->parse_mode = DILLO_HTML_PARSE_MODE_VERBATIM; +} + +/* + * Handle close SCRIPT + */ +static void Html_tag_close_script(DilloHtml *html, int TagIdx) +{ + /* eventually the stash will be sent to an interpreter for parsing */ + Html_pop_tag(html, TagIdx); +} + +/* + * Handle open STYLE + * store the contents to the stash where (in the future) the style + * sheet interpreter can get it. + */ +static void Html_tag_open_style(DilloHtml *html, char *tag, int tagsize) +{ + Html_stash_init(html); + S_TOP(html)->parse_mode = DILLO_HTML_PARSE_MODE_VERBATIM; +} + +/* + * Handle close STYLE + */ +static void Html_tag_close_style(DilloHtml *html, int TagIdx) +{ + /* eventually the stash will be sent to an interpreter for parsing */ + Html_pop_tag(html, TagIdx); +} + +/* + * <BODY> + */ +static void Html_tag_open_body(DilloHtml *html, char *tag, int tagsize) +{ + const char *attrbuf; + Textblock *textblock; + StyleAttrs style_attrs; + Style *style; + int32_t color; + + if (!(html->InFlags & IN_BODY)) + html->InFlags |= IN_BODY; + ++html->Num_BODY; + + if (html->Num_BODY > 1) { + MSG_HTML("BODY element was already open\n"); + return; + } + if (html->InFlags & IN_HEAD) { + /* if we're here, it's bad XHTML, no need to recover */ + MSG_HTML("unclosed HEAD element\n"); + } + + textblock = DW2TB(html->dw); + + if (!prefs.force_my_colors) { + if ((attrbuf = Html_get_attr(html, tag, tagsize, "bgcolor"))) { + color = Html_color_parse(html, attrbuf, prefs.bg_color); + if ((color == 0xffffff && !prefs.allow_white_bg) || + prefs.force_my_colors) + color = prefs.bg_color; + + style_attrs = *html->dw->getStyle (); + style_attrs.backgroundColor = + Color::createSimple (HT2LT(html), color); + style = Style::create (HT2LT(html), &style_attrs); + html->dw->setStyle (style); + style->unref (); + S_TOP(html)->current_bg_color = color; + } + + if ((attrbuf = Html_get_attr(html, tag, tagsize, "text"))) { + color = Html_color_parse(html, attrbuf, prefs.text_color); + HTML_SET_TOP_ATTR (html, color, + Color::createSimple (HT2LT(html),color)); + } + + if ((attrbuf = Html_get_attr(html, tag, tagsize, "link"))) + html->linkblock->link_color = Html_color_parse(html, attrbuf, + prefs.link_color); + + if ((attrbuf = Html_get_attr(html, tag, tagsize, "vlink"))) + html->linkblock->visited_color = + Html_color_parse(html, attrbuf, prefs.visited_color); + + if (prefs.contrast_visited_color) { + /* get a color that has a "safe distance" from text, link and bg */ + html->linkblock->visited_color = + a_Color_vc(html->linkblock->visited_color, + S_TOP(html)->style->color->getColor(), + html->linkblock->link_color, + S_TOP(html)->current_bg_color); + } + } + + S_TOP(html)->parse_mode = DILLO_HTML_PARSE_MODE_BODY; +} + +/* + * BODY + */ +static void Html_tag_close_body(DilloHtml *html, int TagIdx) +{ + if (html->Num_BODY == 1) { + /* some tag soup pages use multiple BODY tags... */ + html->InFlags &= ~IN_BODY; + } + Html_pop_tag(html, TagIdx); +} + +/* + * <P> + * todo: what's the point between adding the parbreak before and + * after the push? + */ +static void Html_tag_open_p(DilloHtml *html, char *tag, int tagsize) +{ + DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style); + Html_tag_set_align_attr (html, tag, tagsize); +} + +/* + * <TABLE> + */ +static void Html_tag_open_table(DilloHtml *html, char *tag, int tagsize) +{ +#ifdef USE_TABLES + Widget *table; + StyleAttrs style_attrs; + Style *tstyle, *old_style; + const char *attrbuf; + int32_t border = 0, cellspacing = 1, cellpadding = 2, bgcolor; +#endif + + DW2TB(html->dw)->addParbreak (0, S_TOP(html)->style); + +#ifdef USE_TABLES + if ((attrbuf = Html_get_attr(html, tag, tagsize, "border"))) + border = isdigit(attrbuf[0]) ? strtol (attrbuf, NULL, 10) : 1; + if ((attrbuf = Html_get_attr(html, tag, tagsize, "cellspacing"))) + cellspacing = strtol (attrbuf, NULL, 10); + if ((attrbuf = Html_get_attr(html, tag, tagsize, "cellpadding"))) + cellpadding = strtol (attrbuf, NULL, 10); + + /* The style for the table */ + style_attrs = *S_TOP(html)->style; + + /* When dillo was started with the --debug-rendering option, there + * is always a border around the table. */ + if (dillo_dbg_rendering) + style_attrs.borderWidth.setVal (MIN (border, 1)); + else + style_attrs.borderWidth.setVal (border); + + style_attrs.setBorderColor ( + Color::createShaded (HT2LT(html), + S_TOP(html)->current_bg_color)); + style_attrs.setBorderStyle (BORDER_OUTSET); + style_attrs.hBorderSpacing = cellspacing; + + if ((attrbuf = Html_get_attr(html, tag, tagsize, "width"))) + style_attrs.width = Html_parse_length (html, attrbuf); + + if ((attrbuf = Html_get_attr(html, tag, tagsize, "align"))) { + if (dStrcasecmp (attrbuf, "left") == 0) + style_attrs.textAlign = TEXT_ALIGN_LEFT; + else if (dStrcasecmp (attrbuf, "right") == 0) + style_attrs.textAlign = TEXT_ALIGN_RIGHT; + else if (dStrcasecmp (attrbuf, "center") == 0) + style_attrs.textAlign = TEXT_ALIGN_CENTER; + } + + if (!prefs.force_my_colors && + (attrbuf = Html_get_attr(html, tag, tagsize, "bgcolor"))) { + bgcolor = Html_color_parse(html, attrbuf, -1); + if (bgcolor != -1) { + if (bgcolor == 0xffffff && !prefs.allow_white_bg) + bgcolor = prefs.bg_color; + S_TOP(html)->current_bg_color = bgcolor; + style_attrs.backgroundColor = + Color::createSimple (HT2LT(html), bgcolor); + } + } + + tstyle = Style::create (HT2LT(html), &style_attrs); + + /* The style for the cells */ + style_attrs = *S_TOP(html)->style; + /* When dillo was started with the --debug-rendering option, there + * is always a border around the cells. */ + if (dillo_dbg_rendering) + style_attrs.borderWidth.setVal (1); + else + style_attrs.borderWidth.setVal (border ? 1 : 0); + + style_attrs.padding.setVal(cellpadding); + style_attrs.setBorderColor (tstyle->borderColor.top); + style_attrs.setBorderStyle (BORDER_INSET); + + old_style = S_TOP(html)->table_cell_style; + S_TOP(html)->table_cell_style = + Style::create (HT2LT(html), &style_attrs); + if (old_style) + old_style->unref (); + + table = new Table(false); + DW2TB(html->dw)->addWidget (table, tstyle); + tstyle->unref (); + + S_TOP(html)->table_mode = DILLO_HTML_TABLE_MODE_TOP; + S_TOP(html)->cell_text_align_set = FALSE; + S_TOP(html)->table = table; +#endif +} + + +/* + * used by <TD> and <TH> + */ +static void Html_tag_open_table_cell(DilloHtml *html, char *tag, int tagsize, + TextAlignType text_align) +{ +#ifdef USE_TABLES + Widget *col_tb; + int colspan = 1, rowspan = 1; + const char *attrbuf; + StyleAttrs style_attrs; + Style *style, *old_style; + int32_t bgcolor; + bool_t new_style; + + switch (S_TOP(html)->table_mode) { + case DILLO_HTML_TABLE_MODE_NONE: + MSG_HTML("<td> or <th> outside <table>\n"); + return; + + case DILLO_HTML_TABLE_MODE_TOP: + MSG_HTML("<td> or <th> outside <tr>\n"); + /* a_Dw_table_add_cell takes care that dillo does not crash. */ + /* continues */ + case DILLO_HTML_TABLE_MODE_TR: + case DILLO_HTML_TABLE_MODE_TD: + /* todo: check errors? */ + if ((attrbuf = Html_get_attr(html, tag, tagsize, "colspan"))) + colspan = strtol (attrbuf, NULL, 10); + if ((attrbuf = Html_get_attr(html, tag, tagsize, "rowspan"))) + rowspan = strtol (attrbuf, NULL, 10); + + /* text style */ + old_style = S_TOP(html)->style; + style_attrs = *old_style; + if (!S_TOP(html)->cell_text_align_set) + style_attrs.textAlign = text_align; + if (Html_get_attr(html, tag, tagsize, "nowrap")) + style_attrs.whiteSpace = WHITE_SPACE_NOWRAP; + else + style_attrs.whiteSpace = WHITE_SPACE_NORMAL; + + S_TOP(html)->style = + Style::create (HT2LT(html), &style_attrs); + old_style->unref (); + Html_tag_set_align_attr (html, tag, tagsize); + + /* cell style */ + style_attrs = *S_TOP(html)->table_cell_style; + new_style = FALSE; + + if ((attrbuf = Html_get_attr(html, tag, tagsize, "width"))) { + style_attrs.width = Html_parse_length (html, attrbuf); + new_style = TRUE; + } + + if (Html_tag_set_valign_attr (html, tag, tagsize, &style_attrs)) + new_style = TRUE; + + if (!prefs.force_my_colors && + (attrbuf = Html_get_attr(html, tag, tagsize, "bgcolor"))) { + bgcolor = Html_color_parse(html, attrbuf, -1); + if (bgcolor != -1) { + if (bgcolor == 0xffffff && !prefs.allow_white_bg) + bgcolor = prefs.bg_color; + + new_style = TRUE; + style_attrs.backgroundColor = + Color::createSimple (HT2LT(html), bgcolor); + S_TOP(html)->current_bg_color = bgcolor; + } + } + + if (S_TOP(html)->style->textAlign + == TEXT_ALIGN_STRING) + col_tb = new TableCell ( + ((Table*)S_TOP(html)->table)->getCellRef (), + false); + else + col_tb = new Textblock (false); + + if (new_style) { + style = Style::create (HT2LT(html), &style_attrs); + col_tb->setStyle (style); + style->unref (); + } else + col_tb->setStyle (S_TOP(html)->table_cell_style); + + ((Table*)S_TOP(html)->table)->addCell ( + col_tb, colspan, rowspan); + S_TOP(html)->textblock = html->dw = col_tb; + + /* Handle it when the user clicks on a link */ + Html_connect_signals(html, col_tb); + break; + + default: + /* compiler happiness */ + break; + } + + S_TOP(html)->table_mode = DILLO_HTML_TABLE_MODE_TD; +#endif +} + + +/* + * <TD> + */ +static void Html_tag_open_td(DilloHtml *html, char *tag, int tagsize) +{ + Html_tag_open_table_cell (html, tag, tagsize, TEXT_ALIGN_LEFT); +} + + +/* + * <TH> + */ +static void Html_tag_open_th(DilloHtml *html, char *tag, int tagsize) +{ + Html_set_top_font(html, NULL, 0, 1, 1); + Html_tag_open_table_cell (html, tag, tagsize, TEXT_ALIGN_CENTER); +} + + +/* + * <TR> + */ +static void Html_tag_open_tr(DilloHtml *html, char *tag, int tagsize) +{ + const char *attrbuf; + StyleAttrs style_attrs; + Style *style, *old_style; + int32_t bgcolor; + +#ifdef USE_TABLES + switch (S_TOP(html)->table_mode) { + case DILLO_HTML_TABLE_MODE_NONE: + _MSG("Invalid HTML syntax: <tr> outside <table>\n"); + return; + + case DILLO_HTML_TABLE_MODE_TOP: + case DILLO_HTML_TABLE_MODE_TR: + case DILLO_HTML_TABLE_MODE_TD: + style = NULL; + + if (!prefs.force_my_colors && + (attrbuf = Html_get_attr(html, tag, tagsize, "bgcolor"))) { + bgcolor = Html_color_parse(html, attrbuf, -1); + if (bgcolor != -1) { + if (bgcolor == 0xffffff && !prefs.allow_white_bg) + bgcolor = prefs.bg_color; + + style_attrs = *S_TOP(html)->style; + style_attrs.backgroundColor = + Color::createSimple (HT2LT(html), bgcolor); + style = Style::create (HT2LT(html), &style_attrs); + S_TOP(html)->current_bg_color = bgcolor; + } + } + + ((Table*)S_TOP(html)->table)->addRow (style); + if (style) + style->unref (); + + if (Html_get_attr (html, tag, tagsize, "align")) { + S_TOP(html)->cell_text_align_set = TRUE; + Html_tag_set_align_attr (html, tag, tagsize); + } + + style_attrs = *S_TOP(html)->table_cell_style; + if (Html_tag_set_valign_attr (html, tag, tagsize, &style_attrs)) { + old_style = S_TOP(html)->table_cell_style; + S_TOP(html)->table_cell_style = + Style::create (HT2LT(html), &style_attrs); + old_style->unref (); + } else + + break; + + default: + break; + } + + S_TOP(html)->table_mode = DILLO_HTML_TABLE_MODE_TR; +#else + DW2TB(html->dw)->addParbreak (0, S_TOP(html)->style); +#endif +} + +/* + * <FRAME>, <IFRAME> + * todo: This is just a temporary fix while real frame support + * isn't finished. Imitates lynx/w3m's frames. + */ +static void Html_tag_open_frame (DilloHtml *html, char *tag, int tagsize) +{ + const char *attrbuf; + char *src, *buf; + DilloUrl *url; + Textblock *textblock; + StyleAttrs style_attrs; + Style *link_style; + Widget *bullet; + int buf_size; + + textblock = DW2TB(html->dw); + + if (!(attrbuf = Html_get_attr(html, tag, tagsize, "src"))) + return; + + if (!(url = Html_url_new(html, attrbuf, NULL, 0, 0, 0, 0))) + return; + + src = dStrdup(attrbuf); + + style_attrs = *(S_TOP(html)->style); + + if (a_Capi_get_buf(url, &buf, &buf_size)) /* visited frame */ + style_attrs.color = + Color::createSimple (HT2LT(html), + html->linkblock->visited_color); + else /* unvisited frame */ + style_attrs.color = Color::createSimple (HT2LT(html), + html->linkblock->link_color); + + style_attrs.textDecoration |= TEXT_DECORATION_UNDERLINE; + style_attrs.x_link = Html_set_new_link(html, &url); + link_style = Style::create (HT2LT(html), &style_attrs); + + textblock->addParbreak (5, S_TOP(html)->style); + + /* The bullet will be assigned the current list style, which should + * be "disc" by default, but may in very weird pages be different. + * Anyway, there should be no harm. */ + bullet = new Bullet(); + textblock->addWidget(bullet, S_TOP(html)->style); + textblock->addSpace(S_TOP(html)->style); + + if (tolower(tag[1]) == 'i') { + /* IFRAME usually comes with very long advertising/spying URLS, + * to not break rendering we will force name="IFRAME" */ + textblock->addText (dStrdup("IFRAME"), link_style); + + } else { + /* FRAME: + * If 'name' tag is present use it, if not use 'src' value */ + if (!(attrbuf = Html_get_attr(html, tag, tagsize, "name"))) { + textblock->addText (dStrdup(src), link_style); + } else { + textblock->addText (dStrdup(attrbuf), link_style); + } + } + + textblock->addParbreak (5, S_TOP(html)->style); + + link_style->unref (); + dFree(src); +} + +/* + * <FRAMESET> + * todo: This is just a temporary fix while real frame support + * isn't finished. Imitates lynx/w3m's frames. + */ +static void Html_tag_open_frameset (DilloHtml *html, char *tag, int tagsize) +{ + DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style); + DW2TB(html->dw)->addText(dStrdup("--FRAME--"), + S_TOP(html)->style); + Html_add_indented(html, 40, 0, 5); +} + +/* + * <H1> | <H2> | <H3> | <H4> | <H5> | <H6> + */ +static void Html_tag_open_h(DilloHtml *html, char *tag, int tagsize) +{ + DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style); + + /* todo: combining these two would be slightly faster */ + Html_set_top_font(html, prefs.vw_fontname, + Html_level_to_fontsize(FontSizesNum - (tag[2] - '0')), + 1, 3); + Html_tag_set_align_attr (html, tag, tagsize); + + /* First finalize unclosed H tags (we test if already named anyway) */ + a_Menu_pagemarks_set_text(html->bw, html->Stash->str); + a_Menu_pagemarks_add(html->bw, DW2TB(html->dw), + S_TOP(html)->style, (tag[2] - '0')); + Html_stash_init(html); + S_TOP(html)->parse_mode = + DILLO_HTML_PARSE_MODE_STASH_AND_BODY; +} + +/* + * Handle close: <H1> | <H2> | <H3> | <H4> | <H5> | <H6> + */ +static void Html_tag_close_h(DilloHtml *html, int TagIdx) +{ + a_Menu_pagemarks_set_text(html->bw, html->Stash->str); + DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style); + Html_pop_tag(html, TagIdx); +} + +/* + * <BIG> | <SMALL> + */ +static void Html_tag_open_big_small(DilloHtml *html, char *tag, int tagsize) +{ + int level; + + level = + Html_fontsize_to_level(S_TOP(html)->style->font->size) + + ((dStrncasecmp(tag+1, "big", 3)) ? -1 : 1); + Html_set_top_font(html, NULL, Html_level_to_fontsize(level), 0, 0); +} + +/* + * <BR> + */ +static void Html_tag_open_br(DilloHtml *html, char *tag, int tagsize) +{ + DW2TB(html->dw)->addLinebreak (S_TOP(html)->style); +} + +/* + * <BUTTON> + */ +static void Html_tag_open_button(DilloHtml *html, char *tag, int tagsize) +{ +// // AL +// /* +// * Buttons are rendered on one line, this is (at several levels) a +// * bit simpler. May be changed in the future. +// */ +// StyleAttrs style_attrs; +// Style *style; +// Widget *button, *page; +// DilloHtmlForm *form; +// DilloHtmlLB *html_lb; +// DilloHtmlInputType inp_type; +// char *name, *value, *type; +// +// /* Render the button */ +// style_attrs = *S_TOP(html)->style; +// +// style_attrs.margin.setVal (0); +// style_attrs.borderWidth.setVal (0); +// style_attrs.padding.setVal(0); +// style = Style::create (HT2LT(html), &style_attrs); +// button = a_Dw_button_new (DW_USES_HINTS, TRUE); +// +// /* The new button is not set button-insensitive, since nested buttons +// * (if they are anyway allowed, todo: search in spec) should all be +// * activatable. */ +// a_Dw_widget_set_button_sensitive (button, TRUE); +// +// DW2TB(html->dw)->addParbreak (5, style); +// DW2TB(html->dw)->addWidget (button, style); +// DW2TB(html->dw)->addParbreak (5, style); +// style->unref (); +// +// style_attrs.margin.setVal (5); +// style = Style::create (HT2LT(html), &style_attrs); +// page = new Textblock (false); +// page->setStyle (style); +// style->unref (); +// a_Dw_container_add (DW_CONTAINER (button), page); +// a_Dw_widget_set_button_sensitive (DW_WIDGET (page), FALSE); +// style_attrs.margin.setVal (0); +// +// a_Dw_button_set_sensitive (DW_BUTTON (button), FALSE); +// +// S_TOP(html)->textblock = html->dw = page; +// +// /* Handle it when the user clicks on a link */ +// Html_connect_signals(html, GTK_OBJECT(page)); +// +// /* Connect it to the form */ +// html_lb = html->linkblock; +// form = html_lb->forms->getRef (html_lb->forms->size() - 1); +// +// type = Html_get_attr_wdef(html, tag, tagsize, "type", ""); +// +// if (strcmp(type, "submit") == 0) { +// inp_type = DILLO_HTML_INPUT_BUTTON_SUBMIT; +// gtk_signal_connect(GTK_OBJECT(button), "clicked", +// GTK_SIGNAL_FUNC(Html_submit_form), html_lb); +// } else if (strcmp(type, "reset") == 0) { +// inp_type = DILLO_HTML_INPUT_BUTTON_RESET; +// gtk_signal_connect(GTK_OBJECT(button), "clicked", +// GTK_SIGNAL_FUNC(Html_reset_form), html_lb); +// } else +// return; +// +// value = Html_get_attr_wdef(html, tag, tagsize, "value", NULL); +// name = Html_get_attr_wdef(html, tag, tagsize, "name", NULL); +// +// Html_add_input(form, inp_type, (GtkWidget*)button, name, value, +// NULL, FALSE); +// +// dFree(type); +// dFree(name); +// dFree(value); +} + + +/* + * <FONT> + */ +static void Html_tag_open_font(DilloHtml *html, char *tag, int tagsize) +{ +#if 1 + StyleAttrs style_attrs; + Style *old_style; + /*Font font;*/ + const char *attrbuf; + int32_t color; + + if (!prefs.force_my_colors) { + old_style = S_TOP(html)->style; + style_attrs = *old_style; + + if ((attrbuf = Html_get_attr(html, tag, tagsize, "color"))) { + if (prefs.contrast_visited_color && html->InVisitedLink) { + color = html->linkblock->visited_color; + } else { + /* use the tag-specified color */ + color = Html_color_parse( + html, attrbuf, style_attrs.color->getColor()); + style_attrs.color = Color::createSimple (HT2LT(html), color); + } + } + +#if 0 + //if ((attrbuf = Html_get_attr(html, tag, tagsize, "face"))) { + // font = *( style_attrs.font ); + // font.name = attrbuf; + // style_attrs.font = a_Dw_style_font_new_from_list (&font); + //} +#endif + + S_TOP(html)->style = + Style::create (HT2LT(html), &style_attrs); + old_style->unref (); + } + +#endif +} + +/* + * <ABBR> + */ +static void Html_tag_open_abbr(DilloHtml *html, char *tag, int tagsize) +{ +// DwTooltip *tooltip; +// const char *attrbuf; +// +// if ((attrbuf = Html_get_attr(html, tag, tagsize, "title"))) { +// tooltip = a_Dw_tooltip_new_no_ref(attrbuf); +// HTML_SET_TOP_ATTR(html, x_tooltip, tooltip); +// } +} + +/* + * <B> + */ +static void Html_tag_open_b(DilloHtml *html, char *tag, int tagsize) +{ + Html_set_top_font(html, NULL, 0, 1, 1); +} + +/* + * <STRONG> + */ +static void Html_tag_open_strong(DilloHtml *html, char *tag, int tagsize) +{ + Html_set_top_font(html, NULL, 0, 1, 1); +} + +/* + * <I> + */ +static void Html_tag_open_i(DilloHtml *html, char *tag, int tagsize) +{ + Html_set_top_font(html, NULL, 0, 2, 2); +} + +/* + * <EM> + */ +static void Html_tag_open_em(DilloHtml *html, char *tag, int tagsize) +{ + Html_set_top_font(html, NULL, 0, 2, 2); +} + +/* + * <CITE> + */ +static void Html_tag_open_cite(DilloHtml *html, char *tag, int tagsize) +{ + Html_set_top_font(html, NULL, 0, 2, 2); +} + +/* + * <CENTER> + */ +static void Html_tag_open_center(DilloHtml *html, char *tag, int tagsize) +{ + DW2TB(html->dw)->addParbreak (0, S_TOP(html)->style); + HTML_SET_TOP_ATTR(html, textAlign, TEXT_ALIGN_CENTER); +} + +/* + * <ADDRESS> + */ +static void Html_tag_open_address(DilloHtml *html, char *tag, int tagsize) +{ + DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style); + Html_set_top_font(html, NULL, 0, 2, 2); +} + +/* + * <TT> + */ +static void Html_tag_open_tt(DilloHtml *html, char *tag, int tagsize) +{ + Html_set_top_font(html, prefs.fw_fontname, 0, 0, 0); +} + +/* + * Read image-associated tag attributes, + * create new image and add it to the html page (if add is TRUE). + */ +static DilloImage *Html_add_new_image(DilloHtml *html, char *tag, + int tagsize, StyleAttrs *style_attrs, + bool_t add) +{ + const int MAX_W = 6000, MAX_H = 6000; + + DilloImage *Image; + char *width_ptr, *height_ptr, *alt_ptr; + const char *attrbuf; + Length l_w, l_h; + int space, w = 0, h = 0; + +// if (prefs.show_tooltip && +// (attrbuf = Html_get_attr(html, tag, tagsize, "title"))) +// style_attrs->x_tooltip = a_Dw_tooltip_new_no_ref(attrbuf); + + alt_ptr = Html_get_attr_wdef(html, tag, tagsize, "alt", NULL); + width_ptr = Html_get_attr_wdef(html, tag, tagsize, "width", NULL); + height_ptr = Html_get_attr_wdef(html, tag, tagsize, "height", NULL); + // Check for malicious values + // TODO: the same for percentage and relative lengths. + if (width_ptr) { + l_w = Html_parse_length (html, width_ptr); + w = isAbsLength(l_w) ? absLengthVal(l_w) : 0; + } + if (height_ptr) { + l_h = Html_parse_length (html, height_ptr); + h = isAbsLength(l_h) ? absLengthVal(l_h) : 0; + } + if (w < 0 || h < 0 || abs(w*h) > MAX_W * MAX_H) { + dFree(width_ptr); + dFree(height_ptr); + width_ptr = height_ptr = NULL; + MSG("Html_add_new_image: suspicious image size request %dx%d\n", w, h); + } + + /* todo: we should scale the image respecting its ratio. + * As the image size is not known at this time, maybe a flag + * can be set to scale it later. + if ((width_ptr && !height_ptr) || (height_ptr && !width_ptr)) + [...] + */ + + /* Spacing to the left and right */ + if ((attrbuf = Html_get_attr(html, tag, tagsize, "hspace"))) { + space = strtol(attrbuf, NULL, 10); + + if (space > 0) + style_attrs->margin.left = style_attrs->margin.right = space; + } + + /* Spacing at the top and bottom */ + if ((attrbuf = Html_get_attr(html, tag, tagsize, "vspace"))) { + space = strtol(attrbuf, NULL, 10); + + if (space > 0) + style_attrs->margin.top = style_attrs->margin.bottom = space; + } + + /* Add a new image widget to this page */ + Image = a_Image_new(0, 0, alt_ptr, S_TOP(html)->current_bg_color); + if (add) { + Html_add_widget(html, (Widget*)Image->dw, + width_ptr, height_ptr, style_attrs); + } + + dFree(width_ptr); + dFree(height_ptr); + dFree(alt_ptr); + return Image; +} + +/* + * Tell cache to retrieve image + */ +static void Html_load_image(DilloHtml *html, DilloUrl *url, DilloImage *Image) +{ + DilloWeb *Web; + int ClientKey; + /* Fill a Web structure for the cache query */ + Web = a_Web_new(url); + Web->bw = html->bw; + Web->Image = Image; + Web->flags |= WEB_Image; + /* Request image data from the cache */ + if ((ClientKey = a_Capi_open_url(Web, NULL, NULL)) != 0) { + a_Bw_add_client(html->bw, ClientKey, 0); + a_Bw_add_url(html->bw, url); + } +} + +/* + * Create a new Image struct and request the image-url to the cache + * (If it either hits or misses, is not relevant here; that's up to the + * cache functions) + */ +static void Html_tag_open_img(DilloHtml *html, char *tag, int tagsize) +{ + DilloImage *Image; + DilloUrl *url, *usemap_url; + Textblock *textblock; + StyleAttrs style_attrs; + const char *attrbuf; + int border; + + /* This avoids loading images. Useful for viewing suspicious HTML email. */ + if (URL_FLAGS(html->linkblock->base_url) & URL_SpamSafe) + return; + + if (!(attrbuf = Html_get_attr(html, tag, tagsize, "src")) || + !(url = Html_url_new(html, attrbuf, NULL, 0, 0, 0, 0))) + return; + + textblock = DW2TB(html->dw); + + + usemap_url = NULL; + if ((attrbuf = Html_get_attr(html, tag, tagsize, "usemap"))) + /* todo: usemap URLs outside of the document are not used. */ + usemap_url = Html_url_new(html, attrbuf, NULL, 0, 0, 0, 0); + + style_attrs = *S_TOP(html)->style; + + if (S_TOP(html)->style->x_link != -1 || + usemap_url != NULL) { + /* Images within links */ + border = 1; + if ((attrbuf = Html_get_attr(html, tag, tagsize, "border"))) + border = strtol (attrbuf, NULL, 10); + + if (S_TOP(html)->style->x_link != -1) { + /* In this case we can use the text color */ + style_attrs.setBorderColor ( + Color::createShaded (HT2LT(html), + style_attrs.color->getColor())); + } else { + style_attrs.setBorderColor ( + Color::createShaded (HT2LT(html), + html->linkblock->link_color)); + } + style_attrs.setBorderStyle (BORDER_SOLID); + style_attrs.borderWidth.setVal (border); + } + + Image = Html_add_new_image(html, tag, tagsize, &style_attrs, TRUE); + // Html_connect_signals(html, GTK_OBJECT(Image->dw)); +// gtk_signal_connect_after(GTK_OBJECT(Image->dw), "button_press_event", +// GTK_SIGNAL_FUNC(Html_image_menu), html->bw); + +#if 0 + /* Image maps */ + if (Html_get_attr(html, tag, tagsize, "ismap")) { + /* BUG: if several ISMAP images follow each other without + * being separated with a word, only the first one is ISMAPed + */ +// a_Dw_image_set_ismap (Image->dw); + _MSG(" Html_tag_open_img: server-side map (ISMAP)\n"); + } else if (S_TOP(html)->style->x_link != -1 && + usemap_url == NULL) + /* For simple links, we have to suppress the "image_pressed" signal. + * This is overridden for USEMAP images. */ +// a_Dw_widget_set_button_sensitive (IM2DW(Image->dw), FALSE); + + if (usemap_url) { +// a_Dw_image_set_usemap (Image->dw, &html->linkblock->maps, usemap_url); + a_Url_free (usemap_url); + } +#endif + + Html_load_image(html, url, Image); + a_Url_free(url); +} + +/* + * <map> + */ +static void Html_tag_open_map(DilloHtml *html, char *tag, int tagsize) +{ + char *hash_name; + const char *attrbuf; + DilloUrl *url; + + if (html->InFlags & IN_MAP) { + MSG_HTML("nested <map>\n"); + } else { + if ((attrbuf = Html_get_attr(html, tag, tagsize, "name"))) { + hash_name = dStrconcat("#", attrbuf, NULL); + url = Html_url_new(html, hash_name, NULL, 0, 0, 0, 0); + //a_Dw_image_map_list_add_map (&html->linkblock->maps, url); + a_Url_free (url); + dFree(hash_name); + } + html->InFlags |= IN_MAP; + } +} + +/* + * Handle close <MAP> + */ +static void Html_tag_close_map(DilloHtml *html, int TagIdx) +{ + html->InFlags &= ~IN_MAP; + Html_pop_tag(html, TagIdx); +} + +/* + * <AREA> + */ +static void Html_tag_open_area(DilloHtml *html, char *tag, int tagsize) +{ +// // AL +// /* todo: point must be a dynamic array */ +// GdkPoint point[1024]; +// DilloUrl* url; +// const char *attrbuf; +// int type = DW_IMAGE_MAP_SHAPE_RECT; +// int nbpoints, link = -1; +// +// if ((attrbuf = Html_get_attr(html, tag, tagsize, "shape"))) { +// if (dStrcasecmp(attrbuf, "rect") == 0) +// type = DW_IMAGE_MAP_SHAPE_RECT; +// else if (dStrcasecmp(attrbuf, "circle") == 0) +// type = DW_IMAGE_MAP_SHAPE_CIRCLE; +// else if (dStrncasecmp(attrbuf, "poly", 4) == 0) +// type = DW_IMAGE_MAP_SHAPE_POLY; +// else +// type = DW_IMAGE_MAP_SHAPE_RECT; +// } +// /* todo: add support for coords in % */ +// if ((attrbuf = Html_get_attr(html, tag, tagsize, "coords"))) { +// /* Is this a valid poly ? +// * rect = x0,y0,x1,y1 => 2 +// * circle = x,y,r => 2 +// * poly = x0,y0,x1,y1,x2,y2 minimum => 3 */ +// nbpoints = Html_read_coords(html, attrbuf, point); +// } else +// return; +// +// if (Html_get_attr(html, tag, tagsize, "nohref")) { +// link = -1; +// _MSG("nohref"); +// } +// +// if ((attrbuf = Html_get_attr(html, tag, tagsize, "href"))) { +// url = Html_url_new(html, attrbuf, NULL, 0, 0, 0, 0); +// dReturn_if_fail ( url != NULL ); +// if ((attrbuf = Html_get_attr(html, tag, tagsize, "alt"))) +// a_Url_set_alt(url, attrbuf); +// +// link = Html_set_new_link(html, &url); +// } +// +// a_Dw_image_map_list_add_shape(&html->linkblock->maps, type, link, +// point, nbpoints); +} + + +/* + * Test and extract the link from a javascript instruction. + */ +static const char* Html_get_javascript_link(DilloHtml *html) +{ + size_t i; + char ch, *p1, *p2; + Dstr *Buf = html->attr_data; + + if (dStrncasecmp("javascript", Buf->str, 10) == 0) { + i = strcspn(Buf->str, "'\""); + ch = Buf->str[i]; + if ((ch == '"' || ch == '\'') && + (p2 = strchr(Buf->str + i + 1 , ch))) { + p1 = Buf->str + i; + MSG_HTML("link depends on javascript()\n"); + dStr_truncate(Buf, p2 - Buf->str); + dStr_erase(Buf, 0, p1 - Buf->str + 1); + } + } + return Buf->str; +} + +/* + * Register an anchor for this page. + */ +static void Html_add_anchor(DilloHtml *html, const char *name) +{ + _MSG("Registering ANCHOR: %s\n", name); + if (!DW2TB(html->dw)->addAnchor (name, S_TOP(html)->style)) + MSG_HTML("Anchor names must be unique within the document\n"); + /* + * According to Sec. 12.2.1 of the HTML 4.01 spec, "anchor names that + * differ only in case may not appear in the same document", but + * "comparisons between fragment identifiers and anchor names must be + * done by exact (case-sensitive) match." We ignore the case issue and + * always test for exact matches. Moreover, what does uppercase mean + * for Unicode characters outside the ASCII range? + */ +} + +/* + * <A> + */ +static void Html_tag_open_a(DilloHtml *html, char *tag, int tagsize) +{ + StyleAttrs style_attrs; + Style *old_style; + DilloUrl *url; + const char *attrbuf; + char *buf; + int buf_size; + + /* todo: add support for MAP with A HREF */ + Html_tag_open_area(html, tag, tagsize); + + if ((attrbuf = Html_get_attr(html, tag, tagsize, "href"))) { + /* if it's a javascript link, extract the reference. */ + if (tolower(attrbuf[0]) == 'j') + attrbuf = Html_get_javascript_link(html); + + url = Html_url_new(html, attrbuf, NULL, 0, 0, 0, 0); + dReturn_if_fail ( url != NULL ); + + old_style = S_TOP(html)->style; + style_attrs = *old_style; + + if (a_Capi_get_buf(url, &buf, &buf_size)) { + html->InVisitedLink = TRUE; + style_attrs.color = Color::createSimple ( + HT2LT(html), + html->linkblock->visited_color +/* + a_Color_vc(html->linkblock->visited_color, + S_TOP(html)->style->color->getColor(), + html->linkblock->link_color, + S_TOP(html)->current_bg_color), +*/ + ); + } else { + style_attrs.color = Color::createSimple(HT2LT(html), + html->linkblock->link_color); + } + +// if ((attrbuf = Html_get_attr(html, tag, tagsize, "title"))) +// style_attrs.x_tooltip = a_Dw_tooltip_new_no_ref(attrbuf); + + style_attrs.textDecoration |= TEXT_DECORATION_UNDERLINE; + style_attrs.x_link = Html_set_new_link(html, &url); + style_attrs.cursor = CURSOR_POINTER; + + S_TOP(html)->style = + Style::create (HT2LT(html), &style_attrs); + old_style->unref (); + } + + if ((attrbuf = Html_get_attr(html, tag, tagsize, "name"))) { + if (prefs.show_extra_warnings) + Html_check_name_val(html, attrbuf, "name"); + /* html->NameVal is freed in Html_process_tag */ + html->NameVal = a_Url_decode_hex_str(attrbuf); + Html_add_anchor(html, html->NameVal); + } +} + +/* + * <A> close function + */ +static void Html_tag_close_a(DilloHtml *html, int TagIdx) +{ + html->InVisitedLink = FALSE; + Html_pop_tag(html, TagIdx); +} + +/* + * Insert underlined text in the page. + */ +static void Html_tag_open_u(DilloHtml *html, char *tag, int tagsize) +{ + Style *style; + StyleAttrs style_attrs; + + style = S_TOP(html)->style; + style_attrs = *style; + style_attrs.textDecoration |= TEXT_DECORATION_UNDERLINE; + S_TOP(html)->style = + Style::create (HT2LT(html), &style_attrs); + style->unref (); +} + +/* + * Insert strike-through text. Used by <S>, <STRIKE> and <DEL>. + */ +static void Html_tag_open_strike(DilloHtml *html, char *tag, int tagsize) +{ + Style *style; + StyleAttrs style_attrs; + + style = S_TOP(html)->style; + style_attrs = *style; + style_attrs.textDecoration |= TEXT_DECORATION_LINE_THROUGH; + S_TOP(html)->style = + Style::create (HT2LT(html), &style_attrs); + style->unref (); +} + +/* + * <BLOCKQUOTE> + */ +static void Html_tag_open_blockquote(DilloHtml *html, char *tag, int tagsize) +{ + DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style); + Html_add_indented(html, 40, 40, 9); +} + +/* + * Handle the <UL> tag. + */ +static void Html_tag_open_ul(DilloHtml *html, char *tag, int tagsize) +{ + const char *attrbuf; + ListStyleType list_style_type; + + DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style); + Html_add_indented(html, 40, 0, 9); + + if ((attrbuf = Html_get_attr(html, tag, tagsize, "type"))) { + /* list_style_type explicitly defined */ + if (dStrncasecmp(attrbuf, "disc", 4) == 0) + list_style_type = LIST_STYLE_TYPE_DISC; + else if (dStrncasecmp(attrbuf, "circle", 6) == 0) + list_style_type = LIST_STYLE_TYPE_CIRCLE; + else if (dStrncasecmp(attrbuf, "square", 6) == 0) + list_style_type = LIST_STYLE_TYPE_SQUARE; + else + /* invalid value */ + list_style_type = LIST_STYLE_TYPE_DISC; + } else { + if (S_TOP(html)->list_type == HTML_LIST_UNORDERED) { + /* Nested <UL>'s. */ + /* --EG :: I changed the behavior here : types are cycling instead of + * being forced to square. It's easier for mixed lists level counting. + */ + switch (S_TOP(html)->style->listStyleType) { + case LIST_STYLE_TYPE_DISC: + list_style_type = LIST_STYLE_TYPE_CIRCLE; + break; + case LIST_STYLE_TYPE_CIRCLE: + list_style_type = LIST_STYLE_TYPE_SQUARE; + break; + case LIST_STYLE_TYPE_SQUARE: + default: /* this is actually a bug */ + list_style_type = LIST_STYLE_TYPE_DISC; + break; + } + } else { + /* Either first <UL>, or a <OL> before. */ + list_style_type = LIST_STYLE_TYPE_DISC; + } + } + + HTML_SET_TOP_ATTR(html, listStyleType, list_style_type); + S_TOP(html)->list_type = HTML_LIST_UNORDERED; + + S_TOP(html)->list_number = 0; + S_TOP(html)->ref_list_item = NULL; +} + +/* + * Handle the <MENU> tag. + * (Deprecated and almost the same as <UL>) + */ +static void Html_tag_open_menu(DilloHtml *html, char *tag, int tagsize) +{ + ListStyleType list_style_type = LIST_STYLE_TYPE_DISC; + + DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style); + Html_add_indented(html, 40, 0, 9); + HTML_SET_TOP_ATTR(html, listStyleType, list_style_type); + S_TOP(html)->list_type = HTML_LIST_UNORDERED; + S_TOP(html)->list_number = 0; + S_TOP(html)->ref_list_item = NULL; + + if (prefs.show_extra_warnings) + MSG_HTML("it is strongly recommended using <UL> instead of <MENU>\n"); +} + +/* + * Handle the <OL> tag. + */ +static void Html_tag_open_ol(DilloHtml *html, char *tag, int tagsize) +{ + const char *attrbuf; + ListStyleType list_style_type; + int n = 1; + + DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style); + Html_add_indented(html, 40, 0, 9); + + list_style_type = LIST_STYLE_TYPE_DECIMAL; + + if ((attrbuf = Html_get_attr(html, tag, tagsize, "type"))) { + if (*attrbuf == '1') + list_style_type = LIST_STYLE_TYPE_DECIMAL; + else if (*attrbuf == 'a') + list_style_type = LIST_STYLE_TYPE_LOWER_ALPHA; + else if (*attrbuf == 'A') + list_style_type = LIST_STYLE_TYPE_UPPER_ALPHA; + else if (*attrbuf == 'i') + list_style_type = LIST_STYLE_TYPE_LOWER_ROMAN; + else if (*attrbuf == 'I') + list_style_type = LIST_STYLE_TYPE_UPPER_ROMAN; + } + + HTML_SET_TOP_ATTR(html, listStyleType, list_style_type); + S_TOP(html)->list_type = HTML_LIST_ORDERED; + + if ((attrbuf = Html_get_attr(html, tag, tagsize, "start")) && + (n = (int) strtol(attrbuf, NULL, 10)) < 0) { + MSG_HTML( "illegal '-' character in START attribute; Starting from 0\n"); + n = 0; + } + S_TOP(html)->list_number = n; + S_TOP(html)->ref_list_item = NULL; +} + +/* + * Handle the <LI> tag. + */ +static void Html_tag_open_li(DilloHtml *html, char *tag, int tagsize) +{ + StyleAttrs style_attrs; + Style *item_style, *word_style; + Widget **ref_list_item; + ListItem *list_item; + int *list_number; + const char *attrbuf; + char buf[16]; + + /* Get our parent tag's variables (used as state storage) */ + list_number = &html->stack->getRef(html->stack->size()-2)->list_number; + ref_list_item = &html->stack->getRef(html->stack->size()-2)->ref_list_item; + + /* set the item style */ + word_style = S_TOP(html)->style; + style_attrs = *word_style; + //style_attrs.backgroundColor = Color::createSimple (HT2LT(html), 0xffff40); + //style_attrs.setBorderColor (Color::createSimple (HT2LT(html), 0x000000)); + //style_attrs.setBorderStyle (BORDER_SOLID); + //style_attrs.borderWidth.setVal (1); + item_style = Style::create (HT2LT(html), &style_attrs); + + DW2TB(html->dw)->addParbreak (2, word_style); + + list_item = new ListItem ((ListItem *)*ref_list_item, false); + DW2TB(html->dw)->addWidget (list_item, item_style); + DW2TB(html->dw)->addParbreak (2, word_style); + *ref_list_item = list_item; + S_TOP(html)->textblock = html->dw = list_item; + item_style->unref(); + + switch (S_TOP(html)->list_type) { + case HTML_LIST_ORDERED: + if ((attrbuf = Html_get_attr(html, tag, tagsize, "value")) && + (*list_number = strtol(attrbuf, NULL, 10)) < 0) { + MSG_HTML("illegal negative LIST VALUE attribute; Starting from 0\n"); + *list_number = 0; + } + numtostr((*list_number)++, buf, 16, S_TOP(html)->style->listStyleType); + list_item->initWithText (dStrdup(buf), word_style); + list_item->addSpace (word_style); + break; + case HTML_LIST_NONE: + MSG_HTML("<li> outside <ul> or <ol>\n"); + default: + list_item->initWithWidget (new Bullet(), word_style); + list_item->addSpace (word_style); + break; + } + + // list_item->flush (); Looks like there's no need to flush. + // If it MUST be done, it could be inside Html_tag_close_li(). +} + +/* + * Close <LI> + */ +static void Html_tag_close_li(DilloHtml *html, int TagIdx) +{ + ((ListItem *)html->dw)->flush (); + Html_pop_tag(html, TagIdx); +} + +/* + * <HR> + */ +static void Html_tag_open_hr(DilloHtml *html, char *tag, int tagsize) +{ + Widget *hruler; + StyleAttrs style_attrs; + Style *style; + char *width_ptr; + const char *attrbuf; + int32_t size = 0; + + width_ptr = Html_get_attr_wdef(html, tag, tagsize, "width", "100%"); + + style_attrs = *S_TOP(html)->style; + + if ((attrbuf = Html_get_attr(html, tag, tagsize, "size"))) + size = strtol(attrbuf, NULL, 10); + + if ((attrbuf = Html_get_attr(html, tag, tagsize, "align"))) { + if (dStrcasecmp (attrbuf, "left") == 0) + style_attrs.textAlign = TEXT_ALIGN_LEFT; + else if (dStrcasecmp (attrbuf, "right") == 0) + style_attrs.textAlign = TEXT_ALIGN_RIGHT; + else if (dStrcasecmp (attrbuf, "center") == 0) + style_attrs.textAlign = TEXT_ALIGN_CENTER; + } + + /* todo: evaluate attribute */ + if (Html_get_attr(html, tag, tagsize, "noshade")) { + style_attrs.setBorderStyle (BORDER_SOLID); + style_attrs.setBorderColor ( + Color::createShaded (HT2LT(html), style_attrs.color->getColor())); + if (size < 1) + size = 1; + } else { + style_attrs.setBorderStyle (BORDER_INSET); + style_attrs.setBorderColor ( + Color::createShaded (HT2LT(html), + (S_TOP(html)->current_bg_color))); + if (size < 2) + size = 2; + } + + style_attrs.borderWidth.top = + style_attrs.borderWidth.left = (size + 1) / 2; + style_attrs.borderWidth.bottom = + style_attrs.borderWidth.right = size / 2; + style = Style::create (HT2LT(html), &style_attrs); + + DW2TB(html->dw)->addParbreak (5, S_TOP(html)->style); + hruler = new Ruler(); + hruler->setStyle (style); + DW2TB(html->dw)->addWidget (hruler, style); + style->unref (); + + //DW2TB(html->dw)->addWidget (hruler, S_TOP(html)->style); + DW2TB(html->dw)->addParbreak (5, S_TOP(html)->style); + dFree(width_ptr); +} + +/* + * <DL> + */ +static void Html_tag_open_dl(DilloHtml *html, char *tag, int tagsize) +{ + /* may want to actually do some stuff here. */ + DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style); +} + +/* + * <DT> + */ +static void Html_tag_open_dt(DilloHtml *html, char *tag, int tagsize) +{ + DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style); + Html_set_top_font(html, NULL, 0, 1, 1); +} + +/* + * <DD> + */ +static void Html_tag_open_dd(DilloHtml *html, char *tag, int tagsize) +{ + DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style); + Html_add_indented(html, 40, 40, 9); +} + +/* + * <PRE> + */ +static void Html_tag_open_pre(DilloHtml *html, char *tag, int tagsize) +{ + DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style); + Html_set_top_font(html, prefs.fw_fontname, 0, 0, 0); + + /* Is the placement of this statement right? */ + S_TOP(html)->parse_mode = DILLO_HTML_PARSE_MODE_PRE; + HTML_SET_TOP_ATTR (html, whiteSpace, WHITE_SPACE_PRE); + html->pre_column = 0; + html->PreFirstChar = TRUE; + html->InFlags |= IN_PRE; +} + +/* + * Custom close for <PRE> + */ +static void Html_tag_close_pre(DilloHtml *html, int TagIdx) +{ + html->InFlags &= ~IN_PRE; + DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style); + Html_pop_tag(html, TagIdx); +} + +/* + * Check whether a tag is in the "excluding" element set for PRE + * Excl. Set = {IMG, OBJECT, APPLET, BIG, SMALL, SUB, SUP, FONT, BASEFONT} + */ +static int Html_tag_pre_excludes(int tag_idx) +{ + char *es_set[] = {"img", "object", "applet", "big", "small", "sub", "sup", + "font", "basefont", NULL}; + static int ei_set[10], i; + + /* initialize array */ + if (!ei_set[0]) + for (i = 0; es_set[i]; ++i) + ei_set[i] = Html_tag_index(es_set[i]); + + for (i = 0; ei_set[i]; ++i) + if (tag_idx == ei_set[i]) + return 1; + return 0; +} + +/* + * Handle <FORM> tag + */ +static void Html_tag_open_form(DilloHtml *html, char *tag, int tagsize) +{ + DilloUrl *action; + DilloHtmlMethod method; + DilloHtmlEnc enc; + const char *attrbuf; + + DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style); + + if (html->InFlags & IN_FORM) { + MSG_HTML("nested forms\n"); + return; + } + html->InFlags |= IN_FORM; + + method = DILLO_HTML_METHOD_GET; + if ((attrbuf = Html_get_attr(html, tag, tagsize, "method"))) { + if (!dStrcasecmp(attrbuf, "post")) + method = DILLO_HTML_METHOD_POST; + /* todo: maybe deal with unknown methods? */ + } + if ((attrbuf = Html_get_attr(html, tag, tagsize, "action"))) + action = Html_url_new(html, attrbuf, NULL, 0, 0, 0, 0); + else + action = a_Url_dup(html->linkblock->base_url); + enc = DILLO_HTML_ENC_URLENCODING; + if ((attrbuf = Html_get_attr(html, tag, tagsize, "encoding"))) { + /* todo: maybe deal with unknown encodings? */ + } + Html_form_new(html->linkblock, method, action, enc); + a_Url_free(action); +} + +static void Html_tag_close_form(DilloHtml *html, int TagIdx) +{ + static char *SubmitTag = + "<input type='submit' value='?Submit?' alt='dillo-generated-button'>"; + DilloHtmlForm *form; +// int i; + + if (html->InFlags & IN_FORM) { + form = html->linkblock->forms->getRef ( + html->linkblock->forms->size() - 1); + /* If we don't have a submit button and the user desires one, + let's add a custom one */ + if (form->num_submit_buttons == 0) { + if (prefs.show_extra_warnings || form->num_entry_fields != 1) + MSG_HTML("FORM lacks a Submit button\n"); + if (prefs.generate_submit) { + MSG_HTML(" (added a submit button internally)\n"); + Html_tag_open_input(html, SubmitTag, strlen(SubmitTag)); + form->num_submit_buttons = 0; + } + } + +// /* Make buttons sensitive again */ +// for (i = 0; i < form->inputs->size(); i++) { +// input_i = form->inputs->getRef(i); +// /* Check for tricky HTML (e.g. <input type=image>) */ +// if (!input_i->widget) +// continue; +// if (input_i->type == DILLO_HTML_INPUT_SUBMIT || +// input_i->type == DILLO_HTML_INPUT_RESET) { +// gtk_widget_set_sensitive(input_i->widget, TRUE); +// } else if (input_i->type == DILLO_HTML_INPUT_IMAGE || +// input_i->type == DILLO_HTML_INPUT_BUTTON_SUBMIT || +// input_i->type == DILLO_HTML_INPUT_BUTTON_RESET) { +// a_Dw_button_set_sensitive(DW_BUTTON(input_i->widget), TRUE); +// } +// } + } + html->InFlags &= ~IN_FORM; + html->InFlags &= ~IN_SELECT; + html->InFlags &= ~IN_TEXTAREA; + Html_pop_tag(html, TagIdx); +} + +/* + * Handle <META> + * We do not support http-equiv=refresh because it's non standard, + * (the HTML 4.01 SPEC recommends explicitily to avoid it), and it + * can be easily abused! + * + * More info at: + * http://lists.w3.org/Archives/Public/www-html/2000Feb/thread.html#232 + * + * todo: Note that we're sending custom HTML while still IN_HEAD. This + * is a hackish way to put the message. A much cleaner approach is to + * build a custom widget for it. + */ +static void Html_tag_open_meta(DilloHtml *html, char *tag, int tagsize) +{ + const char *meta_template = +"<table width='100%%'><tr><td bgcolor='#ee0000'>Warning:</td>\n" +" <td bgcolor='#8899aa' width='100%%'>\n" +" This page uses the NON-STANDARD meta refresh tag.<br> The HTML 4.01 SPEC\n" +" (sec 7.4.4) recommends explicitly to avoid it.</td></tr>\n" +" <tr><td bgcolor='#a0a0a0' colspan='2'>The author wanted you to go\n" +" <a href='%s'>here</a>%s</td></tr></table><br>\n"; + + const char *equiv, *content; + char delay_str[64]; + Dstr *ds_msg; + int delay; + + /* only valid inside HEAD */ + if (!(html->InFlags & IN_HEAD)) { + MSG_HTML("META elements must be inside the HEAD section\n"); + return; + } + + if ((equiv = Html_get_attr(html, tag, tagsize, "http-equiv")) && + !dStrcasecmp(equiv, "refresh") && + (content = Html_get_attr(html, tag, tagsize, "content"))) { + + /* Get delay, if present, and make a message with it */ + if ((delay = strtol(content, NULL, 0))) + snprintf(delay_str, 64, " after %d second%s.", + delay, (delay > 1) ? "s" : ""); + else + sprintf(delay_str, "."); + + /* Skip to anything after "URL=" */ + while (*content && *(content++) != '='); + + /* Send a custom HTML message + * todo: this is a hairy hack, It'd be much better to build a widget. */ + ds_msg = dStr_sized_new(256); + dStr_sprintf(ds_msg, meta_template, content, delay_str); + { + int SaveFlags = html->InFlags; + html->InFlags = IN_BODY; + html->TagSoup = FALSE; + Html_write_raw(html, ds_msg->str, ds_msg->len, 0); + html->TagSoup = TRUE; + html->InFlags = SaveFlags; + } + dStr_free(ds_msg, 1); + } +} + +/* + * Set the history of the menu to be consistent with the active menuitem. + */ +//static void Html_select_set_history(DilloHtmlInput *input) +//{ +// int i; +// +// for (i = 0; i < input->select->num_options; i++) { +// if (GTK_CHECK_MENU_ITEM(input->select->options[i].menuitem)->active) { +// gtk_option_menu_set_history(GTK_OPTION_MENU(input->widget), i); +// break; +// } +// } +//} + +/* + * Reset the input widget to the initial value. + */ +static void Html_reset_input(DilloHtmlInput *input) +{ + int i; + + switch (input->type) { + case DILLO_HTML_INPUT_TEXT: + case DILLO_HTML_INPUT_PASSWORD: + EntryResource *entryres; + entryres = (EntryResource*)((Embed*)input->widget)->getResource(); + entryres->setText(input->init_str ? input->init_str : ""); + break; + case DILLO_HTML_INPUT_CHECKBOX: + case DILLO_HTML_INPUT_RADIO: + ToggleButtonResource *tb_r; + tb_r = (ToggleButtonResource*)((Embed*)input->widget)->getResource(); + tb_r->setActivated(input->init_val); + break; + case DILLO_HTML_INPUT_SELECT: + if (input->select != NULL) { + /* this is in reverse order so that, in case more than one was + * selected, we get the last one, which is consistent with handling + * of multiple selected options in the layout code. */ + for (i = input->select->num_options - 1; i >= 0; i--) { + if (input->select->options[i].init_val) { +// gtk_menu_item_activate(GTK_MENU_ITEM +// (input->select->options[i].menuitem)); +// Html_select_set_history(input); + break; + } + } + } + break; + case DILLO_HTML_INPUT_SEL_LIST: + if (!input->select) + break; + for (i = 0; i < input->select->num_options; i++) { +// if (input->select->options[i].init_val) { +// if (input->select->options[i].menuitem->state == GTK_STATE_NORMAL) +// gtk_list_select_child(GTK_LIST(input->select->menu), +// input->select->options[i].menuitem); +// } else { +// if (input->select->options[i].menuitem->state==GTK_STATE_SELECTED) +// gtk_list_unselect_child(GTK_LIST(input->select->menu), +// input->select->options[i].menuitem); +// } + } + break; + case DILLO_HTML_INPUT_TEXTAREA: + if (input->init_str != NULL) { +// int pos = 0; +// gtk_editable_delete_text(GTK_EDITABLE(input->widget), 0, -1); +// gtk_editable_insert_text(GTK_EDITABLE(input->widget), input->init_str, +// strlen(input->init_str), &pos); + } + break; + default: + break; + } +} + + +/* + * Add a new input to the form data structure, setting the initial + * values. + */ +static void Html_add_input(DilloHtmlForm *form, + DilloHtmlInputType type, + Widget *widget, + Embed *embed, + const char *name, + const char *init_str, + DilloHtmlSelect *select, + bool_t init_val) +{ + DilloHtmlInput *input; + + _MSG("name=[%s] init_str=[%s] init_val=[%d]\n", + name, init_str, init_val); + form->inputs->increase(); + input = form->inputs->getRef (form->inputs->size() - 1); + input->type = type; + input->widget = widget; + input->embed = embed; + input->name = (name) ? dStrdup(name) : NULL; + input->init_str = (init_str) ? dStrdup(init_str) : NULL; + input->select = select; + input->init_val = init_val; + Html_reset_input(input); + + /* some stats */ + if (type == DILLO_HTML_INPUT_PASSWORD || + type == DILLO_HTML_INPUT_TEXT || + type == DILLO_HTML_INPUT_TEXTAREA) { + form->num_entry_fields++; + } else if (type == DILLO_HTML_INPUT_SUBMIT || + type == DILLO_HTML_INPUT_BUTTON_SUBMIT || + type == DILLO_HTML_INPUT_IMAGE) { + form->num_submit_buttons++; + } +} + + +/* + * Given a GtkWidget, find the form that contains it. + * Return value: form_index if successful, -1 otherwise. + */ +//static int Html_find_form(GtkWidget *reset, DilloHtmlLB *html_lb) +//{ +// int form_index; +// int input_idx; +// DilloHtmlForm *form; +// +// for (form_index = 0; form_index < html_lb->forms->size(); form_index++) { +// form = html_lb->forms->getRef (form_index); +// for (input_idx = 0; input_idx < form->inputs->size(); input_idx++) { +// if (form->inputs->get(input_idx).widget == reset) { +// return form_index; +// } +// } +// } +// return -1; +//} + +/* + * Reset all inputs in the form containing reset to their initial values. + * In general, reset is the reset button for the form. + */ +//static void Html_reset_form(GtkWidget *reset, DilloHtmlLB *html_lb) +//{ +// int i, j; +// DilloHtmlForm *form; +// +// if ((i = Html_find_form(reset, html_lb)) != -1){ +// form = html_lb->forms->getRef (i); +// for ( j = 0; j < form->inputs->size(); j++) +// Html_reset_input(&(form->inputs[j])); +// } +//} + +/* + * Urlencode 'val' and append it to 'str' + */ +static void Html_urlencode_append(Dstr *str, const char *val) +{ + char *enc_val = a_Url_encode_hex_str(val); + dStr_append(str, enc_val); + dFree(enc_val); +} + +/* + * Append a name-value pair to an existing url. + * (name and value are urlencoded before appending them) + */ +static void + Html_append_input(Dstr *url, const char *name, const char *value) +{ + if (name != NULL) { + Html_urlencode_append(url, name); + dStr_append_c(url, '='); + Html_urlencode_append(url, value); + dStr_append_c(url, '&'); + } +} + +/* + * Append a image button click position to an existing url. + */ +//static void Html_append_clickpos(Dstr *url, const char *name, int x, int y) +//{ +// if (name) { +// Html_urlencode_append(url, name); +// dStr_sprintfa(url, ".x=%d&", x); +// Html_urlencode_append(url, name); +// dStr_sprintfa(url, ".y=%d&", y); +// } else +// dStr_sprintfa(url, "x=%d&y=%d&", x, y); +//} + +/* + * Submit the form containing the submit input by making a new query URL + * and sending it with a_Nav_push. + * (Called by GTK+) + * click_x and click_y are used only by input images and are set only when + * called by Html_image_clicked. GTK+ does NOT give these arguments. + */ +static void Html_submit_form2(DilloHtmlLB *html_lb, DilloHtmlForm *form, + int e_input_idx) +{ + int input_idx; + DilloHtmlInput *input; + DilloUrl *new_url; + char *url_str, *action_str, *p; + + if ((form->method == DILLO_HTML_METHOD_GET) || + (form->method == DILLO_HTML_METHOD_POST)) { + Dstr *DataStr = dStr_sized_new(4096); + + _MSG("Html_submit_form2: form->action=%s\n",URL_STR_(form->action)); + + for (input_idx = 0; input_idx < form->inputs->size(); input_idx++) { + input = form->inputs->getRef (input_idx); + switch (input->type) { + case DILLO_HTML_INPUT_TEXT: + case DILLO_HTML_INPUT_PASSWORD: + EntryResource *entryres; + entryres = (EntryResource*)((Embed*)input->widget)->getResource(); + Html_append_input(DataStr, input->name, entryres->getText()); + break; + case DILLO_HTML_INPUT_CHECKBOX: + case DILLO_HTML_INPUT_RADIO: + ToggleButtonResource *cb_r; + cb_r=(ToggleButtonResource*)((Embed*)input->widget)->getResource(); + if (input->name && input->init_str && cb_r->isActivated()) { + Html_append_input(DataStr, input->name, input->init_str); + } + break; + case DILLO_HTML_INPUT_HIDDEN: + Html_append_input(DataStr, input->name, input->init_str); + break; +// case DILLO_HTML_INPUT_SELECT: +// for (i = 0; i < input->select->num_options; i++) { +// if (GTK_CHECK_MENU_ITEM(input->select->options[i].menuitem)-> +// active) { +// Html_append_input(DataStr, input->name, +// input->select->options[i].value); +// break; +// } +// } +// break; +// case DILLO_HTML_INPUT_SEL_LIST: +// for (i = 0; i < input->select->num_options; i++) { +// if (input->select->options[i].menuitem->state == +// GTK_STATE_SELECTED) { +// Html_append_input(DataStr, input->name, +// input->select->options[i].value); +// } +// } +// break; +// case DILLO_HTML_INPUT_TEXTAREA: +// text = gtk_editable_get_chars(GTK_EDITABLE (input->widget),0,-1); +// Html_append_input(DataStr, input->name, text); +// dFree(text); +// break; +// case DILLO_HTML_INPUT_INDEX: +// Html_urlencode_append(DataStr, +// gtk_entry_get_text(GTK_ENTRY(input->widget))); +// break; +// case DILLO_HTML_INPUT_IMAGE: +// if (input->widget == submit) { +// Html_append_input(DataStr, input->name, input->init_str); +// Html_append_clickpos(DataStr, input->name, click_x, click_y); +// } +// break; + case DILLO_HTML_INPUT_SUBMIT: + case DILLO_HTML_INPUT_BUTTON_SUBMIT: + if (input_idx == e_input_idx && form->num_submit_buttons > 0) + Html_append_input(DataStr, input->name, input->init_str); + break; + default: + break; + } /* switch */ + } /* for (inputs) */ + + if (DataStr->str[DataStr->len - 1] == '&') + dStr_truncate(DataStr, DataStr->len - 1); + + /* form->action was previously resolved against base URL */ + action_str = dStrdup(URL_STR(form->action)); + + if (form->method == DILLO_HTML_METHOD_POST) { + new_url = a_Url_new(action_str, NULL, 0, 0, 0); + a_Url_set_data(new_url, DataStr->str); + a_Url_set_flags(new_url, URL_FLAGS(new_url) | URL_Post); + } else { + /* remove <fragment> and <query> sections if present */ + if ((p = strchr(action_str, '#'))) + *p = 0; + if ((p = strchr(action_str, '?'))) + *p = 0; + + url_str = dStrconcat(action_str, "?", DataStr->str, NULL); + new_url = a_Url_new(url_str, NULL, 0, 0, 0); + a_Url_set_flags(new_url, URL_FLAGS(new_url) | URL_Get); + dFree(url_str); + } + + a_Nav_push(html_lb->bw, new_url); + dFree(action_str); + dStr_free(DataStr, TRUE); + a_Url_free(new_url); + } else { + MSG("Html_submit_form2: Method unknown\n"); + } + +// /* now, make the rendered area have its focus back */ +// gtk_widget_grab_focus(GTK_BIN(html_lb->bw->render_main_scroll)->child); +} + +/* + * Handler for events related to forms. + * + * TODO: Currently there's "clicked" for buttons, we surely need "enter" for + * textentries, and maybe the "mouseover, ...." set for Javascript. + */ +void a_Html_form_event_handler(void *data, + form::Form *form_receiver, + void *v_resource) +{ + int form_index, input_idx = -1, idx; + DilloHtmlForm *form = NULL; + DilloHtmlLB *html_lb = (DilloHtmlLB*)data; + + printf("Html_form_event_handler %p %p\n", html_lb, form_receiver); + + /* Search the form that generated the submit event */ + for (form_index = 0; form_index < html_lb->forms->size(); form_index++) { + form = html_lb->forms->getRef (form_index); + if (form->form_receiver == form_receiver) { + /* form found, let's get the input index for this event */ + for (idx = 0; idx < form->inputs->size(); idx++) { + DilloHtmlInput *input = form->inputs->getRef(idx); + if (input->embed && + v_resource == (void*)((Embed*)input->widget)->getResource()) { + input_idx = idx; + break; + } + } + break; + } + } + if (form_index == html_lb->forms->size()) { + MSG("a_Html_form_event_handler: ERROR, form not found!\n"); + } else { + Html_submit_form2(html_lb, form, input_idx); + } +} + +/* + * Submit form if it has no submit button. + * (Called by GTK+ when the user presses enter in a text entry within a form) + */ +//static void Html_enter_submit_form(GtkWidget *submit, DilloHtmlLB *html_lb) +//{ +// int i; +// +// /* Search the form that generated the submit event */ +// if ((i = Html_find_form(submit, html_lb)) == -1) +// return; +// +// /* Submit on enterpress when there's a single text-entry only, +// * or if the user set enter to always submit */ +// if ((html_lb->forms->getRef(i))->.num_entry_fields == 1) || +// prefs.enterpress_forces_submit) +// Html_submit_form(submit, html_lb, 1, 1); +//} + +/* + * Call submit form, when input image has been clicked + */ +//static void Html_image_clicked(Widget *widget, int x, int y, +// DilloHtmlLB *lb) +//{ +// _MSG("Hallo! (%d, %d, %p)\n", x, y, lb); +// Html_submit_form((GtkWidget*) widget, lb, x, y); +//} + +/* + * Create input image for the form + */ +static Widget *Html_input_image(DilloHtml *html, char *tag, int tagsize, + DilloHtmlLB *html_lb, DilloUrl *action) +{ +// // AL +// DilloImage *Image; +// Widget *button; +// DilloUrl *url = NULL; +// Style style_attrs; +// const char *attrbuf; +// +// if ((attrbuf = Html_get_attr(html, tag, tagsize, "src")) && +// (url = Html_url_new(html, attrbuf, NULL, 0, 0, 0, 0))) { +// button = a_Dw_button_new (0, FALSE); +// DW2TB(html->dw)->addWidget (button, +// S_TOP(html)->style); +// gtk_signal_connect(GTK_OBJECT(button), "clicked_at", +// GTK_SIGNAL_FUNC(Html_image_clicked), html_lb); +// a_Dw_button_set_sensitive(DW_BUTTON(button), FALSE); +// +// /* create new image and add it to the button */ +// if ((Image = Html_add_new_image(html, tag, tagsize, &style_attrs, +// FALSE))) { +// /* By suppressing the "image_pressed" signal, the events are sent +// * to the parent DwButton */ +// a_Dw_widget_set_button_sensitive (IM2DW(Image->dw), FALSE); +// IM2DW(Image->dw)->setStyle (S_TOP(html)->style); +// a_Dw_container_add(DW_CONTAINER(button), IM2DW(Image->dw)); +// IM2DW(Image->dw)->setCursor (CURSOR_HAND); +// Html_load_image(html, url, Image); +// a_Url_free(url); +// return button; +// } +// } +// +// DEBUG_MSG(10, "Html_input_image: unable to create image submit.\n"); +// a_Url_free(url); + return NULL; +} + +/* + * Add a new input to current form + */ +static void Html_tag_open_input(DilloHtml *html, char *tag, int tagsize) +{ + DilloHtmlForm *form; + DilloHtmlInputType inp_type; + DilloHtmlLB *html_lb; + Widget *widget = NULL; + Embed *embed = NULL; + char *value, *name, *type, *init_str; +// const char *attrbuf, *label; + bool_t init_val = FALSE; + int input_idx; + + if (!(html->InFlags & IN_FORM)) { + MSG_HTML("input camp outside <form>\n"); + return; + } + + html_lb = html->linkblock; + form = html_lb->forms->getRef (html_lb->forms->size() - 1); + + /* Get 'value', 'name' and 'type' */ + value = Html_get_attr_wdef(html, tag, tagsize, "value", NULL); + name = Html_get_attr_wdef(html, tag, tagsize, "name", NULL); + type = Html_get_attr_wdef(html, tag, tagsize, "type", ""); + + init_str = NULL; + inp_type = DILLO_HTML_INPUT_UNKNOWN; + if (!dStrcasecmp(type, "password")) { + inp_type = DILLO_HTML_INPUT_PASSWORD; + EntryResource *entryResource = + HT2LT(html)->getResourceFactory()->createEntryResource (15, true); + embed = new Embed (entryResource); + widget = embed; + init_str = (value) ? value : NULL; + } else if (!dStrcasecmp(type, "checkbox")) { + inp_type = DILLO_HTML_INPUT_CHECKBOX; + CheckButtonResource *check_b_r = HT2LT(html)->getResourceFactory() + ->createCheckButtonResource(false); + embed = new Embed (check_b_r); + widget = embed; + init_val = (Html_get_attr(html, tag, tagsize, "checked") != NULL); + init_str = (value) ? value : dStrdup("on"); + } else if (!dStrcasecmp(type, "radio")) { + inp_type = DILLO_HTML_INPUT_RADIO; + RadioButtonResource *rb_r = NULL; + for (input_idx = 0; input_idx < form->inputs->size(); input_idx++) { + DilloHtmlInput *input = form->inputs->getRef(input_idx); + if (input->type == DILLO_HTML_INPUT_RADIO && + (input->name && !dStrcasecmp(input->name, name)) ) { + rb_r =(RadioButtonResource*)((Embed*)input->widget)->getResource(); + break; + } + } + rb_r = HT2LT(html)->getResourceFactory() + ->createRadioButtonResource(rb_r, false); + embed = new Embed (rb_r); + widget = embed; + + init_val = (Html_get_attr(html, tag, tagsize, "checked") != NULL); + init_str = (value) ? value : NULL; + } else if (!dStrcasecmp(type, "hidden")) { + inp_type = DILLO_HTML_INPUT_HIDDEN; + if (value) + init_str = dStrdup(Html_get_attr(html, tag, tagsize, "value")); + } else if (!dStrcasecmp(type, "submit")) { + inp_type = DILLO_HTML_INPUT_SUBMIT; + init_str = (value) ? value : dStrdup("submit"); + LabelButtonResource *label_b_r = HT2LT(html)->getResourceFactory() + ->createLabelButtonResource(init_str); + widget = embed = new Embed (label_b_r); +// gtk_widget_set_sensitive(widget, FALSE); /* Until end of FORM! */ + label_b_r->connectClicked (form->form_receiver); + } else if (!dStrcasecmp(type, "reset")) { + inp_type = DILLO_HTML_INPUT_RESET; + init_str = (value) ? value : dStrdup("Reset"); + LabelButtonResource *label_b_r = HT2LT(html)->getResourceFactory() + ->createLabelButtonResource(init_str); + widget = embed = new Embed (label_b_r); +// gtk_widget_set_sensitive(widget, FALSE); /* Until end of FORM! */ +// gtk_signal_connect(GTK_OBJECT(widget), "clicked", +// GTK_SIGNAL_FUNC(Html_reset_form), html_lb); +// } else if (!dStrcasecmp(type, "image")) { +// if (URL_FLAGS(html->linkblock->base_url) & URL_SpamSafe) { +// /* Don't request the image, make a text submit button instead */ +// inp_type = DILLO_HTML_INPUT_SUBMIT; +// attrbuf = Html_get_attr(html, tag, tagsize, "alt"); +// label = attrbuf ? attrbuf : value ? value : name ? name : "Submit"; +// init_str = dStrdup(label); +// widget = gtk_button_new_with_label(init_str); +// gtk_widget_set_sensitive(widget, FALSE); /* Until end of FORM! */ +// gtk_signal_connect(GTK_OBJECT(widget), "clicked", +// GTK_SIGNAL_FUNC(Html_submit_form), html_lb); +// } else { +// inp_type = DILLO_HTML_INPUT_IMAGE; +// /* use a dw_image widget */ +// widget = (GtkWidget*) Html_input_image(html, tag, tagsize, +// html_lb, form->action); +// init_str = value; +// } +// } else if (!dStrcasecmp(type, "file")) { +// /* todo: implement it! */ +// inp_type = DILLO_HTML_INPUT_FILE; +// init_str = (value) ? value : NULL; +// MSG("An input of the type \"file\" wasn't rendered!\n"); + } else if (!dStrcasecmp(type, "button")) { + inp_type = DILLO_HTML_INPUT_BUTTON; + if (value) { + init_str = value; + LabelButtonResource *label_b_r = HT2LT(html)->getResourceFactory() + ->createLabelButtonResource(init_str); + widget = embed = new Embed (label_b_r); + } + } else if (!dStrcasecmp(type, "text") || !*type) { + /* Text input, which also is the default */ + inp_type = DILLO_HTML_INPUT_TEXT; + EntryResource *entryResource = + //HT2LT(html)->getResourceFactory()->createEntryResource (15, false); + HT2LT(html)->getResourceFactory()->createEntryResource (10, false); + widget = embed = new Embed (entryResource); + init_str = (value) ? value : NULL; + } else { + /* Unknown input type */ + MSG_HTML("Unknown input type: \"%s\"\n", type); + } + + if (inp_type != DILLO_HTML_INPUT_UNKNOWN) { + Html_add_input(form, inp_type, widget, embed, name, + (init_str) ? init_str : "", NULL, init_val); + } + + if (widget != NULL && inp_type != DILLO_HTML_INPUT_IMAGE && + inp_type != DILLO_HTML_INPUT_UNKNOWN) { + if (inp_type == DILLO_HTML_INPUT_TEXT || + inp_type == DILLO_HTML_INPUT_PASSWORD) { + EntryResource *entryres = (EntryResource*)embed->getResource(); + /* Readonly or not? */ + if (Html_get_attr(html, tag, tagsize, "readonly")) + entryres->setEditable(false); + +// /* Set width of the entry */ +// if ((attrbuf = Html_get_attr(html, tag, tagsize, "size"))) +// gtk_widget_set_usize(widget, (strtol(attrbuf, NULL, 10) + 1) * +// gdk_char_width(widget->style->font, '0'), 0); +// +// /* Maximum length of the text in the entry */ +// if ((attrbuf = Html_get_attr(html, tag, tagsize, "maxlength"))) +// gtk_entry_set_max_length(GTK_ENTRY(widget), +// strtol(attrbuf, NULL, 10)); + } + + DW2TB(html->dw)->addWidget (embed, S_TOP(html)->style); + } + + dFree(type); + dFree(name); + if (init_str != value) + dFree(init_str); + dFree(value); +} + +/* + * The ISINDEX tag is just a deprecated form of <INPUT type=text> with + * implied FORM, afaics. + */ +static void Html_tag_open_isindex(DilloHtml *html, char *tag, int tagsize) +{ +// // AL +// DilloHtmlForm *form; +// DilloHtmlLB *html_lb; +// DilloUrl *action; +// GtkWidget *widget; +// Widget *embed_gtk; +// const char *attrbuf; +// +// html_lb = html->linkblock; +// +// if ((attrbuf = Html_get_attr(html, tag, tagsize, "action"))) +// action = Html_url_new(html, attrbuf, NULL, 0, 0, 0, 0); +// else +// action = a_Url_dup(html->linkblock->base_url); +// +// Html_form_new(html->linkblock, DILLO_HTML_METHOD_GET, action, +// DILLO_HTML_ENC_URLENCODING); +// +// form = html_lb->forms->getRef (html_lb->forms->size() - 1); +// +// DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style); +// +// if ((attrbuf = Html_get_attr(html, tag, tagsize, "prompt"))) +// DW2TB(html->dw)->addText(dStrdup(attrbuf), +// S_TOP(html)->style); +// +// widget = gtk_entry_new(); +// Html_add_input(form, DILLO_HTML_INPUT_INDEX, +// widget, NULL, NULL, NULL, FALSE); +// gtk_signal_connect(GTK_OBJECT(widget), "activate", +// GTK_SIGNAL_FUNC(Html_enter_submit_form), +// html_lb); +// gtk_widget_show(widget); +// /* compare <input type=text> */ +// gtk_signal_connect_after(GTK_OBJECT(widget), "button_press_event", +// GTK_SIGNAL_FUNC(gtk_true), +// NULL); +// +// embed_gtk = a_Dw_embed_gtk_new(); +// a_Dw_embed_gtk_add_gtk(DW_EMBED_GTK(embed_gtk), widget); +// DW2TB(html->dw)->addWidget (embed_gtk, +// S_TOP(html)->style); +// +// a_Url_free(action); +} + +/* + * Close textarea + * (TEXTAREA is parsed in VERBATIM mode, and entities are handled here) + */ +static void Html_tag_close_textarea(DilloHtml *html, int TagIdx) +{ + DilloHtmlLB *html_lb = html->linkblock; + char *str; + DilloHtmlForm *form; + int i; + + if (html->InFlags & IN_FORM && html->InFlags & IN_TEXTAREA) { + /* Remove the line ending that follows the opening tag */ + if (html->Stash->str[0] == '\r') + dStr_erase(html->Stash, 0, 1); + if (html->Stash->str[0] == '\n') + dStr_erase(html->Stash, 0, 1); + + /* As the spec recommends to canonicalize line endings, it is safe + * to replace '\r' with '\n'. It will be canonicalized anyway! */ + for (i = 0; i < html->Stash->len; ++i) { + if (html->Stash->str[i] == '\r') { + if (html->Stash->str[i + 1] == '\n') + dStr_erase(html->Stash, i, 1); + else + html->Stash->str[i] = '\n'; + } + } + + /* The HTML3.2 spec says it can have "text and character entities". */ + str = Html_parse_entities(html, html->Stash->str, html->Stash->len); + + form = html_lb->forms->getRef (html_lb->forms->size() - 1); + form->inputs->get(form->inputs->size() - 1).init_str = str; +// gtk_text_insert(GTK_TEXT(form->inputs[form->inputs->size() - 1].widget), +// NULL, NULL, NULL, str, -1); + + html->InFlags &= ~IN_TEXTAREA; + } + Html_pop_tag(html, TagIdx); +} + +/* + * The textarea tag + * (todo: It doesn't support wrapping). + */ +static void Html_tag_open_textarea(DilloHtml *html, char *tag, int tagsize) +{ +// // AL +// DilloHtmlLB *html_lb; +// DilloHtmlForm *form; +// GtkWidget *widget; +// GtkWidget *scroll; +// Widget *embed_gtk; +// char *name; +// const char *attrbuf; +// int cols, rows; +// +// /* We can't push a new <FORM> because the 'action' URL is unknown */ +// if (!(html->InFlags & IN_FORM)) { +// MSG_HTML("<textarea> outside <form>\n"); +// html->ReqTagClose = TRUE; +// return; +// } +// if (html->InFlags & IN_TEXTAREA) { +// MSG_HTML("nested <textarea>\n"); +// html->ReqTagClose = TRUE; +// return; +// } +// +// html->InFlags |= IN_TEXTAREA; +// html_lb = html->linkblock; +// form = html_lb->forms->getRef (html_lb->forms->size() - 1); +// Html_stash_init(html); +// S_TOP(html)->parse_mode = DILLO_HTML_PARSE_MODE_VERBATIM; +// +// cols = 20; +// if ((attrbuf = Html_get_attr(html, tag, tagsize, "cols"))) +// cols = strtol(attrbuf, NULL, 10); +// rows = 10; +// if ((attrbuf = Html_get_attr(html, tag, tagsize, "rows"))) +// rows = strtol(attrbuf, NULL, 10); +// name = NULL; +// if ((attrbuf = Html_get_attr(html, tag, tagsize, "name"))) +// name = dStrdup(attrbuf); +// +// widget = gtk_text_new(NULL, NULL); +// /* compare <input type=text> */ +// gtk_signal_connect_after(GTK_OBJECT(widget), "button_press_event", +// GTK_SIGNAL_FUNC(gtk_true), +// NULL); +// +// /* Calculate the width and height based on the cols and rows +// * todo: Get it right... Get the metrics from the font that will be used. +// */ +// gtk_widget_set_usize(widget, 6 * cols, 16 * rows); +// +// /* If the attribute readonly isn't specified we make the textarea +// * editable. If readonly is set we don't have to do anything. +// */ +// if (!Html_get_attr(html, tag, tagsize, "readonly")) +// gtk_text_set_editable(GTK_TEXT(widget), TRUE); +// +// scroll = gtk_scrolled_window_new(NULL, NULL); +// gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), +// GTK_POLICY_AUTOMATIC, +// GTK_POLICY_AUTOMATIC); +// gtk_container_add(GTK_CONTAINER(scroll), widget); +// gtk_widget_show(widget); +// gtk_widget_show(scroll); +// +// Html_add_input(form, DILLO_HTML_INPUT_TEXTAREA, +// widget, name, NULL, NULL, FALSE); +// dFree(name); +// +// embed_gtk = a_Dw_embed_gtk_new (); +// a_Dw_embed_gtk_add_gtk (DW_EMBED_GTK (embed_gtk), scroll); +// DW2TB(html->dw)->addWidget (embed_gtk, +// S_TOP(html)->style); +} + +/* + * <SELECT> + */ +/* The select tag is quite tricky, because of gorpy html syntax. */ +static void Html_tag_open_select(DilloHtml *html, char *tag, int tagsize) +{ +// DilloHtmlForm *form; +// DilloHtmlSelect *Select; +// DilloHtmlLB *html_lb; +// GtkWidget *widget, *menu; +// char *name; +// const char *attrbuf; +// int size, type, multi; +// +// if (!(html->InFlags & IN_FORM)) { +// MSG_HTML("<select> outside <form>\n"); +// return; +// } +// if (html->InFlags & IN_SELECT) { +// MSG_HTML("nested <select>\n"); +// return; +// } +// html->InFlags |= IN_SELECT; +// +// html_lb = html->linkblock; +// +// form = html_lb->forms->getRef (html_lb->forms->size() - 1); +// +// name = Html_get_attr_wdef(html, tag, tagsize, "name", NULL); +// +// size = 0; +// if ((attrbuf = Html_get_attr(html, tag, tagsize, "size"))) +// size = strtol(attrbuf, NULL, 10); +// +// multi = (Html_get_attr(html, tag, tagsize, "multiple")) ? 1 : 0; +// if (size < 1) +// size = multi ? 10 : 1; +// +// if (size == 1) { +// menu = gtk_menu_new(); +// widget = gtk_option_menu_new(); +// type = DILLO_HTML_INPUT_SELECT; +// } else { +// menu = gtk_list_new(); +// widget = menu; +// if (multi) +// gtk_list_set_selection_mode(GTK_LIST(menu), GTK_SELECTION_MULTIPLE); +// type = DILLO_HTML_INPUT_SEL_LIST; +// } +// +// Select = dNew(DilloHtmlSelect, 1); +// Select->menu = menu; +// Select->size = size; +// Select->num_options = 0; +// Select->num_options_max = 8; +// Select->options = dNew(DilloHtmlOption, Select->num_options_max); +// +// Html_add_input(form, type, widget, name, NULL, Select, FALSE); +// Html_stash_init(html); +// dFree(name); +} + +/* + * ? + */ +static void Html_option_finish(DilloHtml *html) +{ +// DilloHtmlForm *form; +// DilloHtmlInput *input; +// GtkWidget *menuitem; +// GSList *group; +// DilloHtmlSelect *select; +// +// if (!(html->InFlags & IN_FORM)) +// return; +// +// form = html->linkblock->forms->getRef (html->linkblock->forms->size() - 1); +// input = form->inputs->getRef (form->inputs->size() - 1); +// if (input->select->num_options <= 0) +// return; +// +// select = input->select; +// if (input->type == DILLO_HTML_INPUT_SELECT ) { +// if (select->num_options == 1) +// group = NULL; +// else +// group = gtk_radio_menu_item_group(GTK_RADIO_MENU_ITEM +// (select->options[0].menuitem)); +// menuitem = gtk_radio_menu_item_new_with_label(group, html->Stash->str); +// select->options[select->num_options - 1].menuitem = menuitem; +// if (select->options[select->num_options - 1].value == NULL) +// select->options[select->num_options - 1].value = +// dStrdup(html->Stash->str); +// gtk_menu_append(GTK_MENU(select->menu), menuitem); +// if (select->options[select->num_options - 1].init_val) +// gtk_menu_item_activate(GTK_MENU_ITEM(menuitem)); +// gtk_widget_show(menuitem); +// } else if (input->type == DILLO_HTML_INPUT_SEL_LIST) { +// menuitem = gtk_list_item_new_with_label(html->Stash->str); +// select->options[select->num_options - 1].menuitem = menuitem; +// if (select->options[select->num_options - 1].value == NULL) +// select->options[select->num_options - 1].value = +// dStrdup(html->Stash->str); +// gtk_container_add(GTK_CONTAINER(select->menu), menuitem); +// if (select->options[select->num_options - 1].init_val) +// gtk_list_select_child(GTK_LIST(select->menu), menuitem); +// gtk_widget_show(menuitem); +// } +} + +/* + * <OPTION> + */ +static void Html_tag_open_option(DilloHtml *html, char *tag, int tagsize) +{ +// DilloHtmlForm *form; +// DilloHtmlInput *input; +// DilloHtmlLB *html_lb; +// int no; +// +// if (!(html->InFlags & IN_SELECT)) +// return; +// +// html_lb = html->linkblock; +// +// form = html_lb->forms->getRef (html_lb->forms->size() - 1); +// input = form->inputs->getRef (form->inputs->size() - 1); +// if (input->type == DILLO_HTML_INPUT_SELECT || +// input->type == DILLO_HTML_INPUT_SEL_LIST) { +// Html_option_finish(html); +// no = input->select->num_options; +// a_List_add(input->select->options, no, input->select->num_options_max); +// input->select->options[no].menuitem = NULL; +// input->select->options[no].value = Html_get_attr_wdef(html, tag, tagsize, +// "value", NULL); +// input->select->options[no].init_val = +// (Html_get_attr(html, tag, tagsize, "selected") != NULL); +// input->select->num_options++; +// } +// Html_stash_init(html); +} + +/* + * ? + */ +static void Html_tag_close_select(DilloHtml *html, int TagIdx) +{ +// // AL +// DilloHtmlForm *form; +// DilloHtmlInput *input; +// GtkWidget *scrolledwindow; +// DilloHtmlLB *html_lb; +// Widget *embed_gtk; +// GtkRequisition req; +// int height; +// +// if (html->InFlags & IN_SELECT) { +// html->InFlags &= ~IN_SELECT; +// +// html_lb = html->linkblock; +// +// form = html_lb->forms->getRef (html_lb->forms->size() - 1); +// input = form->inputs->getRef (form->inputs->size() - 1); +// if (input->type == DILLO_HTML_INPUT_SELECT) { +// Html_option_finish(html); +// +// gtk_option_menu_set_menu(GTK_OPTION_MENU(input->widget), +// input->select->menu); +// Html_select_set_history(input); +// +// // gtk_option_menu_set_history(GTK_OPTION_MENU(input->widget), 1); +// +// gtk_widget_show(input->widget); +// +// embed_gtk = a_Dw_embed_gtk_new (); +// a_Dw_embed_gtk_add_gtk (DW_EMBED_GTK (embed_gtk), input->widget); +// DW2TB(html->dw)->addWidget (embed_gtk, +// S_TOP(html)->style); +// } else if (input->type == DILLO_HTML_INPUT_SEL_LIST) { +// Html_option_finish(html); +// +// if (input->select->size < input->select->num_options) { +// scrolledwindow = gtk_scrolled_window_new(NULL, NULL); +// gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwindow), +// GTK_POLICY_NEVER, +// GTK_POLICY_AUTOMATIC); +// gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW +// (scrolledwindow), +// input->widget); +// +// gtk_container_set_focus_vadjustment +// (GTK_CONTAINER (input->widget), +// gtk_scrolled_window_get_vadjustment +// (GTK_SCROLLED_WINDOW(scrolledwindow))); +// +// /* Calculate the height of the scrolled window */ +// gtk_widget_size_request(input->select->options[0].menuitem, &req); +// height = input->select->size * req.height + +// 2 * scrolledwindow->style->klass->ythickness; +// gtk_widget_set_usize(scrolledwindow, -1, height); +// +// gtk_widget_show(input->widget); +// input->widget = scrolledwindow; +// } +// gtk_widget_show(input->widget); +// +// /* note: In this next call, scrolledwindows get a warning from +// * gdkwindow.c:422. I'm not really going to sweat it now - the +// * embedded widget stuff is going to get massively redone anyway. */ +// embed_gtk = a_Dw_embed_gtk_new (); +// a_Dw_embed_gtk_add_gtk (DW_EMBED_GTK (embed_gtk), input->widget); +// DW2TB(html->dw)->addWidget (embed_gtk, +// S_TOP(html)->style); +// } +// } + Html_pop_tag(html, TagIdx); +} + +/* + * Set the Document Base URI + */ +static void Html_tag_open_base(DilloHtml *html, char *tag, int tagsize) +{ + const char *attrbuf; + DilloUrl *BaseUrl; + + if (html->InFlags & IN_HEAD) { + if ((attrbuf = Html_get_attr(html, tag, tagsize, "href"))) { + BaseUrl = Html_url_new(html, attrbuf, "", 0, 0, 0, 1); + if (URL_SCHEME_(BaseUrl)) { + /* Pass the URL_SpamSafe flag to the new base url */ + a_Url_set_flags( + BaseUrl, URL_FLAGS(html->linkblock->base_url) & URL_SpamSafe); + a_Url_free(html->linkblock->base_url); + html->linkblock->base_url = BaseUrl; + } else { + MSG_HTML("base URI is relative (it MUST be absolute)\n"); + a_Url_free(BaseUrl); + } + } + } else { + MSG_HTML("the BASE element must appear in the HEAD section\n"); + } +} + +/* + * <CODE> + */ +static void Html_tag_open_code(DilloHtml *html, char *tag, int tagsize) +{ + Html_set_top_font(html, prefs.fw_fontname, 0, 0, 0); +} + +/* + * <DFN> + */ +static void Html_tag_open_dfn(DilloHtml *html, char *tag, int tagsize) +{ + Html_set_top_font(html, NULL, 0, 2, 3); +} + +/* + * <KBD> + */ +static void Html_tag_open_kbd(DilloHtml *html, char *tag, int tagsize) +{ + Html_set_top_font(html, prefs.fw_fontname, 0, 0, 0); +} + +/* + * <SAMP> + */ +static void Html_tag_open_samp(DilloHtml *html, char *tag, int tagsize) +{ + Html_set_top_font(html, prefs.fw_fontname, 0, 0, 0); +} + +/* + * <VAR> + */ +static void Html_tag_open_var(DilloHtml *html, char *tag, int tagsize) +{ + Html_set_top_font(html, NULL, 0, 2, 2); +} + +/* + * <SUB> + */ +static void Html_tag_open_sub(DilloHtml *html, char *tag, int tagsize) +{ + HTML_SET_TOP_ATTR (html, valign, VALIGN_SUB); +} + +/* + * <SUP> + */ +static void Html_tag_open_sup(DilloHtml *html, char *tag, int tagsize) +{ + HTML_SET_TOP_ATTR (html, valign, VALIGN_SUPER); +} + +/* + * <DIV> (todo: make a complete implementation) + */ +static void Html_tag_open_div(DilloHtml *html, char *tag, int tagsize) +{ + DW2TB(html->dw)->addParbreak (0, S_TOP(html)->style); + Html_tag_set_align_attr (html, tag, tagsize); +} + +/* + * </DIV>, also used for </TABLE> and </CENTER> + */ +static void Html_tag_close_div(DilloHtml *html, int TagIdx) +{ + DW2TB(html->dw)->addParbreak (0, S_TOP(html)->style); + Html_pop_tag(html, TagIdx); +} + +/* + * Default close for most tags - just pop the stack. + */ +static void Html_tag_close_default(DilloHtml *html, int TagIdx) +{ + Html_pop_tag(html, TagIdx); +} + +/* + * Default close for paragraph tags - pop the stack and break. + */ +static void Html_tag_close_par(DilloHtml *html, int TagIdx) +{ + DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style); + Html_pop_tag(html, TagIdx); +} + + +/* + * Function index for the open and close functions for each tag + * (Alphabetically sorted for a binary search) + * + * Explanation for the 'Flags' camp: + * + * {"address", B8(010110), ...} + * |||||`- inline element + * ||||`-- block element + * |||`--- inline container + * ||`---- block container + * |`----- body element + * `------ head element + * + * Notes: + * - The upper two bits are not used yet. + * - Empty elements have both inline and block container clear. + * (flow have both set) + */ +struct _TagInfo{ + char *name; + unsigned char Flags; + char EndTag; + uchar_t TagLevel; + TagOpenFunct open; + TagCloseFunct close; +}; + + +const TagInfo Tags[] = { + {"a", B8(010101),'R',2, Html_tag_open_a, Html_tag_close_a}, + {"abbr", B8(010101),'R',2, Html_tag_open_abbr, Html_tag_close_default}, + /* acronym 010101 */ + {"address", B8(010110),'R',2, Html_tag_open_address, Html_tag_close_par}, + {"area", B8(010001),'F',0, Html_tag_open_area, Html_tag_close_default}, + {"b", B8(010101),'R',2, Html_tag_open_b, Html_tag_close_default}, + {"base", B8(100001),'F',0, Html_tag_open_base, Html_tag_close_default}, + /* basefont 010001 */ + /* bdo 010101 */ + {"big", B8(010101),'R',2, Html_tag_open_big_small, Html_tag_close_default}, + {"blockquote", B8(011110),'R',2,Html_tag_open_blockquote,Html_tag_close_par}, + {"body", B8(011110),'O',1, Html_tag_open_body, Html_tag_close_body}, + {"br", B8(010001),'F',0, Html_tag_open_br, Html_tag_close_default}, + {"button", B8(011101),'R',2, Html_tag_open_button, Html_tag_close_default}, + /* caption */ + {"center", B8(011110),'R',2, Html_tag_open_center, Html_tag_close_div}, + {"cite", B8(010101),'R',2, Html_tag_open_cite, Html_tag_close_default}, + {"code", B8(010101),'R',2, Html_tag_open_code, Html_tag_close_default}, + /* col 010010 'F' */ + /* colgroup */ + {"dd", B8(011110),'O',1, Html_tag_open_dd, Html_tag_close_par}, + {"del", B8(011101),'R',2, Html_tag_open_strike, Html_tag_close_default}, + {"dfn", B8(010101),'R',2, Html_tag_open_dfn, Html_tag_close_default}, + /* dir 011010 */ + /* todo: complete <div> support! */ + {"div", B8(011110),'R',2, Html_tag_open_div, Html_tag_close_div}, + {"dl", B8(011010),'R',2, Html_tag_open_dl, Html_tag_close_par}, + {"dt", B8(010110),'O',1, Html_tag_open_dt, Html_tag_close_par}, + {"em", B8(010101),'R',2, Html_tag_open_em, Html_tag_close_default}, + /* fieldset */ + {"font", B8(010101),'R',2, Html_tag_open_font, Html_tag_close_default}, + {"form", B8(011110),'R',2, Html_tag_open_form, Html_tag_close_form}, + {"frame", B8(010010),'F',0, Html_tag_open_frame, Html_tag_close_default}, + {"frameset", B8(011110),'R',2,Html_tag_open_frameset, Html_tag_close_default}, + {"h1", B8(010110),'R',2, Html_tag_open_h, Html_tag_close_h}, + {"h2", B8(010110),'R',2, Html_tag_open_h, Html_tag_close_h}, + {"h3", B8(010110),'R',2, Html_tag_open_h, Html_tag_close_h}, + {"h4", B8(010110),'R',2, Html_tag_open_h, Html_tag_close_h}, + {"h5", B8(010110),'R',2, Html_tag_open_h, Html_tag_close_h}, + {"h6", B8(010110),'R',2, Html_tag_open_h, Html_tag_close_h}, + {"head", B8(101101),'O',1, Html_tag_open_head, Html_tag_close_head}, + {"hr", B8(010010),'F',0, Html_tag_open_hr, Html_tag_close_default}, + {"html", B8(001110),'O',1, Html_tag_open_html, Html_tag_close_html}, + {"i", B8(010101),'R',2, Html_tag_open_i, Html_tag_close_default}, + {"iframe", B8(011110),'R',2, Html_tag_open_frame, Html_tag_close_default}, + {"img", B8(010001),'F',0, Html_tag_open_img, Html_tag_close_default}, + {"input", B8(010001),'F',0, Html_tag_open_input, Html_tag_close_default}, + /* ins */ + {"isindex", B8(110001),'F',0, Html_tag_open_isindex, Html_tag_close_default}, + {"kbd", B8(010101),'R',2, Html_tag_open_kbd, Html_tag_close_default}, + /* label 010101 */ + /* legend 01?? */ + {"li", B8(011110),'O',1, Html_tag_open_li, Html_tag_close_li}, + /* link 100000 'F' */ + {"map", B8(011001),'R',2, Html_tag_open_map, Html_tag_close_map}, + /* menu 1010 -- todo: not exactly 1010, it can contain LI and inline */ + {"menu", B8(011010),'R',2, Html_tag_open_menu, Html_tag_close_par}, + {"meta", B8(100001),'F',0, Html_tag_open_meta, Html_tag_close_default}, + /* noframes 1011 */ + /* noscript 1011 */ + /* object 11xxxx */ + {"ol", B8(011010),'R',2, Html_tag_open_ol, Html_tag_close_par}, + /* optgroup */ + {"option", B8(010001),'O',1, Html_tag_open_option, Html_tag_close_default}, + {"p", B8(010110),'O',1, Html_tag_open_p, Html_tag_close_par}, + /* param 010001 'F' */ + {"pre", B8(010110),'R',2, Html_tag_open_pre, Html_tag_close_pre}, + /* q 010101 */ + {"s", B8(010101),'R',2, Html_tag_open_strike, Html_tag_close_default}, + {"samp", B8(010101),'R',2, Html_tag_open_samp, Html_tag_close_default}, + {"script", B8(111001),'R',2, Html_tag_open_script, Html_tag_close_script}, + {"select", B8(011001),'R',2, Html_tag_open_select, Html_tag_close_select}, + {"small", B8(010101),'R',2, Html_tag_open_big_small, Html_tag_close_default}, + /* span 0101 */ + {"strike", B8(010101),'R',2, Html_tag_open_strike, Html_tag_close_default}, + {"strong", B8(010101),'R',2, Html_tag_open_strong, Html_tag_close_default}, + {"style", B8(100101),'R',2, Html_tag_open_style, Html_tag_close_style}, + {"sub", B8(010101),'R',2, Html_tag_open_sub, Html_tag_close_default}, + {"sup", B8(010101),'R',2, Html_tag_open_sup, Html_tag_close_default}, + {"table", B8(011010),'R',5, Html_tag_open_table, Html_tag_close_div}, + /* tbody */ + {"td", B8(011110),'O',3, Html_tag_open_td, Html_tag_close_default}, + {"textarea", B8(010101),'R',2,Html_tag_open_textarea,Html_tag_close_textarea}, + /* tfoot */ + {"th", B8(011110),'O',1, Html_tag_open_th, Html_tag_close_default}, + /* thead */ + {"title", B8(100101),'R',2, Html_tag_open_title, Html_tag_close_title}, + {"tr", B8(011010),'O',4, Html_tag_open_tr, Html_tag_close_default}, + {"tt", B8(010101),'R',2, Html_tag_open_tt, Html_tag_close_default}, + {"u", B8(010101),'R',2, Html_tag_open_u, Html_tag_close_default}, + {"ul", B8(011010),'R',2, Html_tag_open_ul, Html_tag_close_par}, + {"var", B8(010101),'R',2, Html_tag_open_var, Html_tag_close_default} + +}; +#define NTAGS (sizeof(Tags)/sizeof(Tags[0])) + + +/* + * Compares tag from buffer ('/' or '>' or space-ended string) [p1] + * with tag from taglist (lowercase, zero ended string) [p2] + * Return value: as strcmp() + */ +static int Html_tag_compare(const char *p1, const char *p2) +{ + while ( *p2 ) { + if (tolower(*p1) != *p2) + return(tolower(*p1) - *p2); + ++p1; + ++p2; + } + return !strchr(" >/\n\r\t", *p1); +} + +/* + * Get 'tag' index + * return -1 if tag is not handled yet + */ +static int Html_tag_index(const char *tag) +{ + int low, high, mid, cond; + + /* Binary search */ + low = 0; + high = NTAGS - 1; /* Last tag index */ + while (low <= high) { + mid = (low + high) / 2; + if ((cond = Html_tag_compare(tag, Tags[mid].name)) < 0 ) + high = mid - 1; + else if (cond > 0) + low = mid + 1; + else + return mid; + } + return -1; +} + +/* + * For elements with optional close, check whether is time to close. + * Return value: (1: Close, 0: Don't close) + * --tuned for speed. + */ +static int Html_needs_optional_close(int old_idx, int cur_idx) +{ + static int i_P = -1, i_LI, i_TD, i_TR, i_TH, i_DD, i_DT, i_OPTION; + // i_THEAD, i_TFOOT, i_COLGROUP; + + if (i_P == -1) { + /* initialize the indexes of elements with optional close */ + i_P = Html_tag_index("p"), + i_LI = Html_tag_index("li"), + i_TD = Html_tag_index("td"), + i_TR = Html_tag_index("tr"), + i_TH = Html_tag_index("th"), + i_DD = Html_tag_index("dd"), + i_DT = Html_tag_index("dt"), + i_OPTION = Html_tag_index("option"); + // i_THEAD = Html_tag_index("thead"); + // i_TFOOT = Html_tag_index("tfoot"); + // i_COLGROUP = Html_tag_index("colgroup"); + } + + if (old_idx == i_P || old_idx == i_DT) { + /* P and DT are closed by block elements */ + return (Tags[cur_idx].Flags & 2); + } else if (old_idx == i_LI) { + /* LI closes LI */ + return (cur_idx == i_LI); + } else if (old_idx == i_TD || old_idx == i_TH) { + /* TD and TH are closed by TD, TH and TR */ + return (cur_idx == i_TD || cur_idx == i_TH || cur_idx == i_TR); + } else if (old_idx == i_TR) { + /* TR closes TR */ + return (cur_idx == i_TR); + } else if (old_idx == i_DD) { + /* DD is closed by DD and DT */ + return (cur_idx == i_DD || cur_idx == i_DT); + } else if (old_idx == i_OPTION) { + return 1; // OPTION always needs close + } + + /* HTML, HEAD, BODY are handled by Html_test_section(), not here. */ + /* todo: TBODY is pending */ + return 0; +} + + +/* + * Conditional cleanup of the stack (at open time). + * - This helps catching block elements inside inline containers (a BUG). + * - It also closes elements with "optional" close tag. + * + * This function is called when opening a block element or <OPTION>. + * + * It searches the stack closing open inline containers, and closing + * elements with optional close tag when necessary. + * + * Note: OPTION is the only non-block element with an optional close. + */ +static void Html_stack_cleanup_at_open(DilloHtml *html, int new_idx) +{ + /* We know that the element we're about to push is a block element. + * (except for OPTION, which is an empty inline, so is closed anyway) + * Notes: + * Its 'tag' is not yet pushed into the stack, + * 'new_idx' is its index inside Tags[]. + */ + + if (!html->TagSoup) + return; + + while (html->stack->size() > 1) { + int oldtag_idx = S_TOP(html)->tag_idx; + + if (Tags[oldtag_idx].EndTag == 'O') { // Element with optional close + if (!Html_needs_optional_close(oldtag_idx, new_idx)) + break; + } else if (Tags[oldtag_idx].Flags & 8) { // Block container + break; + } + + /* we have an inline (or empty) container... */ + if (Tags[oldtag_idx].EndTag == 'R') { + MSG_HTML("<%s> is not allowed to contain <%s>. -- closing <%s>\n", + Tags[oldtag_idx].name, Tags[new_idx].name, + Tags[oldtag_idx].name); + } + + /* Workaround for Apache and its bad HTML directory listings... */ + if ((html->InFlags & IN_PRE) && + strcmp(Tags[new_idx].name, "hr") == 0) + break; + + /* This call closes the top tag only. */ + Html_tag_cleanup_at_close(html, oldtag_idx); + } +} + +/* + * HTML, HEAD and BODY elements have optional open and close tags. + * Handle this "magic" here. + */ +static void Html_test_section(DilloHtml *html, int new_idx, int IsCloseTag) +{ + char *tag; + int tag_idx; + + if (!(html->InFlags & IN_HTML) && html->DocType == DT_NONE) + MSG_HTML("the required DOCTYPE declaration is missing (or invalid)\n"); + + if (!(html->InFlags & IN_HTML)) { + tag = "<html>"; + tag_idx = Html_tag_index(tag + 1); + if (tag_idx != new_idx || IsCloseTag) { + /* implicit open */ + Html_force_push_tag(html, tag_idx); + Tags[tag_idx].open (html, tag, strlen(tag)); + } + } + + if (Tags[new_idx].Flags & 32) { + /* head element */ + if (!(html->InFlags & IN_HEAD)) { + tag = "<head>"; + tag_idx = Html_tag_index(tag + 1); + if (tag_idx != new_idx || IsCloseTag) { + /* implicit open of the head element */ + Html_force_push_tag(html, tag_idx); + Tags[tag_idx].open (html, tag, strlen(tag)); + } + } + + } else if (Tags[new_idx].Flags & 16) { + /* body element */ + if (html->InFlags & IN_HEAD) { + tag = "</head>"; + tag_idx = Html_tag_index(tag + 2); + Tags[tag_idx].close (html, tag_idx); + } + tag = "<body>"; + tag_idx = Html_tag_index(tag + 1); + if (tag_idx != new_idx || IsCloseTag) { + /* implicit open */ + Html_force_push_tag(html, tag_idx); + Tags[tag_idx].open (html, tag, strlen(tag)); + } + } +} + +/* + * Process a tag, given as 'tag' and 'tagsize'. -- tagsize is [1 based] + * ('tag' must include the enclosing angle brackets) + * This function calls the right open or close function for the tag. + */ +static void Html_process_tag(DilloHtml *html, char *tag, int tagsize) +{ + int ci, ni; /* current and new tag indexes */ + const char *attrbuf; + char *start = tag + 1; /* discard the '<' */ + int IsCloseTag = (*start == '/'); + + ni = Html_tag_index(start + IsCloseTag); + + /* todo: doctype parsing is a bit fuzzy, but enough for the time being */ + if (ni == -1 && !(html->InFlags & IN_HTML)) { + if (tagsize > 9 && !dStrncasecmp(tag, "<!doctype", 9)) + Html_parse_doctype(html, tag, tagsize); + } + + if (!(html->InFlags & IN_HTML)) { + _MSG("\nDoctype: %f\n\n", html->DocTypeVersion); + } + + /* Handle HTML, HEAD and BODY. Elements with optional open and close */ + if (ni != -1 && !(html->InFlags & IN_BODY) /* && parsing HTML */) + Html_test_section(html, ni, IsCloseTag); + + /* White space handling */ + if (html->SPCPending && (!SGML_SPCDEL || !IsCloseTag)) + /* SGML_SPCDEL requires space pending and open tag */ + DW2TB(html->dw)->addSpace(S_TOP(html)->style); + html->SPCPending = FALSE; + + /* Tag processing */ + ci = S_TOP(html)->tag_idx; + if (ni != -1) { + + if (!IsCloseTag) { + /* Open function */ + + /* Cleanup when opening a block element, or + * when openning over an element with optional close */ + if (Tags[ni].Flags & 2 || (ci != -1 && Tags[ci].EndTag == 'O')) + Html_stack_cleanup_at_open(html, ni); + + /* todo: this is only raising a warning, take some defined action. + * Note: apache uses IMG inside PRE (we could use its "alt"). */ + if ((html->InFlags & IN_PRE) && Html_tag_pre_excludes(ni)) + MSG_HTML("<pre> is not allowed to contain <%s>\n", Tags[ni].name); + + /* Push the tag into the stack */ + Html_push_tag(html, ni); + + /* Call the open function for this tag */ + Tags[ni].open (html, tag, tagsize); + + /* Now parse attributes that can appear on any tag */ + if (tagsize >= 8 && /* length of "<t id=i>" */ + (attrbuf = Html_get_attr2(html, tag, tagsize, "id", + HTML_LeftTrim | HTML_RightTrim))) { + /* According to the SGML declaration of HTML 4, all NAME values + * occuring outside entities must be converted to uppercase + * (this is what "NAMECASE GENERAL YES" says). But the HTML 4 + * spec states in Sec. 7.5.2 that anchor ids are case-sensitive. + * So we don't do it and hope for better specs in the future ... + */ + Html_check_name_val(html, attrbuf, "id"); + /* We compare the "id" value with the url-decoded "name" value */ + if (!html->NameVal || strcmp(html->NameVal, attrbuf)) { + if (html->NameVal) + MSG_HTML("'id' and 'name' attribute of <a> tag differ\n"); + Html_add_anchor(html, attrbuf); + } + } + + /* Reset NameVal */ + if (html->NameVal) { + dFree(html->NameVal); + html->NameVal = NULL; + } + + /* let the parser know this was an open tag */ + html->PrevWasOpenTag = TRUE; + + /* Request inmediate close for elements with forbidden close tag. */ + /* todo: XHTML always requires close tags. A simple implementation + * of the commented clause below will make it work. */ + if (/* parsing HTML && */ Tags[ni].EndTag == 'F') + html->ReqTagClose = TRUE; + } + + /* Close function: test for </x>, ReqTagClose, <x /> and <x/> */ + if (*start == '/' || /* </x> */ + html->ReqTagClose || /* request */ + (tag[tagsize - 2] == '/' && /* XML: */ + (isspace(tag[tagsize - 3]) || /* <x /> */ + (size_t)tagsize == strlen(Tags[ni].name) + 3))) { /* <x/> */ + + Tags[ni].close (html, ni); + /* This was a close tag */ + html->PrevWasOpenTag = FALSE; + html->ReqTagClose = FALSE; + } + + } else { + /* tag not working - just ignore it */ + } +} + +/* + * Get attribute value for 'attrname' and return it. + * Tags start with '<' and end with a '>' (Ex: "<P align=center>") + * tagsize = strlen(tag) from '<' to '>', inclusive. + * + * Returns one of the following: + * * The value of the attribute. + * * An empty string if the attribute exists but has no value. + * * NULL if the attribute doesn't exist. + */ +static const char *Html_get_attr2(DilloHtml *html, + const char *tag, + int tagsize, + const char *attrname, + int tag_parsing_flags) +{ + int i, isocode, entsize, Found = 0, delimiter = 0, attr_pos = 0; + Dstr *Buf = html->attr_data; + DilloHtmlTagParsingState state = SEEK_ATTR_START; + + dReturn_val_if_fail(*attrname, NULL); + + dStr_truncate(Buf, 0); + + for (i = 1; i < tagsize; ++i) { + switch (state) { + case SEEK_ATTR_START: + if (isspace(tag[i])) + state = SEEK_TOKEN_START; + else if (tag[i] == '=') + state = SEEK_VALUE_START; + break; + + case MATCH_ATTR_NAME: + if ((Found = (!(attrname[attr_pos]) && + (tag[i] == '=' || isspace(tag[i]) || tag[i] == '>')))) { + state = SEEK_TOKEN_START; + --i; + } else if (tolower(tag[i]) != tolower(attrname[attr_pos++])) + state = SEEK_ATTR_START; + break; + + case SEEK_TOKEN_START: + if (tag[i] == '=') { + state = SEEK_VALUE_START; + } else if (!isspace(tag[i])) { + attr_pos = 0; + state = (Found) ? FINISHED : MATCH_ATTR_NAME; + --i; + } + break; + case SEEK_VALUE_START: + if (!isspace(tag[i])) { + delimiter = (tag[i] == '"' || tag[i] == '\'') ? tag[i] : ' '; + i -= (delimiter == ' '); + state = (Found) ? GET_VALUE : SKIP_VALUE; + } + break; + + case SKIP_VALUE: + if ((delimiter == ' ' && isspace(tag[i])) || tag[i] == delimiter) + state = SEEK_TOKEN_START; + break; + case GET_VALUE: + if ((delimiter == ' ' && (isspace(tag[i]) || tag[i] == '>')) || + tag[i] == delimiter) { + state = FINISHED; + } else if (tag[i] == '&' && + (tag_parsing_flags & HTML_ParseEntities)) { + if ((isocode = Html_parse_entity(html, tag+i, + tagsize-i, &entsize)) >= 0) { + if (isocode >= 128) { + char buf[4]; + int k, n = utf8encode(isocode, buf); + for (k = 0; k < n; ++k) + dStr_append_c(Buf, buf[k]); + } else { + dStr_append_c(Buf, (char) isocode); + } + i += entsize-1; + } else { + dStr_append_c(Buf, tag[i]); + } + } else if (tag[i] == '\r' || tag[i] == '\t') { + dStr_append_c(Buf, ' '); + } else if (tag[i] == '\n') { + /* ignore */ + } else { + dStr_append_c(Buf, tag[i]); + } + break; + + case FINISHED: + i = tagsize; + break; + } + } + + if (tag_parsing_flags & HTML_LeftTrim) + while (isspace(Buf->str[0])) + dStr_erase(Buf, 0, 1); + if (tag_parsing_flags & HTML_RightTrim) + while (Buf->len && isspace(Buf->str[Buf->len - 1])) + dStr_truncate(Buf, Buf->len - 1); + + return (Found) ? Buf->str : NULL; +} + +/* + * Call Html_get_attr2 telling it to parse entities and strip the result + */ +static const char *Html_get_attr(DilloHtml *html, + const char *tag, + int tagsize, + const char *attrname) +{ + return Html_get_attr2(html, tag, tagsize, attrname, + HTML_LeftTrim | HTML_RightTrim | HTML_ParseEntities); +} + +/* + * "Html_get_attr with default" + * Call Html_get_attr() and strdup() the returned string. + * If the attribute isn't found a copy of 'def' is returned. + */ +static char *Html_get_attr_wdef(DilloHtml *html, + const char *tag, + int tagsize, + const char *attrname, + const char *def) +{ + const char *attrbuf = Html_get_attr(html, tag, tagsize, attrname); + + return attrbuf ? dStrdup(attrbuf) : dStrdup(def); +} + +/* + * Add a widget to the page. + */ +static void Html_add_widget(DilloHtml *html, + Widget *widget, + char *width_str, + char *height_str, + StyleAttrs *style_attrs) +{ + StyleAttrs new_style_attrs; + Style *style; + + new_style_attrs = *style_attrs; + new_style_attrs.width = width_str ? + Html_parse_length (html, width_str) : LENGTH_AUTO; + new_style_attrs.height = height_str ? + Html_parse_length (html, height_str) : LENGTH_AUTO; + style = Style::create (HT2LT(html), &new_style_attrs); + DW2TB(html->dw)->addWidget (widget, style); + style->unref (); +} + + +/* + * Dispatch the apropriate function for 'Op' + * This function is a Cache client and gets called whenever new data arrives + * Op : operation to perform. + * CbData : a pointer to a DilloHtml structure + * Buf : a pointer to new data + * BufSize : new data size (in bytes) + */ +static void Html_callback(int Op, CacheClient_t *Client) +{ + if (Op) { + Html_write((DilloHtml*)Client->CbData, (char*)Client->Buf, + Client->BufSize, 1); + Html_close((DilloHtml*)Client->CbData, Client->Key); + } else { + Html_write((DilloHtml*)Client->CbData, (char*)Client->Buf, + Client->BufSize, 0); + } +} + +/* + * Here's where we parse the html and put it into the Textblock structure. + * Return value: number of bytes parsed + */ +static int Html_write_raw(DilloHtml *html, char *buf, int bufsize, int Eof) +{ + char ch = 0, *p, *text; + Textblock *textblock; + int token_start, buf_index; + + dReturn_val_if_fail ((textblock = DW2TB(html->dw)) != NULL, 0); + + /* Now, 'buf' and 'bufsize' define a buffer aligned to start at a token + * boundary. Iterate through tokens until end of buffer is reached. */ + buf_index = 0; + token_start = buf_index; + while (buf_index < bufsize) { + /* invariant: buf_index == bufsize || token_start == buf_index */ + + if (S_TOP(html)->parse_mode == + DILLO_HTML_PARSE_MODE_VERBATIM) { + /* Non HTML code here, let's skip until closing tag */ + do { + char *tag = S_TOP(html)->tag_name; + buf_index += strcspn(buf + buf_index, "<"); + if (buf_index + (int)strlen(tag) + 3 > bufsize) { + buf_index = bufsize; + } else if (strncmp(buf + buf_index, "</", 2) == 0 && + Html_match_tag(tag, buf+buf_index+2, strlen(tag)+1)) { + /* copy VERBATIM text into the stash buffer */ + text = dStrndup(buf + token_start, buf_index - token_start); + dStr_append(html->Stash, text); + dFree(text); + token_start = buf_index; + break; + } else + ++buf_index; + } while (buf_index < bufsize); + } + + if (isspace(buf[buf_index])) { + /* whitespace: group all available whitespace */ + while (++buf_index < bufsize && isspace(buf[buf_index])); + Html_process_space(html, buf + token_start, buf_index - token_start); + token_start = buf_index; + + } else if (buf[buf_index] == '<' && (ch = buf[buf_index + 1]) && + (isalpha(ch) || strchr("/!?", ch)) ) { + /* Tag */ + if (buf_index + 3 < bufsize && !strncmp(buf + buf_index, "<!--", 4)) { + /* Comment: search for close of comment, skipping over + * everything except a matching "-->" tag. */ + while ( (p = (char*) memchr(buf + buf_index, '>', + bufsize - buf_index)) ){ + buf_index = p - buf + 1; + if (p[-1] == '-' && p[-2] == '-') break; + } + if (p) { + /* Got the whole comment. Let's throw it away! :) */ + token_start = buf_index; + } else + buf_index = bufsize; + } else { + /* Tag: search end of tag (skipping over quoted strings) */ + html->CurrTagOfs = html->Start_Ofs + token_start; + + while ( buf_index < bufsize ) { + buf_index++; + buf_index += strcspn(buf + buf_index, ">\"'<"); + if ((ch = buf[buf_index]) == '>') { + break; + } else if (ch == '"' || ch == '\'') { + /* Skip over quoted string */ + buf_index++; + buf_index += strcspn(buf + buf_index, + (ch == '"') ? "\">" : "'>"); + if (buf[buf_index] == '>') { + /* Unterminated string value? Let's look ahead and test: + * (<: unterminated, closing-quote: terminated) */ + int offset = buf_index + 1; + offset += strcspn(buf + offset, + (ch == '"') ? "\"<" : "'<"); + if (buf[offset] == ch || !buf[offset]) { + buf_index = offset; + } else { + MSG_HTML("attribute lacks closing quote\n"); + break; + } + } + } else if (ch == '<') { + /* unterminated tag detected */ + p = dStrndup(buf+token_start+1, + strcspn(buf+token_start+1, " <")); + MSG_HTML("<%s> element lacks its closing '>'\n", p); + dFree(p); + --buf_index; + break; + } + } + if (buf_index < bufsize) { + buf_index++; + Html_process_tag(html, buf + token_start, + buf_index - token_start); + token_start = buf_index; + } + } + } else { + /* A Word: search for whitespace or tag open */ + while (++buf_index < bufsize) { + buf_index += strcspn(buf + buf_index, " <\n\r\t\f\v"); + if (buf[buf_index] == '<' && (ch = buf[buf_index + 1]) && + !isalpha(ch) && !strchr("/!?", ch)) + continue; + break; + } + if (buf_index < bufsize || Eof) { + /* successfully found end of token */ + Html_process_word(html, buf + token_start, + buf_index - token_start); + token_start = buf_index; + } + } + }/*while*/ + + textblock->flush (); + + return token_start; +} + +/* + * Process the newly arrived html and put it into the page structure. + * (This function is called by Html_callback whenever there's new data) + */ +static void Html_write(DilloHtml *html, char *Buf, int BufSize, int Eof) +{ + int token_start; + char *buf = Buf + html->Start_Ofs; + int bufsize = BufSize - html->Start_Ofs; + + dReturn_if_fail (DW2TB(html->dw) != NULL); + + html->Start_Buf = Buf; + token_start = Html_write_raw(html, buf, bufsize, Eof); + html->Start_Ofs += token_start; + + if (html->bw) + a_UIcmd_set_page_prog(html->bw, html->Start_Ofs, 1); +} + +/* + * Finish parsing a HTML page + * (Free html struct, close the client and update the page progress bar). + */ +static void Html_close(DilloHtml *html, int ClientKey) +{ + int si; + + //#if defined (DEBUG_LEVEL) && DEBUG_LEVEL >= 1 + //a_Dw_widget_print_tree (GTK_DW_VIEWPORT(html->dw->viewport)->child); + //#endif + + /* force the close of elements left open (todo: not for XHTML) */ + while ((si = html->stack->size() - 1)) { + if (html->stack->getRef(si)->tag_idx != -1) { + Html_tag_cleanup_at_close(html, html->stack->getRef(si)->tag_idx); + } + } + dFree(html->stack->getRef(0)->tag_name); /* "none" */ + (html->stack->getRef(0)->style)->unref (); /* template style */ + + delete (html->stack); + + dStr_free(html->Stash, TRUE); + dFree(html->SPCBuf); + dStr_free(html->attr_data, TRUE); + + /* Remove this client from our active list */ + a_Bw_close_client(html->bw, ClientKey); + + /* Set progress bar insensitive */ + a_UIcmd_set_page_prog(html->bw, 0, 0); + dFree(html); +} + + diff --git a/src/html.hh b/src/html.hh new file mode 100644 index 00000000..2d9202e6 --- /dev/null +++ b/src/html.hh @@ -0,0 +1,279 @@ +#ifndef __HTML_HH__ +#define __HTML_HH__ + +#include "d_size.h" // for uchar_t +#include "bw.h" // for BrowserWindow + +#include "dw/core.hh" +#include "lout/misc.hh" // For SimpleVector + +//#include "dw_image.h" // for DwImageMapList + +#include "form.hh" // For receiving the "clicked" signal + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* + * First, the html linkblock. For now, this mostly has forms, although + * pointers to actual links will go here soon, if for no other reason + * than to implement history-sensitive link colors. Also, it seems + * likely that imagemaps will go here. + */ + +typedef struct _DilloHtmlLB DilloHtmlLB; + +typedef struct _DilloHtml DilloHtml; +typedef struct _DilloHtmlClass DilloHtmlClass; +typedef struct _DilloHtmlState DilloHtmlState; +typedef struct _DilloHtmlForm DilloHtmlForm; +typedef struct _DilloHtmlOption DilloHtmlOption; +typedef struct _DilloHtmlSelect DilloHtmlSelect; +typedef struct _DilloHtmlInput DilloHtmlInput; + + +struct _DilloHtmlLB { + class HtmlLinkReceiver: public dw::core::Widget::LinkReceiver + { + private: + DilloHtmlLB *lb; + + public: + inline HtmlLinkReceiver (DilloHtmlLB *lb) { this->lb = lb; } + + bool enter (dw::core::Widget *widget, int link, int x, int y); + bool press (dw::core::Widget *widget, int link, int x, int y, + dw::core::EventButton *event); + bool click (dw::core::Widget *widget, int link, int x, int y, + dw::core::EventButton *event); + }; + + // Since DilloHtmlLB is a struct, not a class, a simple + // "HtmlLinkReceiver linkReceiver" (see signal documentation) would not + // work, therefore the pointer. + HtmlLinkReceiver *linkReceiver; + + BrowserWindow *bw; + DilloUrl *base_url; + + misc::SimpleVector<DilloHtmlForm> *forms; + + misc::SimpleVector<DilloUrl*> *links; + + //DwImageMapList maps; + + int32_t link_color; + int32_t visited_color; +}; + + +typedef enum { + DT_NONE, + DT_HTML, + DT_XHTML +} DilloHtmlDocumentType; + +typedef enum { + DILLO_HTML_PARSE_MODE_INIT = 0, + DILLO_HTML_PARSE_MODE_STASH, + DILLO_HTML_PARSE_MODE_STASH_AND_BODY, + DILLO_HTML_PARSE_MODE_VERBATIM, + DILLO_HTML_PARSE_MODE_BODY, + DILLO_HTML_PARSE_MODE_PRE +} DilloHtmlParseMode; + +typedef enum { + SEEK_ATTR_START, + MATCH_ATTR_NAME, + SEEK_TOKEN_START, + SEEK_VALUE_START, + SKIP_VALUE, + GET_VALUE, + FINISHED +} DilloHtmlTagParsingState; + +typedef enum { + HTML_LeftTrim = 1 << 0, + HTML_RightTrim = 1 << 1, + HTML_ParseEntities = 1 << 2 +} DilloHtmlTagParsingFlags; + +typedef enum { + DILLO_HTML_TABLE_MODE_NONE, /* no table at all */ + DILLO_HTML_TABLE_MODE_TOP, /* outside of <tr> */ + DILLO_HTML_TABLE_MODE_TR, /* inside of <tr>, outside of <td> */ + DILLO_HTML_TABLE_MODE_TD /* inside of <td> */ +} DilloHtmlTableMode; + +typedef enum { + HTML_LIST_NONE, + HTML_LIST_UNORDERED, + HTML_LIST_ORDERED +} DilloHtmlListMode; + +enum DilloHtmlProcessingState { + IN_NONE = 0, + IN_HTML = 1 << 0, + IN_HEAD = 1 << 1, + IN_BODY = 1 << 2, + IN_FORM = 1 << 3, + IN_SELECT = 1 << 4, + IN_TEXTAREA = 1 << 5, + IN_MAP = 1 << 6, + IN_PRE = 1 << 7, + IN_BUTTON = 1 << 8 +}; + + +struct _DilloHtmlState { + char *tag_name; + //DwStyle *style, *table_cell_style; + dw::core::style::Style *style, *table_cell_style; + DilloHtmlParseMode parse_mode; + DilloHtmlTableMode table_mode; + bool_t cell_text_align_set; + DilloHtmlListMode list_type; + int list_number; + + /* TagInfo index for the tag that's being processed */ + int tag_idx; + + dw::core::Widget *textblock, *table; + + /* This is used to align list items (especially in enumerated lists) */ + dw::core::Widget *ref_list_item; + + /* This makes image processing faster than a function + a_Dw_widget_get_background_color. */ + int32_t current_bg_color; + + /* This is used for list items etc; if it is set to TRUE, breaks + have to be "handed over" (see Html_add_indented and + Html_eventually_pop_dw). */ + bool_t hand_over_break; +}; + +typedef enum { + DILLO_HTML_METHOD_UNKNOWN, + DILLO_HTML_METHOD_GET, + DILLO_HTML_METHOD_POST +} DilloHtmlMethod; + +typedef enum { + DILLO_HTML_ENC_URLENCODING +} DilloHtmlEnc; + +struct _DilloHtmlForm { + DilloHtmlMethod method; + DilloUrl *action; + DilloHtmlEnc enc; + + misc::SimpleVector<DilloHtmlInput> *inputs; + + int num_entry_fields; + int num_submit_buttons; + + form::Form *form_receiver; +}; + +struct _DilloHtmlOption { + //GtkWidget *menuitem; + char *value; + bool_t init_val; +}; + +struct _DilloHtmlSelect { + //GtkWidget *menu; + int size; + + DilloHtmlOption *options; + int num_options; + int num_options_max; +}; + +typedef enum { + DILLO_HTML_INPUT_UNKNOWN, + DILLO_HTML_INPUT_TEXT, + DILLO_HTML_INPUT_PASSWORD, + DILLO_HTML_INPUT_CHECKBOX, + DILLO_HTML_INPUT_RADIO, + DILLO_HTML_INPUT_IMAGE, + DILLO_HTML_INPUT_FILE, + DILLO_HTML_INPUT_BUTTON, + DILLO_HTML_INPUT_HIDDEN, + DILLO_HTML_INPUT_SUBMIT, + DILLO_HTML_INPUT_RESET, + DILLO_HTML_INPUT_BUTTON_SUBMIT, + DILLO_HTML_INPUT_BUTTON_RESET, + DILLO_HTML_INPUT_SELECT, + DILLO_HTML_INPUT_SEL_LIST, + DILLO_HTML_INPUT_TEXTAREA, + DILLO_HTML_INPUT_INDEX +} DilloHtmlInputType; + +struct _DilloHtmlInput { + DilloHtmlInputType type; + void *widget; /* May be a FLTKWidget or a Dw Widget. */ + void *embed; /* May be NULL */ + char *name; + char *init_str; /* note: some overloading - for buttons, init_str + is simply the value of the button; for text + entries, it is the initial value */ + DilloHtmlSelect *select; + bool_t init_val; /* only meaningful for buttons */ +}; + +struct _DilloHtml { + dw::core::Widget *dw; /* this is duplicated in the stack (page) */ + + DilloHtmlLB *linkblock; + char *Start_Buf; + size_t Start_Ofs; + size_t CurrTagOfs; + size_t OldTagOfs, OldTagLine; + + DilloHtmlDocumentType DocType; /* as given by DOCTYPE tag */ + float DocTypeVersion; /* HTML or XHTML version number */ + + misc::SimpleVector<DilloHtmlState> *stack; + + int InFlags; /* tracks which tags we are in */ + + Dstr *Stash; + bool_t StashSpace; + + char *SPCBuf; /* Buffer for white space */ + + int pre_column; /* current column, used in PRE tags with tabs */ + bool_t PreFirstChar; /* used to skip the first CR or CRLF in PRE tags */ + bool_t PrevWasCR; /* Flag to help parsing of "\r\n" in PRE tags */ + bool_t PrevWasOpenTag; /* Flag to help deferred parsing of white space */ + bool_t SPCPending; /* Flag to help deferred parsing of white space */ + bool_t InVisitedLink; /* used to 'contrast_visited_colors' */ + bool_t ReqTagClose; /* Flag to help handling bad-formed HTML */ + bool_t CloseOneTag; /* Flag to help Html_tag_cleanup_at_close() */ + bool_t TagSoup; /* Flag to enable the parser's cleanup functions */ + char *NameVal; /* used for validation of "NAME" and "ID" in <A> */ + + /* element counters: used for validation purposes */ + uchar_t Num_HTML, Num_HEAD, Num_BODY, Num_TITLE; + + Dstr *attr_data; + + BrowserWindow *bw; +}; + +/* + * Exported functions + */ +void a_Html_form_event_handler(void *data, + form::Form *form_receiver, + void *v_resource); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + + +#endif /* __HTML_HH__ */ diff --git a/src/image.cc b/src/image.cc new file mode 100644 index 00000000..f815d575 --- /dev/null +++ b/src/image.cc @@ -0,0 +1,226 @@ +/* + * File: image.cc + * + * Copyright (C) 2005 Jorge Arellano Cid <jcid@dillo.org>, + * Sebastian Geerken <sgeerken@dillo.org> + * + * 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 3 of the License, or + * (at your option) any later version. + */ + +/* + * This file implements image data transfer methods. It handles the transfer + * of data from an Image to a DwImage widget. + */ + +#include <stdio.h> +#include <string.h> + +#include "msg.h" + +#include "image.hh" +#include "dw/core.hh" +#include "dw/image.hh" + +using namespace dw::core; + +// Image to Object-Image macro +#define OI(Image) ((dw::Image*)(Image->dw)) + + +/* + * Local data + */ +static size_t linebuf_size = 0; +static uchar_t *linebuf = NULL; + + +/* + * Create and initialize a new image structure. + */ +DilloImage *a_Image_new(int width, + int height, + const char *alt_text, + int32_t bg_color) +{ + DilloImage *Image; + + Image = dNew(DilloImage, 1); + Image->dw = (void*) new dw::Image(alt_text); + Image->width = 0; + Image->height = 0; + Image->cmap = NULL; + Image->in_type = DILLO_IMG_TYPE_NOTSET; + Image->bg_color = bg_color; + Image->ProcessedBytes = 0; + Image->BitVec = NULL; + Image->State = IMG_Empty; + + Image->RefCount = 1; + + return Image; +} + +/* + * Deallocate an Image structure + */ +static void Image_free(DilloImage *Image) +{ + a_Bitvec_free(Image->BitVec); + dFree(Image); +} + +/* + * Unref and free if necessary + */ +void a_Image_unref(DilloImage *Image) +{ + _MSG(" %d ", Image->RefCount); + if (Image && --Image->RefCount == 0) + Image_free(Image); +} + +/* + * Add a reference to an Image struct + */ +void a_Image_ref(DilloImage *Image) +{ + if (Image) + ++Image->RefCount; +} + +/* + * Decode 'buf' (an image line) into RGB format. + */ +static uchar_t * + Image_line(DilloImage *Image, const uchar_t *buf, const uchar_t *cmap, int y) +{ + uint_t x; + + switch (Image->in_type) { + case DILLO_IMG_TYPE_INDEXED: + if (cmap) { + for (x = 0; x < Image->width; x++) + memcpy(linebuf + x * 3, cmap + buf[x] * 3, 3); + } else { + MSG("Gif:: WARNING, image lacks a color map\n"); + } + break; + case DILLO_IMG_TYPE_GRAY: + for (x = 0; x < Image->width; x++) + memset(linebuf + x * 3, buf[x], 3); + break; + case DILLO_IMG_TYPE_RGB: + /* avoid a memcpy here! --Jcid */ + return (uchar_t *)buf; + case DILLO_IMG_TYPE_NOTSET: + MSG_ERR("Image_line: type not set...\n"); + break; + } + return linebuf; +} + +/* + * Set initial parameters of the image + */ +void a_Image_set_parms(DilloImage *Image, void *v_imgbuf, DilloUrl *url, + int version, uint_t width, uint_t height, + DilloImgType type) +{ + _MSG("a_Image_set_parms: width=%d height=%d\n", width, height); + + OI(Image)->setBuffer((Imgbuf*)v_imgbuf); + + if (!Image->BitVec) + Image->BitVec = a_Bitvec_new(height); + Image->in_type = type; + Image->width = width; + Image->height = height; + if (3 * width > linebuf_size) { + linebuf_size = 3 * width; + linebuf = (uchar_t*) dRealloc(linebuf, linebuf_size); + } + Image->State = IMG_SetParms; +} + +/* + * Reference the dicache entry color map + */ +void a_Image_set_cmap(DilloImage *Image, const uchar_t *cmap) +{ + Image->cmap = cmap; + Image->State = IMG_SetCmap; +} + +/* + * Implement the write method + */ +void a_Image_write(DilloImage *Image, void *v_imgbuf, + const uchar_t *buf, uint_t y, int decode) +{ + uchar_t *newbuf; + + dReturn_if_fail ( y < Image->height ); + + if (decode) { + /* Decode 'buf' and copy it into the DicEntry buffer */ + newbuf = Image_line(Image, buf, Image->cmap, y); + ((Imgbuf*)v_imgbuf)->copyRow(y, (byte *)newbuf); + } + a_Bitvec_set_bit(Image->BitVec, y); + Image->State = IMG_Write; + + /* Update the row in DwImage */ + OI(Image)->drawRow(y); +} + +/* + * Implement the close method + */ +void a_Image_close(DilloImage *Image) +{ + a_Image_unref(Image); +} + + +// Wrappers for Imgbuf ------------------------------------------------------- + +/* + * Increment reference count for an Imgbuf + */ +void a_Image_imgbuf_ref(void *v_imgbuf) +{ + ((Imgbuf*)v_imgbuf)->ref(); +} + +/* + * Decrement reference count for an Imgbuf + */ +void a_Image_imgbuf_unref(void *v_imgbuf) +{ + ((Imgbuf*)v_imgbuf)->unref(); +} + +/* + * Create a new Imgbuf + */ +void *a_Image_imgbuf_new(void *v_dw, int img_type, int width, int height) +{ + Layout *layout = ((Widget*)v_dw)->getLayout(); + if (!layout) { + MSG_ERR("a_Image_imgbuf_new: layout is NULL.\n"); + exit(1); + } + return (void*)layout->createImgbuf(Imgbuf::RGB, width, height); +} + +/* + * Last reference for this Imgbuf? + */ +int a_Image_imgbuf_last_reference(void *v_imgbuf) +{ + return ((Imgbuf*)v_imgbuf)->lastReference () ? 1 : 0; +} + diff --git a/src/image.hh b/src/image.hh new file mode 100644 index 00000000..fde51838 --- /dev/null +++ b/src/image.hh @@ -0,0 +1,79 @@ +#ifndef __IMAGE_HH__ +#define __IMAGE_HH__ + +// The DilloImage data-structure and methods + + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +#include "bitvec.h" +#include "url.h" + +typedef struct _DilloImage DilloImage; + +typedef enum { + DILLO_IMG_TYPE_INDEXED, + DILLO_IMG_TYPE_RGB, + DILLO_IMG_TYPE_GRAY, + DILLO_IMG_TYPE_NOTSET /* Initial value */ +} DilloImgType; + +/* These will reflect the Image's "state" */ +typedef enum { + IMG_Empty, /* Just created the entry */ + IMG_SetParms, /* Parameters set */ + IMG_SetCmap, /* Color map set */ + IMG_Write, /* Feeding the entry */ + IMG_Close, /* Whole image got! */ + IMG_Abort /* Image transfer aborted */ +} ImageState; + +struct _DilloImage { + void *dw; + + /* Parameters as told by image data */ + uint_t width; + uint_t height; + + const uchar_t *cmap; /* Color map (only for indexed) */ + DilloImgType in_type; /* Image Type */ + int32_t bg_color; /* Background color */ + + int ProcessedBytes; /* Amount of bytes already decoded */ + bitvec_t *BitVec; /* Bit vector for decoded rows */ + ImageState State; + + int RefCount; /* Reference counter */ +}; + + +/* + * Function prototypes + */ +DilloImage *a_Image_new(int width, int height, + const char *alt_text, int32_t bg_color); +void a_Image_ref(DilloImage *Image); +void a_Image_unref(DilloImage *Image); + +void a_Image_set_parms(DilloImage *Image, void *v_imgbuf, DilloUrl *url, + int version, uint_t width, uint_t height, + DilloImgType type); +void a_Image_set_cmap(DilloImage *Image, const uchar_t *cmap); +void a_Image_write(DilloImage *Image, void *v_imgbuf, + const uchar_t *buf, uint_t y, int decode); +void a_Image_close(DilloImage *Image); + +void a_Image_imgbuf_ref(void *v_imgbuf); +void a_Image_imgbuf_unref(void *v_imgbuf); +void *a_Image_imgbuf_new(void *v_dw, int img_type, int width, int height) ; +int a_Image_imgbuf_last_reference(void *v_imgbuf); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __IMAGE_HH__ */ + diff --git a/src/jpeg.c b/src/jpeg.c new file mode 100644 index 00000000..fbf3eeee --- /dev/null +++ b/src/jpeg.c @@ -0,0 +1,334 @@ +/* + * File: jpeg.c + * + * Copyright (C) 1997 Raph Levien <raph@acm.org> + * Copyright (C) 2000-2006 Jorge Arellano Cid <jcid@dillo.org> + * + * 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 3 of the License, or + * (at your option) any later version. + */ + +/* + * The jpeg decoder for dillo. It is responsible for decoding JPEG data + * and transferring it to the dicache. It uses libjpeg to do the actual + * decoding. + */ + +#include <config.h> +#ifdef ENABLE_JPEG + +#include <stdio.h> +#include <setjmp.h> + +/* avoid a redefinition of HAVE_STDLIB_H with old jpeglib.h */ +#ifdef HAVE_STDLIB_H +# undef HAVE_STDLIB_H +#endif +#include <jpeglib.h> +#ifdef HAVE_STDLIB_H +# undef HAVE_STDLIB_H +#endif + +#include "image.hh" +#include "web.hh" +#include "cache.h" +#include "dicache.h" + +#define DEBUG_LEVEL 6 +#include "debug.h" + +typedef enum { + DILLO_JPEG_INIT, + DILLO_JPEG_STARTING, + DILLO_JPEG_READING, + DILLO_JPEG_DONE, + DILLO_JPEG_ERROR +} DilloJpegState; + +/* An implementation of a suspending source manager */ + +typedef struct { + struct jpeg_source_mgr pub; /* public fields */ + struct DilloJpeg *jpeg; /* a pointer back to the jpeg object */ +} my_source_mgr; + +struct my_error_mgr { + struct jpeg_error_mgr pub; /* "public" fields */ + jmp_buf setjmp_buffer; /* for return to caller */ +}; +typedef struct my_error_mgr * my_error_ptr; + +typedef struct DilloJpeg { + DilloImage *Image; + DilloUrl *url; + int version; + + my_source_mgr Src; + + DilloJpegState state; + size_t Start_Ofs, Skip, NewStart; + char *Data; + + uint_t y; + + struct jpeg_decompress_struct cinfo; + struct my_error_mgr jerr; +} DilloJpeg; + +/* + * Forward declarations + */ +static DilloJpeg *Jpeg_new(DilloImage *Image, DilloUrl *url, int version); +static void Jpeg_callback(int Op, CacheClient_t *Client); +static void Jpeg_write(DilloJpeg *jpeg, void *Buf, uint_t BufSize); +static void Jpeg_close(DilloJpeg *jpeg, CacheClient_t *Client); +METHODDEF(void) Jpeg_errorexit (j_common_ptr cinfo); + +/* exported function */ +void *a_Jpeg_image(const char *Type, void *P, CA_Callback_t *Call, + void **Data); + + +/* this is the routine called by libjpeg when it detects an error. */ +METHODDEF(void) Jpeg_errorexit (j_common_ptr cinfo) +{ + /* display message and return to setjmp buffer */ + my_error_ptr myerr = (my_error_ptr) cinfo->err; + (*cinfo->err->output_message) (cinfo); + longjmp(myerr->setjmp_buffer, 1); +} + +/* + * MIME handler for "image/jpeg" type + * (Sets Jpeg_callback or a_Dicache_callback as the cache-client) + */ +void *a_Jpeg_image(const char *Type, void *P, CA_Callback_t *Call, + void **Data) +{ + DilloWeb *web = P; + DICacheEntry *DicEntry; + + if (!web->Image) + web->Image = a_Image_new(0, 0, NULL, 0); + + /* Add an extra reference to the Image (for dicache usage) */ + a_Image_ref(web->Image); + + DicEntry = a_Dicache_get_entry(web->url); + if (!DicEntry) { + /* Let's create an entry for this image... */ + DicEntry = a_Dicache_add_entry(web->url); + + /* ... and let the decoder feed it! */ + *Data = Jpeg_new(web->Image, DicEntry->url, DicEntry->version); + *Call = (CA_Callback_t) Jpeg_callback; + } else { + /* Let's feed our client from the dicache */ + a_Dicache_ref(DicEntry->url, DicEntry->version); + *Data = web->Image; + *Call = (CA_Callback_t) a_Dicache_callback; + } + return (web->Image->dw); +} + +/* + * Finish the decoding process + */ +static void Jpeg_close(DilloJpeg *jpeg, CacheClient_t *Client) +{ + a_Dicache_close(jpeg->url, jpeg->version, Client); + + if (jpeg->state != DILLO_JPEG_DONE) { + jpeg_destroy_decompress(&(jpeg->cinfo)); + } + dFree(jpeg); +} + +static void init_source(j_decompress_ptr cinfo) +{ +} + +static boolean fill_input_buffer(j_decompress_ptr cinfo) +{ + DilloJpeg *jpeg = ((my_source_mgr *) cinfo->src)->jpeg; + + DEBUG_MSG(5, "fill_input_buffer\n"); +#if 0 + if (!cinfo->src->bytes_in_buffer) { + DEBUG_MSG(5, "fill_input_buffer: %ld bytes in buffer\n", + (long)cinfo->src->bytes_in_buffer); + + jpeg->Start_Ofs = (ulong_t) jpeg->cinfo.src->next_input_byte - + (ulong_t) jpeg->Data; +#endif + if (jpeg->Skip) { + jpeg->Start_Ofs = jpeg->NewStart + jpeg->Skip - 1; + jpeg->Skip = 0; + } else { + jpeg->Start_Ofs = (ulong_t) jpeg->cinfo.src->next_input_byte - + (ulong_t) jpeg->Data; + } + return FALSE; +#if 0 + } + return TRUE; +#endif +} + +static void skip_input_data(j_decompress_ptr cinfo, long num_bytes) +{ + DilloJpeg *jpeg; + + if (num_bytes < 1) + return; + jpeg = ((my_source_mgr *) cinfo->src)->jpeg; + + DEBUG_MSG(5, "skip_input_data: Start_Ofs = %lu, num_bytes = %ld," + " %ld bytes in buffer\n", + (ulong_t)jpeg->Start_Ofs, num_bytes, + (long)cinfo->src->bytes_in_buffer); + + cinfo->src->next_input_byte += num_bytes; + if (num_bytes < (long)cinfo->src->bytes_in_buffer) { + cinfo->src->bytes_in_buffer -= num_bytes; + } else { + jpeg->Skip += num_bytes - cinfo->src->bytes_in_buffer + 1; + cinfo->src->bytes_in_buffer = 0; + } +} + +static void term_source(j_decompress_ptr cinfo) +{ +} + +static DilloJpeg *Jpeg_new(DilloImage *Image, DilloUrl *url, int version) +{ + my_source_mgr *src; + DilloJpeg *jpeg = dMalloc(sizeof(*jpeg)); + + jpeg->Image = Image; + jpeg->url = url; + jpeg->version = version; + + jpeg->state = DILLO_JPEG_INIT; + jpeg->Start_Ofs = 0; + jpeg->Skip = 0; + + /* decompression step 1 (see libjpeg.doc) */ + jpeg->cinfo.err = jpeg_std_error(&(jpeg->jerr.pub)); + jpeg->jerr.pub.error_exit = Jpeg_errorexit; + + jpeg_create_decompress(&(jpeg->cinfo)); + + /* decompression step 2 (see libjpeg.doc) */ + jpeg->cinfo.src = &jpeg->Src.pub; + src = &jpeg->Src; + src->pub.init_source = init_source; + src->pub.fill_input_buffer = fill_input_buffer; + src->pub.skip_input_data = skip_input_data; + src->pub.resync_to_restart = jpeg_resync_to_restart;/* use default method */ + src->pub.term_source = term_source; + src->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */ + src->pub.next_input_byte = NULL;/* until buffer loaded */ + + src->jpeg = jpeg; + + /* decompression steps continue in write method */ + return jpeg; +} + +static void Jpeg_callback(int Op, CacheClient_t *Client) +{ + if (Op) + Jpeg_close(Client->CbData, Client); + else + Jpeg_write(Client->CbData, Client->Buf, Client->BufSize); +} + +/* + * Receive and process new chunks of JPEG image data + */ +static void Jpeg_write(DilloJpeg *jpeg, void *Buf, uint_t BufSize) +{ + DilloImgType type; + uchar_t *linebuf; + JSAMPLE *array[1]; + int num_read; + + DEBUG_MSG(5, "Jpeg_write: (%p) Bytes in buff: %ld Ofs: %lu\n", jpeg, + (long) BufSize, (ulong_t)jpeg->Start_Ofs); + + /* See if we are supposed to skip ahead. */ + if (BufSize <= jpeg->Start_Ofs) + return; + + /* Concatenate with the partial input, if any. */ + jpeg->cinfo.src->next_input_byte = (uchar_t *)Buf + jpeg->Start_Ofs; + jpeg->cinfo.src->bytes_in_buffer = BufSize - jpeg->Start_Ofs; + jpeg->NewStart = BufSize; + jpeg->Data = Buf; + + if (setjmp(jpeg->jerr.setjmp_buffer)) { + /* If we get here, the JPEG code has signaled an error. */ + jpeg->state = DILLO_JPEG_ERROR; + } + + /* Process the bytes in the input buffer. */ + if (jpeg->state == DILLO_JPEG_INIT) { + + /* decompression step 3 (see libjpeg.doc) */ + if (jpeg_read_header(&(jpeg->cinfo), TRUE) != JPEG_SUSPENDED) { + type = DILLO_IMG_TYPE_GRAY; + if (jpeg->cinfo.num_components == 1) + type = DILLO_IMG_TYPE_GRAY; + else if (jpeg->cinfo.num_components == 3) + type = DILLO_IMG_TYPE_RGB; + else + DEBUG_MSG(5, "jpeg: can't handle %d component images\n", + jpeg->cinfo.num_components); + a_Dicache_set_parms(jpeg->url, jpeg->version, jpeg->Image, + (uint_t)jpeg->cinfo.image_width, + (uint_t)jpeg->cinfo.image_height, + type); + + /* decompression step 4 (see libjpeg.doc) */ + jpeg->state = DILLO_JPEG_STARTING; + } + } + if (jpeg->state == DILLO_JPEG_STARTING) { + /* decompression step 5 (see libjpeg.doc) */ + if (jpeg_start_decompress(&(jpeg->cinfo))) { + jpeg->y = 0; + jpeg->state = DILLO_JPEG_READING; + } + } + if (jpeg->state == DILLO_JPEG_READING) { + linebuf = dMalloc(jpeg->cinfo.image_width * + jpeg->cinfo.num_components); + array[0] = linebuf; + while (jpeg->y < jpeg->cinfo.image_height) { + num_read = jpeg_read_scanlines(&(jpeg->cinfo), array, 1); + if (num_read == 0) + break; + a_Dicache_write(jpeg->Image, jpeg->url, jpeg->version, + linebuf, 0, jpeg->y); + + jpeg->y++; + } + if (jpeg->y == jpeg->cinfo.image_height) { + DEBUG_MSG(5, "height achieved\n"); + + jpeg_destroy_decompress(&(jpeg->cinfo)); + jpeg->state = DILLO_JPEG_DONE; + } + dFree(linebuf); + } + if (jpeg->state == DILLO_JPEG_ERROR) { + jpeg_destroy_decompress(&(jpeg->cinfo)); + jpeg->state = DILLO_JPEG_DONE; + } +} + +#endif /* ENABLE_JPEG */ diff --git a/src/klist.c b/src/klist.c new file mode 100644 index 00000000..113472b6 --- /dev/null +++ b/src/klist.c @@ -0,0 +1,118 @@ +/* + * File: klist.c + * + * Copyright 2001 Jorge Arellano Cid <jcid@dillo.org> + * + * 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 3 of the License, or + * (at your option) any later version. + */ + +/* + * A simple ADT for Key-Data pairs + * + * NOTE: this ADT is not perfect. The possibility of holding a Key, after + * its data has been removed, long enough for the key-counter to reset and + * reuse the same key is very low, but EXISTS. So, the responsibility + * remains with the caller. + */ + +#include "klist.h" + + +/* + * Compare function for searching data by its key + */ +static int Klist_node_by_key_cmp(const void *Node, const void *key) +{ + return ((KlistNode_t *)Node)->Key - VOIDP2INT(key); +} + +/* + * Return the data pointer for a given Key (or NULL if not found) + */ +void *a_Klist_get_data(Klist_t *Klist, int Key) +{ + void *aux; + + if (!Klist) + return NULL; + aux = dList_find_sorted(Klist->List, INT2VOIDP(Key), Klist_node_by_key_cmp); + return (aux) ? ((KlistNode_t*)aux)->Data : NULL; +} + +/* + * Insert a data pointer and return a key for it. + */ +int a_Klist_insert(Klist_t **Klist, void *Data) +{ + KlistNode_t *Node; + + if (!*Klist) { + (*Klist) = dNew(Klist_t, 1); + (*Klist)->List = dList_new(32); + (*Klist)->Clean = 1; + (*Klist)->Counter = 0; + } + + /* This avoids repeated keys at the same time */ + do { + if (++((*Klist)->Counter) == 0) { + (*Klist)->Counter = 1; + (*Klist)->Clean = 0; + } + } while (!((*Klist)->Clean) && + a_Klist_get_data((*Klist), (*Klist)->Counter)); + + Node = dNew(KlistNode_t, 1); + Node->Key = (*Klist)->Counter; + Node->Data = Data; + dList_insert_sorted((*Klist)->List, Node, Klist_node_by_key_cmp); + return (*Klist)->Counter; +} + +/* + * Remove data by Key + */ +void a_Klist_remove(Klist_t *Klist, int Key) +{ + void *data; + + data = dList_find_sorted(Klist->List, INT2VOIDP(Key),Klist_node_by_key_cmp); + if (data) { + dList_remove(Klist->List, data); + dFree(data); + } + if (dList_length(Klist->List) == 0) + Klist->Clean = 1; +} + +/* + * Return the number of elements in the Klist + */ +int a_Klist_length(Klist_t *Klist) +{ + return dList_length(Klist->List); +} + +/* + * Free a Klist + */ +void a_Klist_free(Klist_t **KlistPtr) +{ + void *node; + Klist_t *Klist = *KlistPtr; + + if (!Klist) + return; + + while (dList_length(Klist->List) > 0) { + node = dList_nth_data(Klist->List, 0); + dList_remove_fast(Klist->List, 0); + dFree(node); + } + dFree(Klist); + *KlistPtr = NULL; +} + diff --git a/src/klist.h b/src/klist.h new file mode 100644 index 00000000..53b2bc31 --- /dev/null +++ b/src/klist.h @@ -0,0 +1,40 @@ +#ifndef __KLIST_H__ +#define __KLIST_H__ + +#include "../dlib/dlib.h" + + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef struct _KlistNode KlistNode_t; +typedef struct _Klist Klist_t; + +struct _KlistNode { + int Key; /* primary key */ + void *Data; /* data reference */ +}; + +struct _Klist { + Dlist *List; + int Clean; /* check flag */ + int Counter; /* counter (for making keys) */ +}; + + +/* + * Function prototypes + */ +void* a_Klist_get_data(Klist_t *Klist, int Key); +int a_Klist_insert(Klist_t **Klist, void *Data); +void a_Klist_remove(Klist_t *Klist, int Key); +int a_Klist_length(Klist_t *Klist); +void a_Klist_free(Klist_t **Klist); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __KLIST_H__ */ diff --git a/src/list.h b/src/list.h new file mode 100644 index 00000000..8623bf09 --- /dev/null +++ b/src/list.h @@ -0,0 +1,49 @@ +/* + * Fast list methods + * Feb 2000 --Jcid + * + */ + +#ifndef __LIST_H__ +#define __LIST_H__ + +/* + * a_List_resize() + * + * Make sure there's space for 'num_items' items within the list + * (First, allocate an 'alloc_step' sized chunk, after that, double the + * list size --to make it faster) + */ +#define a_List_resize(list,num_items,alloc_step) \ + if (!list) { \ + list = dMalloc(alloc_step * sizeof((*list))); \ + } \ + if (num_items >= alloc_step){ \ + while ( num_items >= alloc_step ) \ + alloc_step <<= 1; \ + list = dRealloc(list, alloc_step * sizeof((*list))); \ + } + + +/* + * a_List_add() + * + * Make sure there's space for one more item within the list. + */ +#define a_List_add(list,num_items,alloc_step) \ + a_List_resize(list,num_items,alloc_step) + + +/* + * a_List_remove() + * + * Quickly remove an item from the list + * ==> We preserve relative position, but not the element index <== + */ +#define a_List_remove(list, item, num_items) \ + if (list && item < num_items) { \ + list[item] = list[--num_items]; \ + } + + +#endif /* __LIST_H__ */ diff --git a/src/menu.cc b/src/menu.cc new file mode 100644 index 00000000..1de42acf --- /dev/null +++ b/src/menu.cc @@ -0,0 +1,358 @@ +/* + * File: menu.cc + * + * Copyright (C) 2005 Jorge Arellano Cid <jcid@dillo.org> + * + * 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 3 of the License, or + * (at your option) any later version. + */ + +// Functions/Methods for menus + +#include <stdio.h> +#include <stdarg.h> +#include <fltk/events.h> +#include <fltk/PopupMenu.h> +#include <fltk/Item.h> +#include <fltk/Divider.h> + +#include "menu.hh" +#include "uicmd.hh" +#include "history.h" + +using namespace fltk; + +/* + * Local data + */ + +// (This data can be encapsulated inside a class for each popup, but +// as popups are modal, there's no need). +// Weak reference to the popup's URL +static DilloUrl *popup_url = NULL; +// Weak reference to the popup's bw +static BrowserWindow *popup_bw = NULL; +// Weak reference to the page's HTML bugs +static const char *popup_bugs = NULL; +// History popup direction (-1 = back, 1 = forward). +static int history_direction = -1; +// History popup, list of URL-indexes. +static int *history_list = NULL; + +/* + * Local sub class + * (Used to add the hint for history popup menus) + */ + +class NewItem : public Item { +public: + NewItem (const char* label) : Item(label) {}; + void draw(); +}; + +/* + * This adds a call to a_UIcmd_set_msg() to show the URL in the status bar + * TODO: erase the URL on popup close. + */ +void NewItem::draw() { + if (flags() & SELECTED) { + DilloUrl *url = a_History_get_url(history_list[((int)user_data())-1]); + a_UIcmd_set_msg(popup_bw, "%s", URL_STR(url)); + } + Item::draw(); +} + + +//-------------------------------------------------------------------------- +static void Menu_link_cb(Widget* , void *v_url) +{ + printf("Menu_link_cb: click! :-)\n"); +} + +/* + * Open URL in new window + */ +static void Menu_open_url_nw_cb(Widget* ) +{ + printf("Open URL in new window cb: click! :-)\n"); + a_UIcmd_open_url_nw(popup_bw, popup_url); +} + +/* + * Add bookmark + */ +static void Menu_add_bookmark_cb(Widget* ) +{ + a_UIcmd_add_bookmark(popup_bw, popup_url); +} + +/* + * Find text + */ +static void Menu_find_text_cb(Widget* ) +{ + a_UIcmd_fullscreen_toggle(popup_bw); +} + +/* + * Save link + */ +static void Menu_save_link_cb(Widget* ) +{ + a_UIcmd_save_link(popup_bw, popup_url); +} + +/* + * Save current page + */ +static void Menu_save_page_cb(Widget* ) +{ + a_UIcmd_save(popup_bw); +} + +/* + * Save current page + */ +static void Menu_view_page_source_cb(Widget* ) +{ + a_UIcmd_view_page_source(popup_url); +} + +/* + * View current page's bugs + */ +static void Menu_view_page_bugs_cb(Widget* ) +{ + a_UIcmd_view_page_bugs(popup_bw); +} + +/* + * Validate URL with the W3C + */ +static void Menu_bugmeter_validate_w3c_cb(Widget* ) +{ + Dstr *dstr = dStr_sized_new(128); + + dStr_sprintf(dstr, "http://validator.w3.org/check?uri=%s", + URL_STR(popup_url)); + a_UIcmd_open_urlstr(popup_bw, dstr->str); + dStr_free(dstr, 1); +} + +/* + * Validate URL with the WDG + */ +static void Menu_bugmeter_validate_wdg_cb(Widget* ) +{ + Dstr *dstr = dStr_sized_new(128); + + dStr_sprintf(dstr, + "http://www.htmlhelp.org/cgi-bin/validate.cgi?url=%s&warnings=yes", + URL_STR(popup_url)); + a_UIcmd_open_urlstr(popup_bw, dstr->str); + dStr_free(dstr, 1); +} + +/* + * Show info page for the bug meter + */ +static void Menu_bugmeter_about_cb(Widget* ) +{ + a_UIcmd_open_urlstr(popup_bw, "http://www.dillo.org/help/bug_meter.html"); +} + +/* + * Navigation History callback. + * Go to selected URL. + */ +static void Menu_history_cb(Widget *wid, void *data) +{ + int k = event_button(); + int offset = history_direction * (int)data; + + if (k == 2) { + /* middle button, open in a new window */ + a_UIcmd_nav_jump(popup_bw, offset, 1); + } else { + a_UIcmd_nav_jump(popup_bw, offset, 0); + } +} + +/* + * Page popup menu (construction & popup) + */ +void a_Menu_page_popup(BrowserWindow *bw, DilloUrl *url, const char *bugs_txt) +{ + // One menu for every browser window + static PopupMenu *pm = 0; + // Active/inactive control. + static Item *view_page_bugs_item = 0; + + popup_bw = bw; + popup_url = url; + popup_bugs = bugs_txt; + if (!pm) { + Item *i; + pm = new PopupMenu(0,0,0,0,"&PAGE OPTIONS"); + pm->begin(); + i = new Item("View page Source"); + i->callback(Menu_view_page_source_cb); + //i->shortcut(CTRL+'n'); + i = view_page_bugs_item = new Item("View page Bugs"); + i->callback(Menu_view_page_bugs_cb); + i = new Item("Bookmark this page"); + i->callback(Menu_add_bookmark_cb); + new Divider(); + i = new Item("&Find Text"); + i->callback(Menu_find_text_cb); + i->shortcut(CTRL+'f'); + i = new Item("Jump to..."); + i->deactivate(); + new Divider(); + i = new Item("Save page As..."); + i->callback(Menu_save_page_cb); + + pm->type(PopupMenu::POPUP123); + pm->end(); + } + + if (bugs_txt == NULL) + view_page_bugs_item->deactivate(); + else + view_page_bugs_item->activate(); + + // Make the popup a child of the calling UI object + ((Group *)bw->ui)->add(pm); + + pm->popup(); +} + +/* + * Link popup menu (construction & popup) + */ +void a_Menu_link_popup(BrowserWindow *bw, DilloUrl *url) +{ + // One menu for every browser window + static PopupMenu *pm = 0; + + popup_bw = bw; + popup_url = url; + if (!pm) { + Item *i; + pm = new PopupMenu(0,0,0,0,"&LINK OPTIONS"); + //pm->callback(Menu_link_cb, url); + pm->begin(); + i = new Item("Open Link in New Window"); + i->callback(Menu_open_url_nw_cb); + i = new Item("Bookmark this Link"); + i->callback(Menu_add_bookmark_cb); + i = new Item("Copy Link location"); + i->callback(Menu_link_cb, url); + i->deactivate(); + new Divider(); + i = new Item("Save Link As..."); + i->callback(Menu_save_link_cb); + + pm->type(PopupMenu::POPUP123); + pm->end(); + } + + // Make the popup a child of the calling UI object + ((Group *)bw->ui)->add(pm); + + pm->popup(); +} + +/* + * Bugmeter popup menu (construction & popup) + */ +void a_Menu_bugmeter_popup(BrowserWindow *bw, DilloUrl *url) +{ + // One menu for every browser window + static PopupMenu *pm = 0; + + popup_bw = bw; + popup_url = url; + if (!pm) { + Item *i; + pm = new PopupMenu(0,0,0,0,"&BUG METER OPTIONS"); + pm->begin(); + i = new Item("Validate URL with W3C"); + i->callback(Menu_bugmeter_validate_w3c_cb); + i = new Item("Validate URL with WDG"); + i->callback(Menu_bugmeter_validate_wdg_cb); + new Divider(); + i = new Item("About Bug Meter..."); + i->callback(Menu_bugmeter_about_cb); + + pm->type(PopupMenu::POPUP123); + pm->end(); + } + + // Make the popup a child of the calling UI object + ((Group *)bw->ui)->add(pm); + + pm->popup(); +} + +/* + * Navigation History popup menu (construction & popup) + * + * direction: {backward = -1, forward = 1} + */ +void a_Menu_history_popup(BrowserWindow *bw, int direction) +{ + // One menu for every browser window + static PopupMenu *pm = 0; + Item *it; + int i; + + popup_bw = bw; + history_direction = direction; + + // TODO: hook popdown event with delete or similar. + if (pm) + delete(pm); + if (history_list) + dFree(history_list); + + if (direction == -1) { + pm = new PopupMenu(0,0,0,0, "&PREVIOUS PAGES"); + } else { + pm = new PopupMenu(0,0,0,0, "&FOLLOWING PAGES"); + } + + // Get a list of URLs for this popup + history_list = a_UIcmd_get_history(bw, direction); + + pm->begin(); + for (i = 0; history_list[i] != -1; i += 1) { + // TODO: restrict title size + it = new NewItem(a_History_get_title(history_list[i], 1)); + it->callback(Menu_history_cb, (void*)(i+1)); + } + pm->type(PopupMenu::POPUP123); + pm->end(); + + // Make the popup a child of the calling UI object + // I don't know whether this is necessary... + ((Group *)bw->ui)->add(pm); + + pm->popup(); +} + + +void a_Menu_popup_set_url(BrowserWindow *bw, const DilloUrl *url) { } +void a_Menu_popup_set_url2(BrowserWindow *bw, const DilloUrl *url) { } +void a_Menu_popup_clear_url2(void *menu_popup) { } + +DilloUrl *a_Menu_popup_get_url(BrowserWindow *bw) { return NULL; } + +void a_Menu_pagemarks_new (BrowserWindow *bw) { } +void a_Menu_pagemarks_destroy (BrowserWindow *bw) { } +void a_Menu_pagemarks_add(BrowserWindow *bw, void *page, void *style, + int level) { } +void a_Menu_pagemarks_set_text(BrowserWindow *bw, const char *str) { } + diff --git a/src/menu.hh b/src/menu.hh new file mode 100644 index 00000000..b9a2eb99 --- /dev/null +++ b/src/menu.hh @@ -0,0 +1,34 @@ +#ifndef __MENU_HH__ +#define __MENU_HH__ + +#include "bw.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +void a_Menu_page_popup(BrowserWindow *bw, DilloUrl *url, const char *bugs_txt); +void a_Menu_link_popup(BrowserWindow *bw, DilloUrl *url); +void a_Menu_bugmeter_popup(BrowserWindow *bw, DilloUrl *url); +void a_Menu_history_popup(BrowserWindow *bw, int direction); + +//--------------------- +void a_Menu_popup_set_url(BrowserWindow *bw, const DilloUrl *url); +void a_Menu_popup_set_url2(BrowserWindow *bw, const DilloUrl *url); +void a_Menu_popup_clear_url2(void *menu_popup); + +DilloUrl *a_Menu_popup_get_url(BrowserWindow *bw); + +void a_Menu_pagemarks_new (BrowserWindow *bw); +void a_Menu_pagemarks_destroy (BrowserWindow *bw); +void a_Menu_pagemarks_add(BrowserWindow *bw, void *page, void *style, + int level); +void a_Menu_pagemarks_set_text(BrowserWindow *bw, const char *str); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* MENU_HH */ + diff --git a/src/misc.c b/src/misc.c new file mode 100644 index 00000000..f7ab00d3 --- /dev/null +++ b/src/misc.c @@ -0,0 +1,271 @@ +/* + * File: misc.c + * + * Copyright (C) 2000 Jorge Arellano Cid <jcid@dillo.org>, + * Jörgen Viksell <vsksga@hotmail.com> + * + * 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 3 of the License, or + * (at your option) any later version. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <ctype.h> + +#include "msg.h" +#include "misc.h" + + +/* + * Escape characters as %XX sequences. + * Return value: New string. + */ +char *a_Misc_escape_chars(const char *str, char *esc_set) +{ + static const char *hex = "0123456789ABCDEF"; + char *p = NULL; + Dstr *dstr; + int i; + + dstr = dStr_sized_new(64); + for (i = 0; str[i]; ++i) { + if (str[i] <= 0x1F || str[i] == 0x7F || strchr(esc_set, str[i])) { + dStr_append_c(dstr, '%'); + dStr_append_c(dstr, hex[(str[i] >> 4) & 15]); + dStr_append_c(dstr, hex[str[i] & 15]); + } else { + dStr_append_c(dstr, str[i]); + } + } + p = dstr->str; + dStr_free(dstr, FALSE); + + return p; +} + + +#define TAB_SIZE 8 +/* + * Takes a string and converts any tabs to spaces. + */ +char *a_Misc_expand_tabs(const char *str) +{ + Dstr *New = dStr_new(""); + int len, i, j, pos, old_pos; + char *val; + + if ((len = strlen(str))) { + for (pos = 0, i = 0; i < len; i++) { + if (str[i] == '\t') { + /* Fill with whitespaces until the next tab. */ + old_pos = pos; + pos += TAB_SIZE - (pos % TAB_SIZE); + for (j = old_pos; j < pos; j++) + dStr_append_c(New, ' '); + } else { + dStr_append_c(New, str[i]); + pos++; + } + } + } + val = New->str; + dStr_free(New, FALSE); + return val; +} + +/* TODO: could use dStr ADT! */ +typedef struct ContentType_ { + const char *str; + int len; +} ContentType_t; + +static const ContentType_t MimeTypes[] = { + { "application/octet-stream", 24 }, + { "text/html", 9 }, + { "text/plain", 10 }, + { "image/gif", 9 }, + { "image/png", 9 }, + { "image/jpeg", 10 }, + { NULL, 0 } +}; + +/* + * Detects 'Content-Type' from a data stream sample. + * + * It uses the magic(5) logic from file(1). Currently, it + * only checks the few mime types that Dillo supports. + * + * 'Data' is a pointer to the first bytes of the raw data. + * + * Return value: (0 on success, 1 on doubt, 2 on lack of data). + */ +int a_Misc_get_content_type_from_data(void *Data, size_t Size, const char **PT) +{ + int st = 1; /* default to "doubt' */ + int Type = 0; /* default to "application/octet-stream" */ + char *p = Data; + size_t i, non_ascci; + + /* HTML try */ + for (i = 0; i < Size && isspace(p[i]); ++i); + if ((Size - i >= 5 && !dStrncasecmp(p+i, "<html", 5)) || + (Size - i >= 5 && !dStrncasecmp(p+i, "<head", 5)) || + (Size - i >= 6 && !dStrncasecmp(p+i, "<title", 6)) || + (Size - i >= 14 && !dStrncasecmp(p+i, "<!doctype html", 14)) || + /* this line is workaround for FTP through the Squid proxy */ + (Size - i >= 17 && !dStrncasecmp(p+i, "<!-- HTML listing", 17))) { + + Type = 1; + st = 0; + /* Images */ + } else if (Size >= 4 && !dStrncasecmp(p, "GIF8", 4)) { + Type = 3; + st = 0; + } else if (Size >= 4 && !dStrncasecmp(p, "\x89PNG", 4)) { + Type = 4; + st = 0; + } else if (Size >= 2 && !dStrncasecmp(p, "\xff\xd8", 2)) { + /* JPEG has the first 2 bytes set to 0xffd8 in BigEndian - looking + * at the character representation should be machine independent. */ + Type = 5; + st = 0; + + /* Text */ + } else { + /* We'll assume "text/plain" if the set of chars above 127 is <= 10 + * in a 256-bytes sample. Better heuristics are welcomed! :-) */ + non_ascci = 0; + Size = MIN (Size, 256); + for (i = 0; i < Size; i++) + if ((uchar_t) p[i] > 127) + ++non_ascci; + if (Size == 256) { + Type = (non_ascci > 10) ? 0 : 2; + st = 0; + } else { + Type = (non_ascci > 0) ? 0 : 2; + } + } + + *PT = MimeTypes[Type].str; + return st; +} + +/* + * Check the server-supplied 'Content-Type' against our detected type. + * (some servers seem to default to "text/plain"). + * + * Return value: + * 0, if they match + * -1, if a mismatch is detected + * + * There're many MIME types Dillo doesn't know, they're handled + * as "application/octet-stream" (as the SPEC says). + * + * A mismatch happens when receiving a binary stream as + * "text/plain" or "text/html", or an image that's not an image of its kind. + * + * Note: this is a basic security procedure. + * + */ +int a_Misc_content_type_check(const char *EntryType, const char *DetectedType) +{ + int i; + int st = -1; + + _MSG("Type check: [Srv: %s Det: %s]\n", EntryType, DetectedType); + + if (!EntryType) + return 0; /* there's no mismatch without server type */ + + for (i = 1; MimeTypes[i].str; ++i) + if (dStrncasecmp(EntryType, MimeTypes[i].str, MimeTypes[i].len) == 0) + break; + + if (!MimeTypes[i].str) { + /* type not found, no mismatch */ + st = 0; + } else if (dStrncasecmp(EntryType, "image/", 6) == 0 && + !dStrncasecmp(DetectedType,MimeTypes[i].str,MimeTypes[i].len)){ + /* An image, and there's an exact match */ + st = 0; + } else if (dStrncasecmp(EntryType, "text/", 5) || + dStrncasecmp(DetectedType, "application/", 12)) { + /* Not an application sent as text */ + st = 0; + } + + return st; +} + +/* + * Parse a geometry string. + */ +int a_Misc_parse_geometry(char *str, int *x, int *y, int *w, int *h) +{ + char *p, *t1, *t2; + int n1, n2; + int ret = 0; + + if ((p = strchr(str, 'x')) || (p = strchr(str, 'X'))) { + n1 = strtol(str, &t1, 10); + n2 = strtol(++p, &t2, 10); + if (t1 != str && t2 != p) { + *w = n1; + *h = n2; + ret = 1; + /* parse x,y now */ + p = t2; + n1 = strtol(p, &t1, 10); + n2 = strtol(t1, &t2, 10); + if (t1 != p && t2 != t1) { + *x = n1; + *y = n2; + } + } + } + _MSG("geom: w,h,x,y = (%d,%d,%d,%d)\n", *w, *h, *x, *y); + return ret; +} + +/* + * Encodes string using base64 encoding. + * Return value: new string or NULL if input string is empty. + */ +char *a_Misc_encode_base64(const char *in) +{ + static const char *base64_hex = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + char *out = NULL; + int len, i = 0; + + if (in == NULL) return NULL; + len = strlen(in); + + out = (char *)dMalloc((len + 2) / 3 * 4 + 1); + + for (; len >= 3; len -= 3) { + out[i++] = base64_hex[in[0] >> 2]; + out[i++] = base64_hex[((in[0]<<4) & 0x30) | (in[1]>>4)]; + out[i++] = base64_hex[((in[1]<<2) & 0x3c) | (in[2]>>6)]; + out[i++] = base64_hex[in[2] & 0x3f]; + in += 3; + } + + if (len > 0) { + unsigned char fragment; + out[i++] = base64_hex[in[0] >> 2]; + fragment = (in[0] << 4) & 0x30; + if (len > 1) fragment |= in[1] >> 4; + out[i++] = base64_hex[fragment]; + out[i++] = (len < 2) ? '=' : base64_hex[(in[1] << 2) & 0x3c]; + out[i++] = '='; + } + out[i] = '\0'; + return out; +} diff --git a/src/misc.h b/src/misc.h new file mode 100644 index 00000000..71a00b68 --- /dev/null +++ b/src/misc.h @@ -0,0 +1,25 @@ +#ifndef __DILLO_MISC_H__ +#define __DILLO_MISC_H__ + +#include <stddef.h> /* for size_t */ + + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +char *a_Misc_escape_chars(const char *str, char *esc_set); +char *a_Misc_expand_tabs(const char *str); +int a_Misc_get_content_type_from_data(void *Data, size_t Size,const char **PT); +int a_Misc_content_type_check(const char *EntryType, const char *DetectedType); +int a_Misc_parse_geometry(char *geom, int *x, int *y, int *w, int *h); +char *a_Misc_encode_base64(const char *in); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __DILLO_MISC_H__ */ + diff --git a/src/msg.h b/src/msg.h new file mode 100644 index 00000000..cf5b8fed --- /dev/null +++ b/src/msg.h @@ -0,0 +1,42 @@ +#ifndef __MSG_H__ +#define __MSG_H__ + +#include <stdio.h> +#include "prefs.h" + +/* + * You can disable any MSG* macro by adding the '_' prefix. + */ +#define _MSG(...) +#define _MSG_WARN(...) +#define _MSG_HTML(...) +#define _MSG_HTTP(...) + + +#define MSG(...) \ + D_STMT_START { \ + if (prefs.show_msg) \ + printf(__VA_ARGS__); \ + } D_STMT_END + +#define MSG_WARN(...) \ + D_STMT_START { \ + if (prefs.show_msg) \ + printf("** WARNING **: " __VA_ARGS__); \ + } D_STMT_END + +#define MSG_ERR(...) \ + D_STMT_START { \ + if (prefs.show_msg) \ + printf("** ERROR **: " __VA_ARGS__); \ + } D_STMT_END + +#define MSG_HTML(...) \ + D_STMT_START { \ + Html_msg(html, __VA_ARGS__); \ + } D_STMT_END + +#define MSG_HTTP(...) \ + printf("HTTP warning: " __VA_ARGS__) + +#endif /* __MSG_H__ */ diff --git a/src/nav.c b/src/nav.c new file mode 100644 index 00000000..b2d1d679 --- /dev/null +++ b/src/nav.c @@ -0,0 +1,427 @@ +/* + * File: nav.c + * + * Copyright (C) 2000-2006 Jorge Arellano Cid <jcid@dillo.org> + * + * 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 3 of the License, or + * (at your option) any later version. + */ + +/* Support for a navigation stack */ + +#include <stdio.h> +#include <sys/stat.h> +#include "msg.h" +#include "list.h" +#include "nav.h" +#include "history.h" +#include "web.hh" +#include "uicmd.hh" +#include "dialog.hh" +#include "prefs.h" +#include "capi.h" + +//#define DEBUG_LEVEL 3 +#include "debug.h" + +/* + * Forward declarations + */ +static void Nav_reload(BrowserWindow *bw); + + +/* + * Free memory used by this module + */ +void a_Nav_free(BrowserWindow *bw) +{ + a_Nav_cancel_expect(bw); + dFree(bw->nav_stack); +} + + +/* Navigation stack methods ------------------------------------------------ */ + +/* + * Return current nav_stack pointer [0 based; -1 = empty] + */ +int a_Nav_stack_ptr(BrowserWindow *bw) +{ + return bw->nav_stack_ptr; +} + +/* + * Move the nav_stack pointer + */ +static void Nav_stack_move_ptr(BrowserWindow *bw, int offset) +{ + int nptr; + + dReturn_if_fail (bw != NULL); + if (offset != 0) { + nptr = bw->nav_stack_ptr + offset; + dReturn_if_fail (nptr >= 0 && nptr < bw->nav_stack_size); + bw->nav_stack_ptr = nptr; + } +} + +/* + * Return size of nav_stack [1 based] + */ +int a_Nav_stack_size(BrowserWindow *bw) +{ + return bw->nav_stack_size; +} + +/* + * Add an URL-index in the navigation stack. + */ +static void Nav_stack_add(BrowserWindow *bw, int idx) +{ + dReturn_if_fail (bw != NULL); + + ++bw->nav_stack_ptr; + if (bw->nav_stack_ptr == bw->nav_stack_size) { + a_List_add(bw->nav_stack, bw->nav_stack_size, bw->nav_stack_size_max); + ++bw->nav_stack_size; + } else { + bw->nav_stack_size = bw->nav_stack_ptr + 1; + } + bw->nav_stack[bw->nav_stack_ptr] = idx; +} + +/* + * Remove an URL-index from the navigation stack. + */ +static void Nav_stack_remove(BrowserWindow *bw, int idx) +{ + int sz = a_Nav_stack_size(bw); + + dReturn_if_fail (bw != NULL && idx >=0 && idx < sz); + + for ( ; idx < sz - 1; ++idx) + bw->nav_stack[idx] = bw->nav_stack[idx + 1]; + if (bw->nav_stack_ptr == --bw->nav_stack_size) + --bw->nav_stack_ptr; +} + +/* + * Remove equal adyacent URLs at the top of the stack. + * (It may happen with redirections) + */ +static void Nav_stack_clean(BrowserWindow *bw) +{ + int i; + + dReturn_if_fail (bw != NULL); + + if ((i = a_Nav_stack_size(bw)) >= 2 && + bw->nav_stack[i-2] == bw->nav_stack[i-1]) + Nav_stack_remove(bw, i - 1); +} + + +/* General methods --------------------------------------------------------- */ + +/* + * Create a DilloWeb structure for 'url' and ask the cache to send it back. + * - Also set a few things related to the browser window. + * This function requests the page's root-URL; images and related stuff + * are fetched directly by the HTML module. + */ +static void Nav_open_url(BrowserWindow *bw, const DilloUrl *url, int offset) +{ + DilloUrl *old_url = NULL; + bool_t MustLoad; + int ClientKey; + DilloWeb *Web; + char *loc_text; + bool_t ForceReload = (URL_FLAGS(url) & URL_E2EReload); + + MSG("Nav_open_url: Url=>%s<\n", URL_STR_(url)); + + /* Get the url of the current page */ + if (a_Nav_stack_ptr(bw) != -1) + old_url = a_History_get_url(NAV_TOP(bw)); + + /* Record current scrolling position + * (the strcmp check is necessary because of redirections) */ + loc_text = a_UIcmd_get_location_text(bw); + if (old_url && + !strcmp(URL_STR(old_url), loc_text)) { + + //old_url->scrolling_position_x = + // a_Dw_gtk_scrolled_window_get_scrolling_position_x( + // GTK_DW_SCROLLED_WINDOW(bw->docwin)); + //old_url->scrolling_position_y = + // a_Dw_gtk_scrolled_window_get_scrolling_position_y( + // GTK_DW_SCROLLED_WINDOW(bw->docwin)); + + a_Url_set_pos(old_url, 0, 0); + } + + /* Update navigation-stack-pointer (offset may be zero) */ + Nav_stack_move_ptr(bw, offset); + + /* Page must be reloaded, if old and new url (without anchor) differ */ + MustLoad = ForceReload || !old_url; + if (old_url){ + MustLoad |= (a_Url_cmp(old_url, url) != 0); + MustLoad |= strcmp(URL_STR(old_url), a_UIcmd_get_location_text(bw)); + } + dFree(loc_text); + + if (MustLoad) { + a_Bw_stop_clients(bw, BW_Root + BW_Img); + a_Bw_cleanup(bw); + + // a_Menu_pagemarks_new(bw); + + Web = a_Web_new(url); + Web->bw = bw; + Web->flags |= WEB_RootUrl; + if ((ClientKey = a_Capi_open_url(Web, NULL, NULL)) != 0) { + a_Bw_add_client(bw, ClientKey, 1); + a_Bw_add_url(bw, url); + } + } + + /* Jump to #anchor position */ + if (URL_FRAGMENT_(url)) { + /* todo: push on stack */ + char *pf = a_Url_decode_hex_str(URL_FRAGMENT_(url)); + //a_Dw_render_layout_set_anchor(bw->render_layout, pf); + dFree(pf); + } +} + +/* + * Cancel the last expected url if present. The responsibility + * for actually aborting the data stream remains with the caller. + */ +void a_Nav_cancel_expect(BrowserWindow *bw) +{ + if (bw->nav_expecting) { + if (bw->nav_expect_url) { + a_Url_free(bw->nav_expect_url); + bw->nav_expect_url = NULL; + } + bw->nav_expecting = FALSE; + } +} + +/* + * We have an answer! Set things accordingly. + */ +void a_Nav_expect_done(BrowserWindow *bw) +{ + int idx; + DilloUrl *url; + + dReturn_if_fail(bw != NULL); + + if (bw->nav_expecting) { + url = bw->nav_expect_url; + /* unset E2EReload before adding this url to history */ + a_Url_set_flags(url, URL_FLAGS(url) & ~URL_E2EReload); + idx = a_History_add_url(url); + Nav_stack_add(bw, idx); + + a_Url_free(url); + bw->nav_expect_url = NULL; + bw->nav_expecting = FALSE; + } + Nav_stack_clean(bw); + a_UIcmd_set_buttons_sens(bw); +} + +/* + * Make 'url' the current browsed page (upon data arrival) + * - Set bw to expect the URL data + * - Ask the cache to feed back the requested URL (via Nav_open_url) + */ +void a_Nav_push(BrowserWindow *bw, const DilloUrl *url) +{ + dReturn_if_fail (bw != NULL); + + if (bw->nav_expecting && !a_Url_cmp(bw->nav_expect_url, url) && + !strcmp(URL_FRAGMENT(bw->nav_expect_url),URL_FRAGMENT(url))) { + /* we're already expecting that url (most probably a double-click) */ + return; + } + a_Nav_cancel_expect(bw); + bw->nav_expect_url = a_Url_dup(url); + bw->nav_expecting = TRUE; + Nav_open_url(bw, url, 0); +} + +/* + * Same as a_Nav_push() but in a new window. + */ +void a_Nav_push_nw(BrowserWindow *bw, const DilloUrl *url) +{ + int w, h; + BrowserWindow *newbw; + + a_UIcmd_get_wh(bw, &w, &h); + newbw = a_UIcmd_browser_window_new(w, h); + a_Nav_push(newbw, url); +} + +/* + * Wraps a_Nav_push to match 'DwPage->link' function type + */ +void a_Nav_vpush(void *vbw, const DilloUrl *url) +{ + a_Nav_push(vbw, url); +} + +/* + * Send the browser back to previous page + */ +void a_Nav_back(BrowserWindow *bw) +{ + int idx = a_Nav_stack_ptr(bw); + + a_Nav_cancel_expect(bw); + if (--idx >= 0){ + a_UIcmd_set_msg(bw, ""); + Nav_open_url(bw, a_History_get_url(NAV_IDX(bw,idx)), -1); + } +} + +/* + * Send the browser to next page in the history list + */ +void a_Nav_forw(BrowserWindow *bw) +{ + int idx = a_Nav_stack_ptr(bw); + + a_Nav_cancel_expect(bw); + if (++idx < a_Nav_stack_size(bw)) { + a_UIcmd_set_msg(bw, ""); + Nav_open_url(bw, a_History_get_url(NAV_IDX(bw,idx)), +1); + } +} + +/* + * Redirect the browser to the HOME page! + */ +void a_Nav_home(BrowserWindow *bw) +{ + a_Nav_push(bw, prefs.home); +} + +/* + * This one does a_Nav_reload's job! + */ +static void Nav_reload(BrowserWindow *bw) +{ + DilloUrl *url, *ReqURL; + + a_Nav_cancel_expect(bw); + if (a_Nav_stack_size(bw)) { + url = a_History_get_url(NAV_TOP(bw)); + ReqURL = a_Url_dup(a_History_get_url(NAV_TOP(bw))); + /* Let's make reload be end-to-end */ + a_Url_set_flags(ReqURL, URL_FLAGS(ReqURL) | URL_E2EReload); + /* This is an explicit reload, so clear the SpamSafe flag */ + a_Url_set_flags(ReqURL, URL_FLAGS(ReqURL) & ~URL_SpamSafe); + Nav_open_url(bw, ReqURL, 0); + a_Url_free(ReqURL); + } +} + +/* + * Implement the RELOAD button functionality. + * (Currently it only reloads the page, not its images) + */ +void a_Nav_reload(BrowserWindow *bw) +{ + DilloUrl *url; + int choice; + + a_Nav_cancel_expect(bw); + if (a_Nav_stack_size(bw)) { + url = a_History_get_url(NAV_TOP(bw)); + if (URL_FLAGS(url) & URL_Post) { + /* Attempt to repost data, let's confirm... */ + choice = a_Dialog_choice3("Repost form data?", + "Yes", "*No", "Cancel"); + if (choice == 0) { /* "Yes" */ + DEBUG_MSG(3, "Nav_reload_confirmed\n"); + Nav_reload(bw); + } + + } else { + Nav_reload(bw); + } + } +} + +/* + * Jump to a URL in the Navigation stack. + */ +void a_Nav_jump(BrowserWindow *bw, int offset, int new_bw) +{ + int idx = a_Nav_stack_ptr(bw) + offset; + + if (new_bw) { + a_Nav_push_nw(bw, a_History_get_url(NAV_IDX(bw,idx))); + } else { + a_Nav_cancel_expect(bw); + Nav_open_url(bw, a_History_get_url(NAV_IDX(bw,idx)), offset); + a_UIcmd_set_buttons_sens(bw); + } +} + + +/* Specific methods -------------------------------------------------------- */ + +/* + * Receive data from the cache and save it to a local file + */ +static void Nav_save_cb(int Op, CacheClient_t *Client) +{ + DilloWeb *Web = Client->Web; + int Bytes; + + if (Op){ + struct stat st; + + fflush(Web->stream); + fstat(fileno(Web->stream), &st); + fclose(Web->stream); + a_UIcmd_set_msg(Web->bw, "File saved (%d Bytes)", st.st_size); + } else { + if ((Bytes = Client->BufSize - Web->SavedBytes) > 0) { + Bytes = fwrite(Client->Buf + Web->SavedBytes, 1, Bytes, Web->stream); + Web->SavedBytes += Bytes; + } + } +} + +/* + * Save a URL (from cache or from the net). + */ +void a_Nav_save_url(BrowserWindow *bw, + const DilloUrl *url, const char *filename) +{ + DilloWeb *Web = a_Web_new(url); + Web->bw = bw; + Web->filename = dStrdup(filename); + Web->flags |= WEB_Download; + /* todo: keep track of this client */ + a_Capi_open_url(Web, Nav_save_cb, Web); +} + +/* + * Wrapper for a_Capi_get_buf. + */ +int a_Nav_get_buf(const DilloUrl *Url, char **PBuf, int *BufSize) +{ + return a_Capi_get_buf(Url, PBuf, BufSize); +} + diff --git a/src/nav.h b/src/nav.h new file mode 100644 index 00000000..7b6999ac --- /dev/null +++ b/src/nav.h @@ -0,0 +1,40 @@ +#ifndef __NAV_H__ +#define __NAV_H__ + +#include "bw.h" + + +/* useful macros for the navigation stack */ +#define NAV_IDX(bw, i) (bw)->nav_stack[i] +#define NAV_TOP(bw) (bw)->nav_stack[(bw)->nav_stack_ptr] + + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +void a_Nav_push(BrowserWindow *bw, const DilloUrl *url); +void a_Nav_push_nw(BrowserWindow *bw, const DilloUrl *url); +void a_Nav_vpush(void *vbw, const DilloUrl *url); +void a_Nav_back(BrowserWindow *bw); +void a_Nav_forw(BrowserWindow *bw); +void a_Nav_home(BrowserWindow *bw); +void a_Nav_reload(BrowserWindow *bw); +void a_Nav_jump(BrowserWindow *bw, int offset, int new_bw); +void a_Nav_free(BrowserWindow *bw); +void a_Nav_cancel_expect (BrowserWindow *bw); +void a_Nav_expect_done(BrowserWindow *bw); +int a_Nav_stack_ptr(BrowserWindow *bw); +int a_Nav_stack_size(BrowserWindow *bw); + +void a_Nav_save_url(BrowserWindow *bw, + const DilloUrl *url, const char *filename); +int a_Nav_get_buf(const DilloUrl *Url, char **PBuf, int *BufSize); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __NAV_H__ */ + + diff --git a/src/pixmaps.h b/src/pixmaps.h new file mode 100644 index 00000000..57b05f97 --- /dev/null +++ b/src/pixmaps.h @@ -0,0 +1,1652 @@ +/* + * File: pixmaps.h + * + * Copyright (C) 2000, 2001 Jorge Arellano Cid <jcid@dillo.org> + * Design by John Grantham, Dipl.-Designer; http://www.grantham.de/ + * + * 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. + */ + +#ifndef __PIXMAPS_H__ +#define __PIXMAPS_H__ + +/* XPM +static char * history_xpm[] = { +"11 20 6 1", +" c None", +". c #000000000000", +"X c #9658A289BEFB", +"O c #FFFF9A690000", +"+ c #FFFFFFFF0000", +"@ c #FFFFFFFFFFFF", +" ", +" . ", +" .X. ", +" .XXX. ", +" .XXXXX. ", +" .XXXXXXX. ", +" ...XXX... ", +" .XXX. ", +" .XXX. ", +" .XXX. ", +" OOOOO ", +" .XXX. ", +" .XXX. ", +" ...XXX... ", +" .XXXXXXX. ", +" .XXXXX. ", +" .XXX. ", +" .X. ", +" . ", +" "}; +*/ +/* XPM */ +static char * left_xpm[] = { +"22 22 46 1", +" c None", +". c #000000", +"+ c #FF7856", +"@ c #FF8361", +"# c #FF5325", +"$ c #FF8C68", +"% c #FF6636", +"& c #FF6333", +"* c #FF936B", +"= c #FF6E39", +"- c #FF5111", +"; c #FF8C61", +"> c #FFA685", +", c #FF8659", +"' c #FF996E", +") c #FF773D", +"! c #FF5C16", +"~ c #FF570F", +"{ c #EC510E", +"] c #FF894C", +"^ c #FF7E3B", +"/ c #FF681A", +"( c #FF6414", +"_ c #EC5D13", +": c #C56932", +"< c #CB5A15", +"[ c #F86E19", +"} c #FF711A", +"| c #EC6918", +"1 c #9F5F31", +"2 c #C56118", +"3 c #F87A1E", +"4 c #FF7D1F", +"5 c #EC741D", +"6 c #9F6532", +"7 c #C56A1C", +"8 c #F88523", +"9 c #8C4B14", +"0 c #582F0C", +"a c #894A13", +"b c #9F6B35", +"c c #C57320", +"d c #B96C1E", +"e c #A47339", +"f c #C77C23", +"g c #93601C", +" ", +" ", +" ", +" ", +" .. ", +" .+. ", +" .@#. ", +" .$%&...... .... ", +" .*=-=;>>>,. .,,. ", +" .')!~~~~~~{. .{{. ", +" .]^/(((((((_. .__. ", +" .:<[}}}}}}}|. .||. ", +" .1234444445. .55. ", +" .67879000a. .aa. ", +" .bcd...... .... ", +" .ef. ", +" .g. ", +" .. ", +" .....", +" .>. ", +" . ", +" "}; +/* XPM */ +static char * right_xpm[] = { +"22 22 46 1", +" c None", +". c #000000", +"+ c #569A59", +"@ c #228126", +"# c #60A463", +"$ c #2E9132", +"% c #319335", +"& c #65AE68", +"* c #52AC55", +"= c #80C282", +"- c #5BB15E", +"; c #319C35", +"> c #07880C", +", c #65B568", +"' c #008905", +") c #009405", +"! c #07970C", +"~ c #31A935", +"{ c #65BF68", +"] c #009705", +"^ c #00A305", +"/ c #07A60C", +"( c #2AB22E", +"_ c #3CB83F", +": c #00A605", +"< c #00B305", +"[ c #00AE05", +"} c #008E04", +"| c #219424", +"1 c #00B405", +"2 c #00C205", +"3 c #00BD05", +"4 c #009604", +"5 c #218124", +"6 c #007003", +"7 c #004802", +"8 c #007303", +"9 c #00A104", +"0 c #00CB05", +"a c #218924", +"b c #00A304", +"c c #00AD04", +"d c #219024", +"e c #00B804", +"f c #219B24", +"g c #008E03", +" ", +" ", +" ", +" ", +" .. ", +" .+. ", +" .@#. ", +" .... ......$%&. ", +" .**. .*===-;>;,. ", +" .''. .'))))))!~{. ", +" .]]. .]^^^^^^^/(_. ", +" .::. .:<<<<<<<[}|. ", +" .11. .1222222345. ", +" .66. .67778909a. ", +" .... ......bcd. ", +" .ef. ", +" .g. ", +" .. ", +" .....", +" .=. ", +" . ", +" "}; +/* XPM */ +static char * reload_xpm[] = { +"22 22 147 2", +" c None", +". c #000000", +"+ c #BABABA", +"@ c #CCCCCC", +"# c #CBCBCB", +"$ c #5D5D5D", +"% c #909090", +"& c #9C9C9C", +"* c #A0A0A0", +"= c #EBEBEB", +"- c #4C4C4C", +"; c #949494", +"> c #8D8D8D", +", c #FFFFFF", +"' c #E4E4E4", +") c #424242", +"! c #989898", +"~ c #A4A4A4", +"{ c #878787", +"] c #FBFBFB", +"^ c #E1E1E1", +"/ c #393939", +"( c #9B9B9B", +"_ c #A8A8A8", +": c #898989", +"< c #ECECEC", +"[ c #DEDEDE", +"} c #313131", +"| c #0B0B0B", +"1 c #0C0C0C", +"2 c #2B2B2B", +"3 c #575757", +"4 c #8B8B8B", +"5 c #AEAEAE", +"6 c #8E8E8E", +"7 c #DDDDDD", +"8 c #2C2C2C", +"9 c #282828", +"0 c #666666", +"a c #A3A3A3", +"b c #C0C0C0", +"c c #7D7D7D", +"d c #3F3F3F", +"e c #1D1D1D", +"f c #6B6B6B", +"g c #929292", +"h c #585858", +"i c #BEBEBE", +"j c #4F4F4F", +"k c #A5A5A5", +"l c #BDBDBD", +"m c #B1B1B1", +"n c #A9A9A9", +"o c #AAAAAA", +"p c #696969", +"q c #0A0A0A", +"r c #5F5F5F", +"s c #515151", +"t c #545454", +"u c #686868", +"v c #868686", +"w c #B5B5B5", +"x c #B4B4B4", +"y c #7F907F", +"z c #436B43", +"A c #144F14", +"B c #004800", +"C c #1E1E1E", +"D c #838383", +"E c #9F9F9F", +"F c #262626", +"G c #9D9D9D", +"H c #B8B8B8", +"I c #839583", +"J c #577A57", +"K c #7E917E", +"L c #A7AAA7", +"M c #426D42", +"N c #004600", +"O c #A1A1A1", +"P c #404040", +"Q c #636363", +"R c #C6C6C6", +"S c #B7B7B7", +"T c #728C72", +"U c #B0B2B0", +"V c #777777", +"W c #323232", +"X c #C2C2C2", +"Y c #BCBCBC", +"Z c #8EA18E", +"` c #004B00", +" . c #979797", +".. c #0F0F0F", +"+. c #D2D2D2", +"@. c #AFAFAF", +"#. c #C4C4C4", +"$. c #B0B0B0", +"%. c #D9D9D9", +"&. c #C9C9C9", +"*. c #7A5200", +"=. c #B8AE9A", +"-. c #101010", +";. c #DFDFDF", +">. c #CECECE", +",. c #BFBFBF", +"'. c #D4D4D4", +"). c #7F5500", +"!. c #D2D1CE", +"~. c #B4A585", +"{. c #E5E5E5", +"]. c #845800", +"^. c #A58A53", +"/. c #C3B79F", +"(. c #D9D7D3", +"_. c #B09A6E", +":. c #C6BBA5", +"<. c #747474", +"[. c #3A3A3A", +"}. c #EAEAEA", +"|. c #222222", +"1. c #865A00", +"2. c #CCC1AA", +"3. c #AC915B", +"4. c #936C1C", +"5. c #E0E0E0", +"6. c #414141", +"7. c #787878", +"8. c #F0F0F0", +"9. c #8C8C8C", +"0. c #C3C3C3", +"a. c #E2E2E2", +"b. c #F4F4F4", +"c. c #646464", +"d. c #9E9E9E", +"e. c #DCDCDC", +"f. c #6A6A6A", +"g. c #555555", +"h. c #0E0E0E", +"i. c #F8F8F8", +"j. c #E6E6E6", +"k. c #191919", +"l. c #2F2F2F", +"m. c #464646", +"n. c #444444", +"o. c #242424", +"p. c #343434", +" . . . . . . . . . . . ", +" . + @ @ @ @ @ @ @ # $ . ", +" . % & & & & & & & * = - . ", +" . ; * * * * * * * > , ' ) . ", +" . ! ~ ~ ~ ~ ~ ~ ~ { ] , ^ / . ", +" . ( _ _ _ _ _ _ _ : < , , [ } . ", +" . | 1 1 1 2 3 4 5 6 < , , , 7 8 . ", +" . 9 0 a b b a c d e f g : h h f * i . ", +" . j k l m n n m l o p q r f s s t u v . ", +" . - w x y z A A z y B w u C D & & & E * . ", +" F G H I J K L K M N N H O P Q R R R R S . ", +". $ i S T U w w w B B B S i V W @ @ @ @ l . ", +". 6 X Y Z Y Y Y ` ` ` ` Y X ...+.+.+.+.X . ", +". @.R #.#.#.#.#.#.#.#.#.#.R $...%.%.%.%.&.. ", +". n #.@ *.*.*.*.@ @ @ =.@ #.o -.;.;.;.;.>.. ", +". v ,.'.).).).'.'.'.!.~.'.,.% -.{.{.{.{.'.. ", +". 3 w '.].].^./.(./._.:.'.w <.[.}.}.}.}.%.. ", +" |.{ X 1.2.3.4.4.3.2.5.X > 6.7.8.8.8.8.[ . ", +" . [.9.0.a.}.}.}.}.a.0.> $ 9 X b.b.b.b.a.. ", +" . W c.d.R e.e.R d.f.g.h.; i.i.i.i.i.j.. ", +" . k.l.m.j j n.[.o.h.p.3 3 3 3 3 3 { . ", +" . . . . . . . . . . . . . . . . . "}; +/* XPM */ +static char * home_xpm[] = { +"22 22 106 2", +" c None", +". c #190E0B", +"+ c #180D09", +"@ c #D8947D", +"# c #000000", +"$ c #190D09", +"% c #CC6746", +"& c #A7401F", +"* c #C65834", +"= c #1A0D09", +"- c #D16746", +"; c #A63F1F", +"> c #160D09", +", c #C13206", +"' c #D86746", +") c #AD3F1F", +"! c #1C0F0A", +"~ c #9B9376", +"{ c #D2502A", +"] c #1B0D09", +"^ c #DF6746", +"/ c #B43F1F", +"( c #999174", +"_ c #C1B588", +": c #D34721", +"< c #D8461E", +"[ c #1C0D09", +"} c #E76746", +"| c #BB3F1F", +"1 c #1D0F0A", +"2 c #C5B98B", +"3 c #C4B889", +"4 c #93371F", +"5 c #E14821", +"6 c #1B0803", +"7 c #1D0D09", +"8 c #EE6746", +"9 c #C33F1F", +"0 c #1E0F0A", +"a c #9E9678", +"b c #CABD8F", +"c c #CCBF8E", +"d c #CFC290", +"e c #1E0D09", +"f c #F56746", +"g c #CA3F1F", +"h c #21110A", +"i c #A09879", +"j c #CDC091", +"k c #D2C593", +"l c #D8CA97", +"m c #DCCE9A", +"n c #1D0500", +"o c #C63F21", +"p c #9E361F", +"q c #130300", +"r c #988E6A", +"s c #D1C492", +"t c #D5C895", +"u c #DDCF9A", +"v c #E4D69F", +"w c #E9DAA3", +"x c #C9BC8C", +"y c #DACC98", +"z c #E0D29C", +"A c #E8D9A2", +"B c #EFE0A7", +"C c #F2E3A9", +"D c #5E5842", +"E c #676047", +"F c #F9E9AE", +"G c #655F46", +"H c #DBCD99", +"I c #C16C4E", +"J c #D3927C", +"K c #C86F51", +"L c #FCECB0", +"M c #D6D6D6", +"N c #D2D2D2", +"O c #E3D49F", +"P c #A82900", +"Q c #B82C00", +"R c #AB2A00", +"S c #FEEEB1", +"T c #6F6D6D", +"U c #6D6B6B", +"V c #E7D9A2", +"W c #B52A00", +"X c #C42D00", +"Y c #B62A00", +"Z c #FFEFB2", +"` c #696249", +" . c #676048", +".. c #EADBA4", +"+. c #C12A00", +"@. c #D22D00", +"#. c #C22A00", +"$. c #ECDDA5", +"%. c #CE2A00", +"&. c #DF2D00", +"*. c #DA2A00", +"=. c #EC2D00", +"-. c #898060", +";. c #851800", +">. c #551000", +",. c #58523D", +" ", +" . . ", +" + @ @ + # # # ", +" $ % & & % $ # * # ", +" = - ; > > ; - = # , # ", +" = ' ) ! ~ ~ ! ) ' = { # ", +" ] ^ / ! ( _ _ ( ! / : < # ", +" [ } | 1 ~ 2 3 3 2 ~ 1 4 5 6 ", +" 7 8 9 0 a b c d d c b a 0 9 8 7 ", +" e f g h i j k l m m l k j i h g f e ", +" n o p q r s t u v w w v u t s r q p o n ", +" # # # # x y z A B C C B A z y x # # # # ", +" # s D # # # E F G # # D s # ", +" # H # I J K # L # M N # H # ", +" # O # P Q R # S # T U # O # ", +" # V # W X Y # Z ` # # .V # ", +" # ..# +.@.#.# Z Z Z S S ..# ", +" # $.# %.&.%.# Z Z Z Z Z $.# ", +" # $.# *.=.*.# Z Z Z Z Z $.# ", +" # -.# ;.>.;.# ,.,.,.,.,.-.# ", +" # # # # # # # # # # # # # # ", +" "}; +/* XPM */ +static char * save_xpm[] = { +"22 22 59 1", +" c None", +". c #000000", +"+ c #638163", +"@ c #4D5A4D", +"# c #A4BDA4", +"$ c #184318", +"% c #0E280E", +"& c #4B7E4B", +"* c #194419", +"= c #0F290F", +"- c #508350", +"; c #1B481B", +"> c #102B10", +", c #568956", +"' c #1D4C1D", +") c #112D11", +"! c #5D905D", +"~ c #205020", +"{ c #133013", +"] c #639663", +"^ c #235223", +"/ c #153115", +"( c #6B9E6B", +"_ c #275627", +": c #173317", +"< c #73A673", +"[ c #2B5A2B", +"} c #2E612E", +"| c #193619", +"1 c #2E5D2E", +"2 c #152A15", +"3 c #326132", +"4 c #838383", +"5 c #9D9D9D", +"6 c #B3B3B3", +"7 c #979797", +"8 c #366536", +"9 c #585858", +"0 c #565656", +"a c #727272", +"b c #6A6A6A", +"c c #386838", +"d c #848484", +"e c #7A7A7A", +"f c #3C6B3C", +"g c #999999", +"h c #8E8E8E", +"i c #366036", +"j c #3F6E3F", +"k c #BDBDBD", +"l c #BABABA", +"m c #ADADAD", +"n c #A0A0A0", +"o c #162616", +"p c #264126", +"q c #949494", +"r c #696969", +"s c #424242", +"t c #676767", +" ", +" ", +" ................ ", +" .+@############@+. ", +" .$%&&&&&&&&&&&&%$. ", +" .*=------------=*. ", +" .;>,,,,,,,,,,,,>;. ", +" .')!!!!!!!!!!!!)'. ", +" .~{]]]]]]]]]]]]{~. ", +" .^/((((((((((((/^. ", +" ._:<<<<<<<<<<<<:_. ", +" .[}||||||||||||}[. ", +" .12............21. ", +" .3.444566666667.3. ", +" .8.9..0aaaaaaab.8. ", +" .c.d..eddddddde.c. ", +" .f.g..hgggggggh.i. ", +" .j.k..lmmmmmmmn.o. ", +" .p.qqqrssssssst.. ", +" ............... ", +" ", +" "}; + +/* XPM */ +static char * stop_xpm[] = { +"22 22 77 1", +" c None", +". c #000000", +"+ c #703434", +"@ c #E57F7F", +"# c #E68080", +"$ c #6F2929", +"% c #DB4343", +"& c #D31A1A", +"* c #CE0000", +"= c #DC4343", +"- c #D51A1A", +"; c #D00000", +"> c #702929", +", c #DF4343", +"' c #D71A1A", +") c #D30000", +"! c #6F1B1B", +"~ c #E14343", +"{ c #DA1A1A", +"] c #D60000", +"^ c #A66666", +"/ c #841717", +"( c #DA0404", +"_ c #DD1A1A", +": c #D90000", +"< c #D56B6B", +"[ c #FFFFFF", +"} c #E7D5D5", +"| c #861717", +"1 c #CC0000", +"2 c #DC0000", +"3 c #E64C4C", +"4 c #FADEDE", +"5 c #EAD5D5", +"6 c #722C2C", +"7 c #CF0000", +"8 c #E00000", +"9 c #EB5C5C", +"0 c #FBDFDF", +"a c #FDF8F8", +"b c #E40000", +"c c #DE3535", +"d c #FEF8F8", +"e c #E70000", +"f c #971C1C", +"g c #EED5D5", +"h c #FEFAFA", +"i c #EB0000", +"j c #AA1C1C", +"k c #EFD5D5", +"l c #FDE3E3", +"m c #F69595", +"n c #C50000", +"o c #D50000", +"p c #EF0000", +"q c #F77777", +"r c #FDE4E4", +"s c #F56565", +"t c #4D0000", +"u c #9E0000", +"v c #D70000", +"w c #F20000", +"x c #FAA7A7", +"y c #F76969", +"z c #420000", +"A c #A00000", +"B c #DA0000", +"C c #F50000", +"D c #A20000", +"E c #DD0000", +"F c #F80000", +"G c #430000", +"H c #A40000", +"I c #DF0000", +"J c #FB0000", +"K c #380000", +"L c #570000", +" ", +" ", +" ......... ", +" .+@#####@+. ", +" .$%&*****&%$. ", +" .$=-;;;;;;;-=$. ", +" .>,')))))))))',>. ", +" .!~{]]^/]]]/^]]{~!. ", +" .(_::<[}|:|}[<::_(. ", +" .122234[565[432221. ", +" .7888890[a[0988887. ", +" .)bbbbbcd[dcbbbbb). ", +" .]eeeefg[h[gfeeee]. ", +" .:iiijk[lml[kjiii:. ", +" .noppq[rspsr[qppon. ", +" .tuvwwxywwwyxwwvut. ", +" .zABCCCCCCCCCBAz. ", +" .zDEFFFFFFFEDz. ", +" .GHIJJJJJIHG. ", +" .KLLLLLLLK. ", +" ......... ", +" "}; +/* XPM */ +static char * bm_xpm[] = { +"22 22 86 1", +" c None", +". c #000000", +"+ c #150B0A", +"@ c #A46C6A", +"# c #B15652", +"$ c #C68380", +"% c #840600", +"& c #8F0600", +"* c #979797", +"= c #B3B3B3", +"- c #880600", +"; c #930600", +"> c #616161", +", c #696969", +"' c #595959", +") c #8D0600", +"! c #980600", +"~ c #666666", +"{ c #6E6E6E", +"] c #5E5E5E", +"^ c #920600", +"/ c #9E0700", +"( c #6B6B6B", +"_ c #747474", +": c #636363", +"< c #970600", +"[ c #A30700", +"} c #727272", +"| c #7B7B7B", +"1 c #9D0600", +"2 c #AA0700", +"3 c #777777", +"4 c #818181", +"5 c #A40600", +"6 c #B10700", +"7 c #7F7F7F", +"8 c #898989", +"9 c #757575", +"0 c #A90700", +"a c #B70800", +"b c #868686", +"c c #919191", +"d c #7C7C7C", +"e c #BF0800", +"f c #8E8E8E", +"g c #999999", +"h c #828282", +"i c #C60900", +"j c #959595", +"k c #A1A1A1", +"l c #BD0800", +"m c #CC0900", +"n c #9C9C9C", +"o c #A9A9A9", +"p c #909090", +"q c #C40800", +"r c #D40900", +"s c #A3A3A3", +"t c #B0B0B0", +"u c #969696", +"v c #CA0900", +"w c #DA0A00", +"x c #B7B7B7", +"y c #D00900", +"z c #E10A00", +"A c #AFAFAF", +"B c #BDBDBD", +"C c #D70900", +"D c #E80A00", +"E c #B4B4B4", +"F c #C3C3C3", +"G c #A6A6A6", +"H c #DB0900", +"I c #E10900", +"J c #454545", +"K c #CD0900", +"L c #A00700", +"M c #7B1510", +"N c #A40700", +"O c #A20700", +"P c #3E0300", +"Q c #4A0300", +"R c #A90800", +"S c #540400", +"T c #010000", +"U c #660400", +" ...... ", +" +@#$$$#. ", +" .......%&&&%.... ", +" .*====.-;;;-.=*. ", +" .>,',,.)!!!).,>. ", +" .~{]{{.^///^.{~. ", +" .(_:__.<[[[<._(. ", +" .}|,||.12221.|}. ", +" .34{44.56665.43. ", +" .78988.0aaa0.87. ", +" .bcdcc.6eee6.cb. ", +" .fghgg.aiiia.gf. ", +" .jk8kk.lmmml.kj. ", +" .nopoo.qrrrq.on. ", +" .stutt.vwwwv.ts. ", +" .oxnxx.yzzzy.xo. ", +" .ABkBB.CDDDC.BA. ", +" .EFGFF.HIIIH.FE. ", +" .(JJJJ.KLMNy.J(. ", +" .......OP.QR.... ", +" .S. TU. ", +" . . "}; + +/* Small icons here */ + +/* XPM */ +static char * left_s_xpm[] = { +"16 16 33 1", +" c None", +". c #000000", +"+ c #1F120A", +"@ c #FF9A59", +"# c #DE7E42", +"$ c #FF7A26", +"% c #DE8247", +"& c #FF8335", +"* c #FF8232", +"= c #FF6A0C", +"- c #FF9D5E", +"; c #FFB382", +"> c #FF9755", +", c #FF6605", +"' c #EC5E05", +") c #1F0F05", +"! c #DE691E", +"~ c #FF7F2E", +"{ c #180A00", +"] c #A44103", +"^ c #CB5104", +"/ c #F86305", +"( c #120700", +"_ c #7E3203", +": c #C54F04", +"< c #8C3803", +"[ c #582302", +"} c #893703", +"| c #B94A04", +"1 c #833403", +"2 c #C75004", +"3 c #110700", +"4 c #933B03", +" ", +" .. ", +" +@. ", +" +#$. ", +" +%&*.... ....", +" +%&=&-;>. .>>.", +" +%&=,,,,'. .''.", +")!~=,,,,,'. .''.", +"{]^/,,,,,'. .''.", +" (_:/,,,,'. .''.", +" (_:/:<[}. .}}.", +" (_:|.... ....", +" (12. ", +" 34. .....", +" .. .;. ", +" . "}; +/* XPM */ +static char * right_s_xpm[] = { +"16 16 56 1", +" c None", +". c #000000", +"+ c #569A59", +"@ c #0A120A", +"# c #228126", +"$ c #3F8342", +"% c #09130A", +"& c #2E9132", +"* c #319335", +"= c #448D47", +"- c #09140A", +"; c #52AC55", +"> c #80C282", +", c #5BB15E", +"' c #319C35", +") c #07880C", +"! c #449447", +"~ c #09150A", +"{ c #008905", +"] c #009405", +"^ c #07970C", +"/ c #31A935", +"( c #449E47", +"_ c #09160A", +": c #009705", +"< c #00A305", +"[ c #07A60C", +"} c #2AB22E", +"| c #1B971E", +"1 c #041505", +"2 c #00A605", +"3 c #00B305", +"4 c #00AE05", +"5 c #008E04", +"6 c #007303", +"7 c #001100", +"8 c #00B405", +"9 c #00C205", +"0 c #00BD05", +"a c #009604", +"b c #006003", +"c c #000D00", +"d c #007003", +"e c #004802", +"f c #00A104", +"g c #00CB05", +"h c #006803", +"i c #000E00", +"j c #00A304", +"k c #00AD04", +"l c #006F03", +"m c #000F00", +"n c #00B804", +"o c #007A03", +"p c #001000", +"q c #008E03", +" ", +" .. ", +" .+@ ", +" .#$% ", +".... ....&*=- ", +".;;. .;>,')'!~ ", +".{{. .{]]]]^/(_ ", +".::. .:<<<<<[}|1", +".22. .2333334567", +".88. .899990abc ", +".dd. .de6fgfhi ", +".... ....jklm ", +" .nop ", +" .qpb....", +" .. .>. ", +" . "}; +/* XPM */ +static char * home_s_xpm[] = { +"16 16 54 1", +" c None", +". c #170E0B", +"+ c #000000", +"@ c #170C09", +"# c #B0705B", +"$ c #B35636", +"% c #A13F20", +"& c #661B03", +"* c #AA3009", +"= c #190C09", +"- c #AC3F20", +"; c #721C03", +"> c #8E8875", +", c #C24F2D", +"' c #1A0C09", +") c #B93F20", +"! c #7C1C03", +"~ c #8F876F", +"{ c #A9A081", +"] c #A72403", +"^ c #CE4522", +"/ c #1B0C09", +"( c #C63F20", +"_ c #861C03", +": c #A9A080", +"< c #FDEDB3", +"[ c #631503", +"} c #CC2903", +"| c #1B0703", +"1 c #1D0C08", +"2 c #D03E20", +"3 c #871902", +"4 c #847C67", +"5 c #FFEFB2", +"6 c #847C68", +"7 c #851902", +"8 c #CF3A1B", +"9 c #A81E02", +"0 c #721402", +"a c #9A9070", +"b c #8B8163", +"c c #9C926D", +"d c #FDEDB1", +"e c #EADBA4", +"f c #696249", +"g c #ECDDA5", +"h c #C25834", +"i c #FFFFFF", +"j c #BE2D00", +"k c #BF0000", +"l c #898060", +"m c #58523D", +"n c #D90000", +"o c #BB0000", +" ", +" .. +++ ", +" @##@ +$+ ", +" @%&&%@+*+ ", +" =-;>>;-=,+ ", +" ')!~{{~!]^+ ", +" /(_~:<<:~[}| ", +" 1234:<55<:6781 ", +"+90a{<5555<:b09+", +"+++cd555555dc+++", +" +e5f+f5f+fe+ ", +" +g5+h+5+i+g+ ", +" +g5+j+5f+fg+ ", +" +g5+k+5555g+ ", +" +lm+n+mmmml+ ", +" ++++o+++++++ "}; +/* XPM */ +static char * reload_s_xpm[] = { +"16 16 74 1", +" c None", +". c #000000", +"+ c #282828", +"@ c #666666", +"# c #9B9B9B", +"$ c #BFBFBF", +"% c #4F4F4F", +"& c #A5A5A5", +"* c #BDBDBD", +"= c #B1B1B1", +"- c #A9A9A9", +"; c #4C4C4C", +"> c #B5B5B5", +", c #B4B4B4", +"' c #7F907F", +") c #436B43", +"! c #144F14", +"~ c #004800", +"{ c #262626", +"] c #9D9D9D", +"^ c #B8B8B8", +"/ c #839583", +"( c #577A57", +"_ c #7E917E", +": c #A7AAA7", +"< c #426D42", +"[ c #004600", +"} c #5D5D5D", +"| c #BEBEBE", +"1 c #B7B7B7", +"2 c #728C72", +"3 c #B0B2B0", +"4 c #8E8E8E", +"5 c #C2C2C2", +"6 c #BCBCBC", +"7 c #8EA18E", +"8 c #004B00", +"9 c #AFAFAF", +"0 c #C6C6C6", +"a c #C4C4C4", +"b c #CCCCCC", +"c c #7A5200", +"d c #B8AE9A", +"e c #868686", +"f c #D4D4D4", +"g c #7F5500", +"h c #D2D1CE", +"i c #B4A585", +"j c #575757", +"k c #845800", +"l c #A58A53", +"m c #C3B79F", +"n c #D9D7D3", +"o c #B09A6E", +"p c #C6BBA5", +"q c #222222", +"r c #878787", +"s c #865A00", +"t c #CCC1AA", +"u c #AC915B", +"v c #936C1C", +"w c #E0E0E0", +"x c #3A3A3A", +"y c #8C8C8C", +"z c #C3C3C3", +"A c #E2E2E2", +"B c #EAEAEA", +"C c #323232", +"D c #646464", +"E c #9E9E9E", +"F c #DCDCDC", +"G c #191919", +"H c #2F2F2F", +"I c #404040", +" ...... ", +" .+@#$$#@+. ", +" .%&*=--=*&%. ", +" .;>,')!!)'~>;. ", +" {]^/(_:_<[[^]{ ", +".}|123>>>~~~1|}.", +".45676668888654.", +".90aaaaaaaaaa09.", +".-abccccbbbdba-.", +".e$fgggfffhif$e.", +".j>fkklmnmopf>j.", +" qr5stuvvutw5rq ", +" .xyzABBBBAzyx. ", +" .CDE0FF0EDC. ", +" .GHI%%IHG. ", +" ...... "}; +/* XPM */ +static char * save_s_xpm[] = { +"16 16 51 1", +" c None", +". c #000000", +"+ c #638163", +"@ c #8CA28C", +"# c #A4BDA4", +"$ c #184318", +"% c #1A481A", +"& c #4C7F4C", +"* c #1A461A", +"= c #1C4C1C", +"- c #528552", +"; c #1B4A1B", +"> c #1D501D", +", c #598C59", +"' c #1F4E1F", +") c #215421", +"! c #619461", +"~ c #225122", +"{ c #255825", +"] c #699C69", +"^ c #275627", +"/ c #2A5D2A", +"( c #72A572", +"_ c #2B5A2B", +": c #2E612E", +"< c #2F5E2F", +"[ c #152A15", +"} c #336233", +"| c #838383", +"1 c #9D9D9D", +"2 c #B3B3B3", +"3 c #979797", +"4 c #376637", +"5 c #5A5A5A", +"6 c #585858", +"7 c #757575", +"8 c #6C6C6C", +"9 c #3A6A3A", +"0 c #8C8C8C", +"a c #828282", +"b c #345E34", +"c c #3E6D3E", +"d c #B6B6B6", +"e c #A5A5A5", +"f c #999999", +"g c #162616", +"h c #264126", +"i c #919191", +"j c #676767", +"k c #414141", +"l c #656565", +"................", +".+@##########@+.", +".$%&&&&&&&&&&%$.", +".*=----------=*.", +".;>,,,,,,,,,,>;.", +".')!!!!!!!!!!)'.", +".~{]]]]]]]]]]{~.", +".^/((((((((((/^.", +"._::::::::::::_.", +".<[..........[<.", +".}.|||1222223.}.", +".4.5..6777778.4.", +".9.0..a00000a.b.", +".c.d..2eeeeef.g.", +".h.iiijkkkkkl.. ", +" ............. "}; +/* XPM */ +static char * stop_s_xpm[] = { +"16 16 65 1", +" c None", +". c #000000", +"+ c #5A3435", +"@ c #BF7F80", +"# c #BF8081", +"$ c #562929", +"% c #A54344", +"& c #911A1C", +"* c #850002", +"= c #582929", +"- c #AA4344", +"; c #981A1C", +"> c #8C0002", +", c #551B1C", +"' c #B04345", +") c #9F1A1D", +"! c #940003", +"~ c #926667", +"{ c #661C1E", +"] c #A00407", +"^ c #A81A1D", +"/ c #9E0003", +"( c #B86B6D", +"_ c #FFFFFF", +": c #E2D5D5", +"< c #581C1D", +"[ c #9C0003", +"} c #A90003", +"| c #C24C4E", +"1 c #F4DDDE", +"2 c #E9DADA", +"3 c #A70003", +"4 c #B40003", +"5 c #C4383A", +"6 c #F5DEDE", +"7 c #B10004", +"8 c #BF0004", +"9 c #9A1C1F", +"0 c #F0D9D9", +"a c #BB0004", +"b c #CA0004", +"c c #971C1F", +"d c #ECD5D5", +"e c #FAE6E6", +"f c #AF0003", +"g c #BE0004", +"h c #D50004", +"i c #E87779", +"j c #FBE5E5", +"k c #EA7E80", +"l c #470001", +"m c #930003", +"n c #C70004", +"o c #E00004", +"p c #F5A7A9", +"q c #EC6568", +"r c #3F0001", +"s c #990003", +"t c #D00004", +"u c #EA0004", +"v c #410001", +"w c #D70004", +"x c #F20005", +"y c #370001", +"z c #560002", +" ........ ", +" .+@####@+. ", +" .$%&****&%$. ", +" .=-;>>>>>>;-=. ", +".,')!~{!!{~!)',.", +".]^/(_:<<:_(/^].", +".[}}|1_22_1|}}[.", +".344456__654443.", +".788890__098887.", +".abbcd_ee_dcbba.", +".fghi_jkkj_ihgf.", +".lmnopqooqponml.", +" .rstuuuuuutsr. ", +" .v/wxxxxw/v. ", +" .yzzzzzzy. ", +" ........ "}; + +/* XPM */ +static char * bm_s_xpm[] = { +"16 16 63 1", +" c None", +". c #000000", +"+ c #B15652", +"@ c #C68380", +"# c #979797", +"$ c #B3B3B3", +"% c #999999", +"& c #860600", +"* c #910600", +"= c #646464", +"- c #6C6C6C", +"; c #5C5C5C", +"> c #8B0600", +", c #960600", +"' c #6A6A6A", +") c #737373", +"! c #626262", +"~ c #920600", +"{ c #9E0700", +"] c #747474", +"^ c #7D7D7D", +"/ c #990600", +"( c #A50700", +"_ c #7E7E7E", +": c #888888", +"< c #A10600", +"[ c #AE0700", +"} c #939393", +"| c #A90700", +"1 c #B70800", +"2 c #9F9F9F", +"3 c #B30700", +"4 c #C10800", +"5 c #9D9D9D", +"6 c #AAAAAA", +"7 c #919191", +"8 c #BB0800", +"9 c #CA0900", +"0 c #A8A8A8", +"a c #B5B5B5", +"b c #9A9A9A", +"c c #C40800", +"d c #D40900", +"e c #B0B0B0", +"f c #BEBEBE", +"g c #A2A2A2", +"h c #CD0900", +"i c #DD0A00", +"j c #444444", +"k c #3A3A3A", +"l c #D50900", +"m c #DA0900", +"n c #C80800", +"o c #9C0700", +"p c #680500", +"q c #A00700", +"r c #CB0900", +"s c #3D0300", +"t c #490300", +"u c #A70800", +"v c #540400", +"w c #010000", +"x c #660400", +" ....... ", +" ......+@@@+... ", +" .#$%$.&***&.#. ", +" .=-;-.>,,,>.=. ", +" .')!).~{{{~.'. ", +" .]^'^./(((/.]. ", +" ._:]:.<[[[<._. ", +" .:}^}.|111|.:. ", +" .}2:2.34443.}. ", +" .5676.89998.5. ", +" .0aba.cdddc.0. ", +" .efgf.hiiih.e. ", +" .'jkj.lmmml.'. ", +" ......nopqr... ", +" .qs.tu. ", +" .v. wx. "}; + +/* XPM */ +static char * new_s_xpm[] = { +"11 11 35 1", +" c None", +". c #000000", +"+ c #482929", +"@ c #0F0808", +"# c #390606", +"$ c #A04242", +"% c #925050", +"& c #0F0707", +"* c #0B0000", +"= c #500000", +"- c #8C0101", +"; c #944444", +"> c #221515", +", c #0A0000", +"' c #560000", +") c #A10909", +"! c #AE3636", +"~ c #230000", +"{ c #AE0000", +"] c #B40000", +"^ c #180808", +"/ c #B43535", +"( c #C40000", +"_ c #960000", +": c #190606", +"< c #C02B2B", +"[ c #E00000", +"} c #6B0000", +"| c #120000", +"1 c #590000", +"2 c #A30000", +"3 c #680000", +"4 c #0F0000", +"5 c #350000", +"6 c #100000", +" . . ", +" .+@ @+. ", +".#$%& &%$#.", +" *=-;>;-=* ", +" ,')!)', ", +" ~{]{~ ", +" ^/(_(/^ ", +" :<[}|}[<: ", +".1234 4321.", +" .56 65. ", +" . . "}; + +/* XPM */ +static char * search_xpm[] = { +"14 16 11 1", +" c None", +". c #000000", +"+ c #EEEEEE", +"[ c #EE0000", +"} c #CC0000", +"| c #BB0000", +"1 c #AA0000", +"2 c #880000", +"3 c #660000", +"4 c #440000", +"5 c #330000", +" ..... ", +" . . ", +" . + . ", +". +++ . ", +". + . ", +". . ", +". . ", +" . . ", +" . . ", +" ...545 ", +" 424 ", +" 313 ", +" 2|2 ", +" 2}2 ", +" 2}2 ", +" 11 "}; + +/* XPM */ +static char * full_screen_on_xpm[] = { +"13 15 2 1", +" c None", +". c #000000", +" ", +".............", +". . .", +". ... .", +". ..... .", +". ....... .", +". . .", +". . .", +". . .", +". ....... .", +". ..... .", +". ... .", +". . .", +".............", +" "}; + +/* XPM */ +static char * full_screen_off_xpm[] = { +"13 15 2 1", +" c None", +". c #000000", +" ", +".............", +". . . . . . .", +".. . . . . ..", +".............", +". .", +". .", +". .", +". .", +". .", +". .", +".............", +". . . . . . .", +".............", +" "}; + +/* XPM */ +static char * mini_bug_xpm[] = { +"15 16 6 1", +" c None", +". c #000000000000", +"X c #BEFBC30BBEFB", +"o c #861782078617", +"O c #FFFFFFFFFFFF", +"+ c #30C230C230C2", +" ", +" . . ", +" ... ", +" X.....X ", +" o.O...o ", +" o.O...o.o ", +" ..OoXo... ", +" .....X..... ", +" ....X.... ", +" .o...X...o. ", +" ...X... ", +" .X..X..X. ", +" .o. ", +" .+++.", +" .o. ", +" . "}; + +/* XPM */ +static char * mini_ok_xpm[] = { +"15 15 5 1", +"@ c #000000", +"a c #808080", +"b c #303030", +"c c #606060", +" s none m none c none", +" ", +" ", +" @ ", +" @@ ", +" @@@ ", +" @@@ ", +" @@ @@@ ", +" @@@ @@@ ", +" @@@ @@@ ", +" @@@@@@ ", +" @@@@@ ", +" @@@@ ", +" @@ @bbb@", +" @a@ ", +" @ " +}; + +/* XPM */ +static char *left_i_xpm[] = { +"22 22 3 1", +" c None", +". c #000000", +"@ c gray70", +" ", +" ", +" ", +" ", +" @@ ", +" @@@ ", +" @@@@ ", +" @@@@@@@@@@ @@@@ ", +" @@@@@@@@@@@ @@@@ ", +" @@@@@@@@@@@@ @@@@ ", +" @@@@@@@@@@@@@ @@@@ ", +" @@@@@@@@@@@@@ @@@@ ", +" @@@@@@@@@@@@ @@@@ ", +" @@@@@@@@@@@ @@@@ ", +" @@@@@@@@@@ @@@@ ", +" @@@@ ", +" @@@ ", +" @@ ", +" @@@@@", +" @ @ ", +" @ ", +" "}; + +/* XPM */ +static char *right_i_xpm[] = { +"22 22 3 1", +" c None", +". c #000000", +"@ c gray70", +" ", +" ", +" ", +" ", +" @@ ", +" @@@ ", +" @@@@ ", +" @@@@ @@@@@@@@@@ ", +" @@@@ @@@@@@@@@@@ ", +" @@@@ @@@@@@@@@@@@ ", +" @@@@ @@@@@@@@@@@@@ ", +" @@@@ @@@@@@@@@@@@@ ", +" @@@@ @@@@@@@@@@@@ ", +" @@@@ @@@@@@@@@@@ ", +" @@@@ @@@@@@@@@@ ", +" @@@@ ", +" @@@ ", +" @@ ", +" @@@@@", +" @ @ ", +" @ ", +" "}; + +/* XPM */ +static char *stop_i_xpm[] = { +/* columns rows colors chars-per-pixel */ +"22 22 2 1", +" c None", +"@ c gray70", +/* pixels */ +" ", +" ", +" @@@@@@@@@ ", +" @@@@@@@@@@@ ", +" @@@@@@@@@@@@@ ", +" @@@@@@@@@@@@@@@ ", +" @@@@@@@@@@@@@@@@@ ", +" @@@@@@@@@@@@@@@@@@@ ", +" @@@@@@ @@@ @@@@@@ ", +" @@@@@@ @ @@@@@@ ", +" @@@@@@@ @@@@@@@ ", +" @@@@@@@@ @@@@@@@@ ", +" @@@@@@@ @@@@@@@ ", +" @@@@@@ @ @@@@@@ ", +" @@@@@@ @@@ @@@@@@ ", +" @@@@@@@@@@@@@@@@@@@ ", +" @@@@@@@@@@@@@@@@@ ", +" @@@@@@@@@@@@@@@ ", +" @@@@@@@@@@@@@ ", +" @@@@@@@@@@@ ", +" @@@@@@@@@ ", +" " +}; + +/* XPM */ +static char * stop_si_xpm[] = { +"16 16 2 1", +" c None", +"@ c gray70", +" @@@@@@@@ ", +" @@@@@@@@@@ ", +" @@@@@@@@@@@@ ", +" @@@@@@@@@@@@@@ ", +"@@@@@@@@@@@@@@@@", +"@@@@@ @@@@ @@@@@", +"@@@@@@ @@ @@@@@@", +"@@@@@@@ @@@@@@@", +"@@@@@@@ @@@@@@@", +"@@@@@@ @@ @@@@@@", +"@@@@@ @@@@ @@@@@", +"@@@@@@@@@@@@@@@@", +" @@@@@@@@@@@@@@ ", +" @@@@@@@@@@@@ ", +" @@@@@@@@@@ ", +" @@@@@@@@ "}; + +/* XPM */ +static char * left_si_xpm[] = { +"16 16 2 1", +" c None", +"@ c gray70", +" ", +" @@ ", +" @@@ ", +" @@@@ ", +" @@@@@@@@ @@@@", +" @@@@@@@@@ @@@@", +" @@@@@@@@@@ @@@@", +"@@@@@@@@@@@ @@@@", +"@@@@@@@@@@@ @@@@", +" @@@@@@@@@@ @@@@", +" @@@@@@@@@ @@@@", +" @@@@@@@@ @@@@", +" @@@@ ", +" @@@ @@@@@", +" @@ @ @ ", +" @ "}; + +/* XPM */ +static char * right_si_xpm[] = { +"16 16 2 1", +" c None", +"@ c gray70", +" ", +" @@ ", +" @@@ ", +" @@@@ ", +"@@@@ @@@@@@@@ ", +"@@@@ @@@@@@@@@ ", +"@@@@ @@@@@@@@@@ ", +"@@@@ @@@@@@@@@@@", +"@@@@ @@@@@@@@@@@", +"@@@@ @@@@@@@@@@ ", +"@@@@ @@@@@@@@@ ", +"@@@@ @@@@@@@@ ", +" @@@@ ", +" @@@@@@@@", +" @@ @ @ ", +" @ "}; + +#endif /* __PIXMAPS_H__ */ diff --git a/src/plain.cc b/src/plain.cc new file mode 100644 index 00000000..be7b1e4c --- /dev/null +++ b/src/plain.cc @@ -0,0 +1,233 @@ +/* + * File: plain.cc + * + * Copyright (C) 2005 Jorge Arellano Cid <jcid@dillo.org> + * + * 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 3 of the License, or + * (at your option) any later version. + */ + +/* + * Module for decoding a text/plain object into a dw widget. + */ + +#include <string.h> /* for memcpy and memmove */ +#include <math.h> /* for rint() */ + +#include "prefs.h" +#include "cache.h" +#include "bw.h" +#include "web.hh" +#include "misc.h" +#include "history.h" +#include "nav.h" +#include "menu.hh" + +#include "uicmd.hh" + +#include "dw/core.hh" +#include "dw/textblock.hh" + +using namespace dw; +using namespace dw::core; + + +typedef struct _DilloPlain { + //DwWidget *dw; + Widget *dw; + size_t Start_Ofs; /* Offset of where to start reading next */ + //DwStyle *style; + style::Style *widgetStyle; + BrowserWindow *bw; + int state; +} DilloPlain; + +/* FSM states */ +enum { + ST_SeekingEol, + ST_Eol, + ST_Eof +}; + +/* + * Exported function with C linkage. + */ +extern "C" { +void *a_Plain_text(const char *type, void *P, CA_Callback_t *Call,void **Data); +} + +/* + * Forward declarations + */ +static void Plain_write(DilloPlain *plain, void *Buf, uint_t BufSize, int Eof); +static void Plain_callback(int Op, CacheClient_t *Client); + + + +/* + * Popup the page menu ("button_press_event" callback of the viewport) + */ +//static int Plain_page_menu(GtkWidget *viewport, GdkEventButton *event, +// BrowserWindow *bw) +//{ +// if (event->button == 3) { +// a_Menu_popup_set_url(bw, a_History_get_url(NAV_TOP(bw))); +// gtk_menu_popup(GTK_MENU(bw->menu_popup.over_page), NULL, NULL, +// NULL, NULL, event->button, event->time); +// return TRUE; +// } else +// return FALSE; +//} + +/* + * Create and initialize a new DilloPlain structure. + */ +static DilloPlain *Plain_new(BrowserWindow *bw) +{ + DilloPlain *plain; + //DwPage *page; + //DwStyle style_attrs; + //DwStyleFont font_attrs; + Textblock *textblock; + style::StyleAttrs styleAttrs; + style::FontAttrs fontAttrs; + + plain = dNew(DilloPlain, 1); + plain->state = ST_SeekingEol; + plain->Start_Ofs = 0; + plain->bw = bw; + //plain->dw = a_Dw_page_new(); + //page = (DwPage *) plain->dw; + textblock = new Textblock (false); + plain->dw = (Widget*) textblock; + + /* Create the font and attribute for the page. */ + //font_attrs.name = prefs.fw_fontname; + //font_attrs.size = rint(12.0 * prefs.font_factor); + //font_attrs.weight = 400; + //font_attrs.style = DW_STYLE_FONT_STYLE_NORMAL; + fontAttrs.name = "Courier"; + fontAttrs.size = (int) rint(12.0 * prefs.font_factor); + fontAttrs.weight = 400; + fontAttrs.style = style::FONT_STYLE_NORMAL; + + //a_Dw_style_init_values (&style_attrs); + //style_attrs.font = + // a_Dw_style_font_new (plain->bw->render_layout, &font_attrs); + //style_attrs.color = + // a_Dw_style_color_new (plain->bw->render_layout, prefs.text_color); + //plain->style = a_Dw_style_new (plain->bw->render_layout, &style_attrs); + //a_Dw_widget_set_style (plain->dw, plain->style); + Layout *layout = (Layout*)bw->render_layout; + styleAttrs.initValues (); + styleAttrs.margin.setVal (5); + styleAttrs.font = style::Font::create (layout, &fontAttrs); + styleAttrs.color = style::Color::createSimple (layout, 0x0000ff); + styleAttrs.backgroundColor = + style::Color::createSimple (layout, 0xdcd1ba); + plain->widgetStyle = style::Style::create (layout, &styleAttrs); + + /* The context menu */ +// gtk_signal_connect_while_alive +// (GTK_OBJECT(GTK_BIN(plain->bw->render_main_scroll)->child), +// "button_press_event", GTK_SIGNAL_FUNC(Plain_page_menu), +// plain->bw, GTK_OBJECT (page)); + + return plain; +} + +/* + * Set callback function and callback data for "text/" MIME major-type. + */ +void *a_Plain_text(const char *type, void *P, CA_Callback_t *Call, void **Data) +{ + DilloWeb *web = (DilloWeb*)P; + DilloPlain *plain = Plain_new(web->bw); + + *Call = (CA_Callback_t)Plain_callback; + *Data = (void*)plain; + + return (void*)plain->dw; +} + +/* + * This function is a cache client + */ +static void Plain_callback(int Op, CacheClient_t *Client) +{ + DilloPlain *plain = (DilloPlain*)Client->CbData; + Textblock *textblock = (Textblock*)plain->dw; + + if (Op) { + /* Do the last line: */ + if (plain->Start_Ofs < Client->BufSize) + Plain_write(plain, Client->Buf, Client->BufSize, 1); + /* remove this client from our active list */ + a_Bw_close_client(plain->bw, Client->Key); + /* set progress bar insensitive */ + a_UIcmd_set_page_prog(plain->bw, 0, 0); + + //a_Dw_style_unref (plain->bw->render_layout, plain->style); + plain->widgetStyle->unref(); + dFree(plain); + } else { + Plain_write(plain, Client->Buf, Client->BufSize, 0); + } + + textblock->flush(); +} + +/* + * Here we parse plain text and put it into the page structure. + * (This function is called by Plain_callback whenever there's new data) + */ +static void Plain_write(DilloPlain *plain, void *Buf, uint_t BufSize, int Eof) +{ + //DwPage *page = (DwPage *)plain->dw; + Textblock *textblock = (Textblock*)plain->dw; + char *Start; + char *data; + uint_t i, len, MaxBytes; + + Start = (char*)Buf + plain->Start_Ofs; + MaxBytes = BufSize - plain->Start_Ofs; + i = len = 0; + while ( i < MaxBytes ) { + switch ( plain->state ) { + case ST_SeekingEol: + if (Start[i] == '\n' || Start[i] == '\r') + plain->state = ST_Eol; + else { + ++i; ++len; + } + break; + case ST_Eol: + data = dStrndup(Start + i - len, len); + //a_Dw_page_add_text(page, a_Misc_expand_tabs(data), plain->style); + //a_Dw_page_add_parbreak(page, 0, plain->style); + textblock->addText(a_Misc_expand_tabs(data), plain->widgetStyle); + textblock->addParbreak(0, plain->widgetStyle); + dFree(data); + if (Start[i] == '\r' && Start[i + 1] == '\n') ++i; + if (i < MaxBytes) ++i; + plain->state = ST_SeekingEol; + len = 0; + break; + } + } + plain->Start_Ofs += i - len; + if (Eof && len) { + data = dStrndup(Start + i - len, len); + //a_Dw_page_add_text(page, a_Misc_expand_tabs(data), plain->style); + //a_Dw_page_add_parbreak(page, 0, plain->style); + textblock->addText(a_Misc_expand_tabs(data), plain->widgetStyle); + textblock->addParbreak(0, plain->widgetStyle); + dFree(data); + plain->Start_Ofs += len; + } + + if (plain->bw) + a_UIcmd_set_page_prog(plain->bw, plain->Start_Ofs, 1); +} diff --git a/src/png.c b/src/png.c new file mode 100644 index 00000000..43ef5519 --- /dev/null +++ b/src/png.c @@ -0,0 +1,472 @@ +/* + * The png decoder for Dillo. It is responsible for decoding PNG data + * and transferring it to the dicache. + * + * Geoff Lane nov 1999 zzassgl@twirl.mcc.ac.uk + * Luca Rota, Jorge Arellano Cid, Eric Gaudet 2000 + * + * "PNG: The Definitive Guide" by Greg Roelofs, O'Reilly + * ISBN 1-56592-542-4 + */ + +#include <config.h> +#ifdef ENABLE_PNG + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> /* For abort() */ + +#include <zlib.h> + +#ifdef HAVE_LIBPNG_PNG_H +#include <libpng/png.h> +#else +#include <png.h> +#endif + +#include "msg.h" +#include "image.hh" +#include "web.hh" +#include "cache.h" +#include "dicache.h" +#include "prefs.h" + +#define DEBUG_LEVEL 6 +#include "debug.h" + +enum prog_state { + IS_finished, IS_init, IS_nextdata +}; + +static char *prog_state_name[] = +{ + "IS_finished", "IS_init", "IS_nextdata" +}; + +/* + * This holds the data that must be saved between calls to this module. + * Each time it is called it is supplied with a vector of data bytes + * obtained from the web server. The module can process any amount of the + * supplied data. The next time the module is called, the vector may be + * extended with additional data bytes to be processed. The module must + * keep track of the current start and cursor position of the input data + * vector. As complete output rasters are determined they are sent out of + * the module for additional processing. + * + * NOTE: There is no external control of the splitting of the input data + * vector (only this module understands PNG format data.) This means that + * the complete state of a PNG image being processed must be held in the + * structure below so that processing can be suspended or resumed at any + * point within an input image. + * + * In the case of the libpng library, it maintains it's own state in + * png_ptr and into_ptr so the FSM is very simple - much simpler than the + * ones for XBM and PNM are. + */ + +typedef +struct _DilloPng { + DilloImage *Image; /* Image meta data */ + DilloUrl *url; /* Primary Key for the dicache */ + int version; /* Secondary Key for the dicache */ + + double display_exponent; /* gamma correction */ + ulong_t width; /* png image width */ + ulong_t height; /* png image height */ + png_structp png_ptr; /* libpng private data */ + png_infop info_ptr; /* libpng private info */ + uchar_t *image_data; /* decoded image data */ + uchar_t **row_pointers; /* pntr to row starts */ + jmp_buf jmpbuf; /* png error processing */ + int error; /* error flag */ + int rowbytes; /* No. bytes in image row */ + short passes; + short channels; /* No. image channels */ + +/* + * 0 last byte + * +-------+-+-----------------------------------+-+ + * | | | -- data to be processed -- | | + * +-------+-+-----------------------------------+-+ + * ^ ^ ^ + * ipbuf ipbufstart ipbufsize + */ + + uchar_t *ipbuf; /* image data in buffer */ + int ipbufstart; /* first valid image byte */ + int ipbufsize; /* size of valid data in */ + + enum prog_state state; /* FSM current state */ + + uchar_t *linebuf; /* o/p raster data */ + +} DilloPng; + +#define DATASIZE (png->ipbufsize - png->ipbufstart) +#define BLACK 0 +#define WHITE 255 + +/* + * Forward declarations + */ +/* exported function */ +void *a_Png_image(const char *Type, void *Ptr, CA_Callback_t *Call, + void **Data); + + +static +void Png_error_handling(png_structp png_ptr, png_const_charp msg) +{ + DilloPng *png; + + DEBUG_MSG(6, "Png_error_handling: %s\n", msg); + png = png_get_error_ptr(png_ptr); + + png->error = 1; + png->state = IS_finished; + + longjmp(png->jmpbuf, 1); +} + +static void +Png_datainfo_callback(png_structp png_ptr, png_infop info_ptr) +{ + DilloPng *png; + int color_type; + int bit_depth; + int interlace_type; + uint_t i; + double gamma; + + DEBUG_MSG(5, "Png_datainfo_callback:\n"); + + png = png_get_progressive_ptr(png_ptr); + dReturn_if_fail (png != NULL); + + png_get_IHDR(png_ptr, info_ptr, &png->width, &png->height, + &bit_depth, &color_type, &interlace_type, NULL, NULL); + + DEBUG_MSG(5, "Png_datainfo_callback: png->width = %ld\n" + "Png_datainfo_callback: png->height = %ld\n", + png->width, png->height); + + /* we need RGB/RGBA in the end */ + if (color_type == PNG_COLOR_TYPE_PALETTE && bit_depth <= 8) { + /* Convert indexed images to RGB */ + png_set_expand (png_ptr); + } else if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) { + /* Convert grayscale to RGB */ + png_set_expand (png_ptr); + } else if (png_get_valid (png_ptr, info_ptr, PNG_INFO_tRNS)) { + /* We have transparency header, convert it to alpha channel */ + png_set_expand(png_ptr); + } else if (bit_depth < 8) { + png_set_expand(png_ptr); + } + + if (bit_depth == 16) { + png_set_strip_16(png_ptr); + } + + /* Get and set gamma information. Beware: gamma correction 2.2 will + only work on PC's. todo: select screen gamma correction for other + platforms. */ + if (png_get_gAMA(png_ptr, info_ptr, &gamma)) + png_set_gamma(png_ptr, 2.2, gamma); + + /* Convert gray scale to RGB */ + if (color_type == PNG_COLOR_TYPE_GRAY || + color_type == PNG_COLOR_TYPE_GRAY_ALPHA) { + png_set_gray_to_rgb(png_ptr); + } + + /* Interlaced */ + if (interlace_type != PNG_INTERLACE_NONE) { + png->passes = png_set_interlace_handling(png_ptr); + } + + /* get libpng to update it's state */ + png_read_update_info(png_ptr, info_ptr); + + png_get_IHDR(png_ptr, info_ptr, &png->width, &png->height, + &bit_depth, &color_type, &interlace_type, NULL, NULL); + + png->rowbytes = png_get_rowbytes(png_ptr, info_ptr); + png->channels = png_get_channels(png_ptr, info_ptr); + + /* init Dillo specifics */ + DEBUG_MSG(5, "Png_datainfo_callback: rowbytes = %d\n" + "Png_datainfo_callback: width = %ld\n" + "Png_datainfo_callback: height = %ld\n", + png->rowbytes, png->width, png->height); + + png->image_data = (uchar_t *) dMalloc(png->rowbytes * png->height); + png->row_pointers = (uchar_t **) dMalloc(png->height * sizeof(uchar_t *)); + + for (i = 0; i < png->height; i++) + png->row_pointers[i] = png->image_data + (i * png->rowbytes); + + png->linebuf = dMalloc(3 * png->width); + + /* Initialize the dicache-entry here */ + a_Dicache_set_parms(png->url, png->version, png->Image, + (uint_t)png->width, (uint_t)png->height, + DILLO_IMG_TYPE_RGB); +} + +static void + Png_datarow_callback(png_structp png_ptr, png_bytep new_row, + png_uint_32 row_num, int pass) +{ + DilloPng *png; + uint_t i; + + if (!new_row) /* work to do? */ + return; + + DEBUG_MSG(5, "Png_datarow_callback: row_num = %ld\n", row_num); + + png = png_get_progressive_ptr(png_ptr); + + png_progressive_combine_row(png_ptr, png->row_pointers[row_num], new_row); + + switch (png->channels) { + case 3: + a_Dicache_write(png->Image, png->url, png->version, + png->image_data + (row_num * png->rowbytes), + 0, (uint_t)row_num); + break; + case 4: + { + /* todo: get the backgound color from the parent + * of the image widget -- Livio. */ + int a, bg_red, bg_green, bg_blue; + uchar_t *pl = png->linebuf; + uchar_t *data = png->image_data + (row_num * png->rowbytes); + + /* todo: maybe change prefs.bg_color to `a_Dw_widget_get_bg_color`, + * when background colors are correctly implementated */ + bg_blue = (png->Image->bg_color) & 0xFF; + bg_green = (png->Image->bg_color>>8) & 0xFF; + bg_red = (png->Image->bg_color>>16) & 0xFF; + + for (i = 0; i < png->width; i++) { + a = *(data+3); + + if (a == 255) { + *(pl++) = *(data++); + *(pl++) = *(data++); + *(pl++) = *(data++); + data++; + } else if (a == 0) { + *(pl++) = bg_red; + *(pl++) = bg_green; + *(pl++) = bg_blue; + data += 4; + } else { + png_composite(*(pl++), *(data++), a, bg_red); + png_composite(*(pl++), *(data++), a, bg_green); + png_composite(*(pl++), *(data++), a, bg_blue); + data++; + } + } + a_Dicache_write(png->Image, png->url, png->version, + png->linebuf, 0, (uint_t)row_num); + break; + } + default: + MSG("Png_datarow_callback: unexpected number of channels = %d\n", + png->channels); + abort(); + } +} + +static void + Png_dataend_callback(png_structp png_ptr, png_infop info_ptr) +{ + DilloPng *png; + + DEBUG_MSG(5, "Png_dataend_callback:\n"); + + png = png_get_progressive_ptr(png_ptr); + png->state = IS_finished; +} + + +/* + * Op: Operation to perform. + * If (Op == 0) + * start or continue processing an image if image data exists. + * else + * terminate processing, cleanup any allocated memory, + * close down the decoding process. + * + * Client->CbData : pointer to previously allocated DilloPng work area. + * This holds the current state of the image processing and is saved + * across calls to this routine. + * Client->Buf : Pointer to data start. + * Client->BufSize : the size of the data buffer. + * + * You have to keep track of where you are in the image data and + * how much has been processed. + * + * It's entirely possible that you will not see the end of the data. The + * user may terminate transfer via a Stop button or there may be a network + * failure. This means that you can't just wait for all the data to be + * presented before starting conversion and display. + */ +static void Png_callback(int Op, CacheClient_t *Client) +{ + DilloPng *png = Client->CbData; + + if (Op) { + /* finished - free up the resources for this image */ + a_Dicache_close(png->url, png->version, Client); + dFree(png->image_data); + dFree(png->row_pointers); + dFree(png->linebuf); + + if (setjmp(png->jmpbuf)) + MSG_WARN("PNG: can't destroy read structure\n"); + else if (png->png_ptr) + png_destroy_read_struct(&png->png_ptr, &png->info_ptr, NULL); + dFree(png); + return; + } + + /* Let's make some sound if we have been called with no data */ + dReturn_if_fail ( Client->Buf != NULL && Client->BufSize > 0 ); + + DEBUG_MSG(5, "Png_callback BufSize = %d\n", Client->BufSize); + + /* Keep local copies so we don't have to pass multiple args to + * a number of functions. */ + png->ipbuf = Client->Buf; + png->ipbufsize = Client->BufSize; + + /* start/resume the FSM here */ + while (png->state != IS_finished && DATASIZE) { + DEBUG_MSG(5, "State = %s\n", prog_state_name[png->state]); + + switch (png->state) { + case IS_init: + if (DATASIZE < 8) { + return; /* need MORE data */ + } + /* check the image signature - DON'T update ipbufstart! */ + if (!png_check_sig(png->ipbuf, DATASIZE)) { + /* you lied to me about it being a PNG image */ + png->state = IS_finished; + break; + } + /* OK, it looks like a PNG image, lets do some set up stuff */ + png->png_ptr = png_create_read_struct( + PNG_LIBPNG_VER_STRING, + png, + (png_error_ptr)Png_error_handling, + (png_error_ptr)Png_error_handling); + dReturn_if_fail (png->png_ptr != NULL); + png->info_ptr = png_create_info_struct(png->png_ptr); + dReturn_if_fail (png->info_ptr != NULL); + + setjmp(png->jmpbuf); + if (!png->error) { + png_set_progressive_read_fn( + png->png_ptr, + png, + Png_datainfo_callback, /* performs local init functions */ + Png_datarow_callback, /* performs per row action */ + Png_dataend_callback); /* performs cleanup actions */ + png->state = IS_nextdata; + } + break; + + case IS_nextdata: + if (setjmp(png->jmpbuf)) { + png->state = IS_finished; + } else if (!png->error) { + png_process_data( png->png_ptr, + png->info_ptr, + png->ipbuf + png->ipbufstart, + (png_size_t)DATASIZE); + + png->ipbufstart += DATASIZE; + } + break; + + default: + MSG_WARN("PNG decoder: bad state = %d\n", png->state); + abort(); + } + } +} + +/* + * Create the image state data that must be kept between calls + */ +static DilloPng *Png_new(DilloImage *Image, DilloUrl *url, int version) +{ + DilloPng *png = dNew0(DilloPng, 1); + + png->Image = Image; + png->url = url; + png->version = version; + png->error = 0; + png->ipbuf = NULL; + png->ipbufstart = 0; + png->ipbufsize = 0; + png->state = IS_init; + png->linebuf = NULL; + png->image_data = NULL; + png->row_pointers = NULL; + + return png; +} + +/* + * MIME handler for "image/png" type + * (Sets Png_callback or a_Dicache_callback as the cache-client) + */ +void *a_Png_image(const char *Type, void *Ptr, CA_Callback_t *Call, + void **Data) +{ +/* + * Type: MIME type + * Ptr: points to a Web structure + * Call: Dillo calls this with more data/eod + * Data: raw image data + */ + + DilloWeb *web = Ptr; + DICacheEntry *DicEntry; + + DEBUG_MSG(5, "a_Png_image: Type = %s\n" + "a_Png_image: libpng - Compiled %s; using %s.\n" + "a_Png_image: zlib - Compiled %s; using %s.\n", + Type, PNG_LIBPNG_VER_STRING, png_libpng_ver, + ZLIB_VERSION, zlib_version); + + if (!web->Image) + web->Image = a_Image_new(0, 0, NULL, prefs.bg_color); + + /* Add an extra reference to the Image (for dicache usage) */ + a_Image_ref(web->Image); + + DicEntry = a_Dicache_get_entry(web->url); + if (!DicEntry) { + /* Let's create an entry for this image... */ + DicEntry = a_Dicache_add_entry(web->url); + + /* ... and let the decoder feed it! */ + *Data = Png_new(web->Image, DicEntry->url, DicEntry->version); + *Call = (CA_Callback_t) Png_callback; + } else { + /* Let's feed our client from the dicache */ + a_Dicache_ref(DicEntry->url, DicEntry->version); + *Data = web->Image; + *Call = (CA_Callback_t) a_Dicache_callback; + } + return (web->Image->dw); +} + +#endif /* ENABLE_PNG */ diff --git a/src/prefs.c b/src/prefs.c new file mode 100644 index 00000000..1d5e6053 --- /dev/null +++ b/src/prefs.c @@ -0,0 +1,434 @@ +/* + * Preferences for dillo + * + * Copyright (C) 2006 Jorge Arellano Cid <jcid@dillo.org> + * + * 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 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <stdlib.h> +#include <string.h> /* for strchr */ +#include <fcntl.h> +#include <unistd.h> +#include <locale.h> /* for setlocale */ +#include <ctype.h> /* for isspace */ +#include "prefs.h" +#include "colors.h" +#include "misc.h" +#include "msg.h" + +#define RCNAME "dillorc2" + + +/* + * Global Data + */ +DilloPrefs prefs; + +typedef struct SymNode_ SymNode_t; + +struct SymNode_ { + char *name; + RcToken_t token; +}; + +/* Symbol array, sorted alphabetically */ +SymNode_t symbols[] = { + { "allow_white_bg", DRC_TOKEN_ALLOW_WHITE_BG }, + { "bg_color", DRC_TOKEN_BG_COLOR }, + { "contrast_visited_color", DRC_TOKEN_CONTRAST_VISITED_COLOR }, + { "enterpress_forces_submit", DRC_TOKEN_ENTERPRESS_FORCES_SUBMIT }, + { "font_factor", DRC_TOKEN_FONT_FACTOR }, + { "force_my_colors", DRC_TOKEN_FORCE_MY_COLORS }, + { "fullwindow_start", DRC_TOKEN_FULLWINDOW_START }, + { "fw_fontname", DRC_TOKEN_FW_FONT }, + { "generate_submit", DRC_TOKEN_GENERATE_SUBMIT }, + { "geometry", DRC_TOKEN_GEOMETRY }, + { "home", DRC_TOKEN_HOME }, + { "http_proxy", DRC_TOKEN_PROXY }, + { "http_proxyuser", DRC_TOKEN_PROXYUSER }, + { "limit_text_width", DRC_TOKEN_LIMIT_TEXT_WIDTH }, + { "link_color", DRC_TOKEN_LINK_COLOR }, + { "no_proxy", DRC_TOKEN_NOPROXY }, + { "panel_size", DRC_TOKEN_PANEL_SIZE }, + { "search_url", DRC_TOKEN_SEARCH_URL }, + { "show_back", DRC_TOKEN_SHOW_BACK }, + { "show_bookmarks", DRC_TOKEN_SHOW_BOOKMARKS }, + { "show_clear_url", DRC_TOKEN_SHOW_CLEAR_URL }, + { "show_extra_warnings", DRC_TOKEN_SHOW_EXTRA_WARNINGS }, + { "show_forw", DRC_TOKEN_SHOW_FORW }, + { "show_home", DRC_TOKEN_SHOW_HOME }, + { "show_menubar", DRC_TOKEN_SHOW_MENUBAR }, + { "show_msg", DRC_TOKEN_SHOW_MSG }, + { "show_progress_box", DRC_TOKEN_SHOW_PROGRESS_BOX }, + { "show_reload", DRC_TOKEN_SHOW_RELOAD }, + { "show_save", DRC_TOKEN_SHOW_SAVE }, + { "show_search", DRC_TOKEN_SHOW_SEARCH }, + { "show_stop", DRC_TOKEN_SHOW_STOP }, + { "show_tooltip", DRC_TOKEN_SHOW_TOOLTIP }, + { "show_url", DRC_TOKEN_SHOW_URL }, + { "small_icons", DRC_TOKEN_SMALL_ICONS }, + { "start_page", DRC_TOKEN_START_PAGE }, + { "text_color", DRC_TOKEN_TEXT_COLOR }, + { "transient_dialogs", DRC_TOKEN_TRANSIENT_DIALOGS }, + { "use_dicache", DRC_TOKEN_USE_DICACHE }, + { "use_oblique", DRC_TOKEN_USE_OBLIQUE }, + { "visited_color", DRC_TOKEN_VISITED_COLOR, }, + { "vw_fontname", DRC_TOKEN_VW_FONT }, + { "w3c_plus_heuristics", DRC_TOKEN_W3C_PLUS_HEURISTICS } +}; + +static const uint_t n_symbols = sizeof (symbols) / sizeof (symbols[0]); + +/* + *- Mini parser ------------------------------------------------------------- + */ + +/* + * Comparison function for binary search + */ +static int Prefs_symbol_cmp(const void *a, const void *b) +{ + return strcmp(((SymNode_t*)a)->name, ((SymNode_t*)b)->name); +} + +/* + * Take a dillo rc line and return 'name' and 'value' pointers to it. + * Notes: + * - line is modified! + * - it skips blank lines and lines starting with '#' + * + * Return value: 0 on successful value/pair, -1 otherwise + */ +static int Prefs_get_pair(char **line, char **name, char **value) +{ + char *eq, *p; + int len, ret = -1; + + dReturn_val_if_fail(*line, ret); + + *name = NULL; + *value = NULL; + dStrstrip(*line); + if (*line[0] != '#' && (eq = strchr(*line, '='))) { + /* get name */ + for (p = *line; *p && *p != '=' && !isspace(*p); ++p); + *p = 0; + *name = *line; + + /* get value */ + if (p == eq) { + for (++p; isspace(*p); ++p); + len = strlen(p); + if (len >= 2 && *p == '"' && p[len-1] == '"') { + p[len-1] = 0; + ++p; + } + *value = p; + ret = 0; + } + } + + if (*line[0] && *line[0] != '#' && (!*name || !*value)) { + MSG("prefs: Syntax error in %s: name=\"%s\" value=\"%s\"\n", + RCNAME, *name, *value); + } + return ret; +} + +/* + * Parse a name/value pair and set preferences accordingly. + */ +static int Prefs_parse_pair(char *name, char *value) +{ + int st; + SymNode_t key, *node; + + key.name = name; + node = bsearch(&key, symbols, n_symbols, + sizeof(SymNode_t), Prefs_symbol_cmp); + if (!node) { + MSG("prefs: {%s} is not a recognized token.\n", name); + return -1; + } + + switch (node->token) { + case DRC_TOKEN_GEOMETRY: + a_Misc_parse_geometry(value, &prefs.xpos, &prefs.ypos, + &prefs.width, &prefs.height); + break; + case DRC_TOKEN_PROXY: + a_Url_free(prefs.http_proxy); + prefs.http_proxy = a_Url_new(value, NULL, 0, 0, 0); + break; + case DRC_TOKEN_PROXYUSER: + dFree(prefs.http_proxyuser); + prefs.http_proxyuser = dStrdup(value); + break; + case DRC_TOKEN_NOPROXY: + dFree(prefs.no_proxy); + prefs.no_proxy = dStrdup(value); + break; + case DRC_TOKEN_LINK_COLOR: + prefs.link_color = a_Color_parse(value, prefs.link_color, &st); + break; + case DRC_TOKEN_VISITED_COLOR: + prefs.visited_color = a_Color_parse(value, prefs.visited_color, &st); + break; + case DRC_TOKEN_TEXT_COLOR: + prefs.text_color = a_Color_parse(value, prefs.text_color, &st); + break; + case DRC_TOKEN_BG_COLOR: + prefs.bg_color = a_Color_parse(value, prefs.bg_color, &st); + break; + case DRC_TOKEN_ALLOW_WHITE_BG: + prefs.allow_white_bg = (strcmp(value, "YES") == 0); + break; + case DRC_TOKEN_FORCE_MY_COLORS: + prefs.force_my_colors = (strcmp(value, "YES") == 0); + break; + case DRC_TOKEN_CONTRAST_VISITED_COLOR: + prefs.contrast_visited_color = (strcmp(value, "YES") == 0); + break; + case DRC_TOKEN_USE_OBLIQUE: + prefs.use_oblique = (strcmp(value, "YES") == 0); + break; + case DRC_TOKEN_PANEL_SIZE: + if (!dStrcasecmp(value, "tiny")) + prefs.panel_size = 1; + else if (!dStrcasecmp(value, "small")) + prefs.panel_size = 2; + else if (!dStrcasecmp(value, "medium")) + prefs.panel_size = 3; + else /* default to "large" */ + prefs.panel_size = 4; + break; + case DRC_TOKEN_SMALL_ICONS: + prefs.small_icons = (strcmp(value, "YES") == 0); + break; + case DRC_TOKEN_START_PAGE: + a_Url_free(prefs.start_page); + prefs.start_page = a_Url_new(value, NULL, 0, 0, 0); + break; + case DRC_TOKEN_HOME: + a_Url_free(prefs.home); + prefs.home = a_Url_new(value, NULL, 0, 0, 0); + break; + case DRC_TOKEN_SHOW_TOOLTIP: + prefs.show_tooltip = (strcmp(value, "YES") == 0); + break; + case DRC_TOKEN_FONT_FACTOR: + prefs.font_factor = strtod(value, NULL); + break; + case DRC_TOKEN_LIMIT_TEXT_WIDTH: + prefs.limit_text_width = (strcmp(value, "YES") == 0); + break; + case DRC_TOKEN_W3C_PLUS_HEURISTICS: + prefs.w3c_plus_heuristics = (strcmp(value,"YES") == 0); + break; + case DRC_TOKEN_USE_DICACHE: + prefs.use_dicache = (strcmp(value, "YES") == 0); + break; + case DRC_TOKEN_SHOW_BACK: + prefs.show_back = (strcmp(value, "YES") == 0); + break; + case DRC_TOKEN_SHOW_FORW: + prefs.show_forw = (strcmp(value, "YES") == 0); + break; + case DRC_TOKEN_SHOW_HOME: + prefs.show_home = (strcmp(value, "YES") == 0); + break; + case DRC_TOKEN_SHOW_RELOAD: + prefs.show_reload = (strcmp(value, "YES") == 0); + break; + case DRC_TOKEN_SHOW_SAVE: + prefs.show_save = (strcmp(value, "YES") == 0); + break; + case DRC_TOKEN_SHOW_STOP: + prefs.show_stop = (strcmp(value, "YES") == 0); + break; + case DRC_TOKEN_SHOW_BOOKMARKS: + prefs.show_bookmarks = (strcmp(value, "YES") == 0); + break; + case DRC_TOKEN_SHOW_MENUBAR: + prefs.show_menubar = (strcmp(value, "YES") == 0); + break; + case DRC_TOKEN_SHOW_CLEAR_URL: + prefs.show_clear_url = (strcmp(value, "YES") == 0); + break; + case DRC_TOKEN_SHOW_URL: + prefs.show_url = (strcmp(value, "YES") == 0); + break; + case DRC_TOKEN_SHOW_SEARCH: + prefs.show_search = (strcmp(value, "YES") == 0); + break; + case DRC_TOKEN_SHOW_PROGRESS_BOX: + prefs.show_progress_box = (strcmp(value, "YES") == 0); + break; + case DRC_TOKEN_FULLWINDOW_START: + prefs.fullwindow_start = (strcmp(value, "YES") == 0); + break; + case DRC_TOKEN_TRANSIENT_DIALOGS: + prefs.transient_dialogs = (strcmp(value, "YES") == 0); + break; + case DRC_TOKEN_FW_FONT: + dFree(prefs.fw_fontname); + prefs.fw_fontname = dStrdup(value); + break; + case DRC_TOKEN_VW_FONT: + dFree(prefs.vw_fontname); + prefs.vw_fontname = dStrdup(value); + break; + case DRC_TOKEN_GENERATE_SUBMIT: + prefs.generate_submit = (strcmp(value, "YES") == 0); + break; + case DRC_TOKEN_ENTERPRESS_FORCES_SUBMIT: + prefs.enterpress_forces_submit = (strcmp(value, "YES") == 0); + break; + case DRC_TOKEN_SEARCH_URL: + dFree(prefs.search_url); + prefs.search_url = dStrdup(value); + break; + case DRC_TOKEN_SHOW_MSG: + prefs.show_msg = (strcmp(value, "YES") == 0); + break; + case DRC_TOKEN_SHOW_EXTRA_WARNINGS: + prefs.show_extra_warnings = (strcmp(value, "YES") == 0); + break; + default: + MSG_WARN("prefs: {%s} IS recognized but not handled!\n", name); + break; /* Not reached */ + } + + return 0; +} + +/* + * Parse dillorc and set the values in the prefs structure. + */ +static int Prefs_parse_dillorc(void) +{ + FILE *F_in; + char *filename, *line, *name, *value; + int ret = -1; + + filename = dStrconcat(dGethomedir(), "/.dillo/", RCNAME, NULL); + if (!(F_in = fopen(filename, "r"))) { + MSG("prefs: Can't open %s file: %s\n", RCNAME, filename); + if (!(F_in = fopen(DILLORC_SYS, "r"))) { + MSG("prefs: Can't open %s file: %s\n", RCNAME, DILLORC_SYS); + MSG("prefs: Using internal defaults.\n"); + } else { + MSG("prefs: Using %s\n", DILLORC_SYS); + } + } + + if (F_in) { + /* scan dillorc line by line */ + while ((line = dGetline(F_in)) != NULL) { + if (Prefs_get_pair(&line, &name, &value) == 0){ + _MSG("{%s}, {%s}\n", name, value); + Prefs_parse_pair(name, value); + } + dFree(line); + } + fclose(F_in); + ret = 0; + } + dFree(filename); + + return ret; +} + +/*---------------------------------------------------------------------------*/ + +void a_Prefs_init(void) +{ + char *old_locale; + + prefs.width = D_GEOMETRY_DEFAULT_WIDTH; + prefs.height = D_GEOMETRY_DEFAULT_HEIGHT; + prefs.xpos = D_GEOMETRY_DEFAULT_XPOS; + prefs.ypos = D_GEOMETRY_DEFAULT_YPOS; + prefs.http_proxy = NULL; + prefs.http_proxyuser = NULL; + prefs.no_proxy = NULL; + prefs.link_color = DW_COLOR_DEFAULT_BLUE; + prefs.visited_color = DW_COLOR_DEFAULT_PURPLE; + prefs.bg_color = DW_COLOR_DEFAULT_BGND; + prefs.text_color = DW_COLOR_DEFAULT_BLACK; + prefs.use_oblique = FALSE; + prefs.start_page = a_Url_new(DILLO_START_PAGE, NULL, 0, 0, 0); + prefs.home = a_Url_new(DILLO_HOME, NULL, 0, 0, 0); + prefs.allow_white_bg = TRUE; + prefs.force_my_colors = FALSE; + prefs.contrast_visited_color = FALSE; + prefs.show_tooltip = FALSE; + prefs.panel_size = 1; + prefs.small_icons = FALSE; + prefs.limit_text_width = FALSE; + prefs.w3c_plus_heuristics = TRUE; + prefs.font_factor = 1.0; + prefs.use_dicache = FALSE; + prefs.show_back=TRUE; + prefs.show_forw=TRUE; + prefs.show_home=TRUE; + prefs.show_reload=TRUE; + prefs.show_save=TRUE; + prefs.show_stop=TRUE; + prefs.show_bookmarks=TRUE; + prefs.show_menubar=TRUE; + prefs.show_clear_url=TRUE; + prefs.show_url=TRUE; + prefs.show_search=TRUE; + prefs.show_progress_box=TRUE; + prefs.fullwindow_start=FALSE; + prefs.transient_dialogs=FALSE; + prefs.vw_fontname = dStrdup("helvetica"); + prefs.fw_fontname = dStrdup("courier"); + prefs.generate_submit = FALSE; + prefs.enterpress_forces_submit = FALSE; + prefs.search_url = dStrdup("http://www.google.com/search?q=%s"); + prefs.show_msg = TRUE; + prefs.show_extra_warnings = FALSE; + + /* this locale stuff is to avoid parsing problems with float numbers */ + old_locale = dStrdup (setlocale (LC_NUMERIC, NULL)); + setlocale (LC_NUMERIC, "C"); + + Prefs_parse_dillorc(); + + setlocale (LC_NUMERIC, old_locale); + dFree (old_locale); + +} + +/* + * Preferences memory-deallocation + * (Call this one at exit time) + */ +void a_Prefs_freeall(void) +{ + dFree(prefs.http_proxyuser); + dFree(prefs.no_proxy); + a_Url_free(prefs.http_proxy); + dFree(prefs.fw_fontname); + dFree(prefs.vw_fontname); + a_Url_free(prefs.start_page); + a_Url_free(prefs.home); + dFree(prefs.search_url); +} diff --git a/src/prefs.h b/src/prefs.h new file mode 100644 index 00000000..79f06bc1 --- /dev/null +++ b/src/prefs.h @@ -0,0 +1,130 @@ +#ifndef __PREFS_H__ +#define __PREFS_H__ + +#include "url.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#define DILLO_START_PAGE "about:splash" +#define DILLO_HOME "http://www.dillo.org/" +#define D_GEOMETRY_DEFAULT_WIDTH 640 +#define D_GEOMETRY_DEFAULT_HEIGHT 550 +#define D_GEOMETRY_DEFAULT_XPOS -9999 +#define D_GEOMETRY_DEFAULT_YPOS -9999 + +#define DW_COLOR_DEFAULT_GREY 0xd6d6d6 +#define DW_COLOR_DEFAULT_BLACK 0x000000 +#define DW_COLOR_DEFAULT_BLUE 0x0000ff +#define DW_COLOR_DEFAULT_PURPLE 0x800080 +#define DW_COLOR_DEFAULT_BGND 0xd6d6c0 + + +/* define enumeration values to be returned for specific symbols */ +typedef enum { + DRC_TOKEN_ALLOW_WHITE_BG, + DRC_TOKEN_BG_COLOR, + DRC_TOKEN_CONTRAST_VISITED_COLOR, + DRC_TOKEN_ENTERPRESS_FORCES_SUBMIT, + DRC_TOKEN_FONT_FACTOR, + DRC_TOKEN_FORCE_MY_COLORS, + DRC_TOKEN_FULLWINDOW_START, + DRC_TOKEN_FW_FONT, + DRC_TOKEN_GENERATE_SUBMIT, + DRC_TOKEN_GEOMETRY, + DRC_TOKEN_HOME, + DRC_TOKEN_LIMIT_TEXT_WIDTH, + DRC_TOKEN_LINK_COLOR, + DRC_TOKEN_NOPROXY, + DRC_TOKEN_PANEL_SIZE, + DRC_TOKEN_PROXY, + DRC_TOKEN_PROXYUSER, + DRC_TOKEN_SEARCH_URL, + DRC_TOKEN_SHOW_BACK, + DRC_TOKEN_SHOW_BOOKMARKS, + DRC_TOKEN_SHOW_CLEAR_URL, + DRC_TOKEN_SHOW_EXTRA_WARNINGS, + DRC_TOKEN_SHOW_FORW, + DRC_TOKEN_SHOW_HOME, + DRC_TOKEN_SHOW_MENUBAR, + DRC_TOKEN_SHOW_MSG, + DRC_TOKEN_SHOW_PROGRESS_BOX, + DRC_TOKEN_SHOW_RELOAD, + DRC_TOKEN_SHOW_SAVE, + DRC_TOKEN_SHOW_SEARCH, + DRC_TOKEN_SHOW_STOP, + DRC_TOKEN_SHOW_TOOLTIP, + DRC_TOKEN_SHOW_URL, + DRC_TOKEN_SMALL_ICONS, + DRC_TOKEN_START_PAGE, + DRC_TOKEN_TEXT_COLOR, + DRC_TOKEN_TRANSIENT_DIALOGS, + DRC_TOKEN_USE_DICACHE, + DRC_TOKEN_USE_OBLIQUE, + DRC_TOKEN_VISITED_COLOR, + DRC_TOKEN_VW_FONT, + DRC_TOKEN_W3C_PLUS_HEURISTICS +} RcToken_t; + +typedef struct _DilloPrefs DilloPrefs; + +struct _DilloPrefs { + int width; + int height; + int xpos; + int ypos; + DilloUrl *http_proxy; + char *http_proxyuser; + char *no_proxy; + DilloUrl *start_page; + DilloUrl *home; + int32_t link_color; + int32_t visited_color; + int32_t bg_color; + int32_t text_color; + bool_t allow_white_bg; + bool_t use_oblique; + bool_t force_my_colors; + bool_t contrast_visited_color; + bool_t show_tooltip; + int panel_size; + bool_t small_icons; + bool_t limit_text_width; + bool_t w3c_plus_heuristics; + double font_factor; + bool_t use_dicache; + bool_t show_back; + bool_t show_forw; + bool_t show_home; + bool_t show_reload; + bool_t show_save; + bool_t show_stop; + bool_t show_bookmarks; + bool_t show_menubar; + bool_t show_clear_url; + bool_t show_url; + bool_t show_search; + bool_t show_progress_box; + bool_t fullwindow_start; + bool_t transient_dialogs; + char *vw_fontname; + char *fw_fontname; + bool_t generate_submit; + bool_t enterpress_forces_submit; + char *search_url; + bool_t show_msg; + bool_t show_extra_warnings; +}; + +/* Global Data */ +extern DilloPrefs prefs; + +void a_Prefs_init(void); +void a_Prefs_freeall(void); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __PREFS_H__ */ diff --git a/src/srch b/src/srch new file mode 100755 index 00000000..06266382 --- /dev/null +++ b/src/srch @@ -0,0 +1,33 @@ +#!/bin/sh +# +# Find a token within source files ( *.[ch] ) +# Enjoy! +# Jorge.- + +if [ $# = 1 ]; then + FLAGS="-H" +elif [ $# = 2 ]; then + FLAGS="-H $1" + shift 1 +else + echo "Usage:" + echo " srch [-options] <token>" + exit 0 +fi + +# find "./" -name "*.[ch]" -print -exec grep $1 {} \; +egrep $FLAGS "$1" *.[ch][ch] +egrep $FLAGS "$1" *.[ch] +egrep $FLAGS "$1" IO/*.[ch][ch]* +egrep $FLAGS "$1" IO/*.[ch] +egrep $FLAGS "$1" ../dpi/*.[ch] +egrep $FLAGS "$1" ../dpi/*.[ch][ch] +egrep $FLAGS "$1" ../dpid/*.[ch] +egrep $FLAGS "$1" ../dpip/*.[ch] +egrep $FLAGS "$1" ../dlib/*.[ch] + +#egrep $FLAGS "$1" dw/*[ch] +#egrep $FLAGS "$1" lout/*[ch] +egrep $FLAGS "$1" ../../dw-testbed/dw/*[ch] +egrep $FLAGS "$1" ../../dw-testbed/lout/*[ch] + diff --git a/src/timeout.cc b/src/timeout.cc new file mode 100644 index 00000000..da944803 --- /dev/null +++ b/src/timeout.cc @@ -0,0 +1,46 @@ +/* + * File: timeout.cc + * + * Copyright (C) 2005 Jorge Arellano Cid <jcid@dillo.org> + * + * 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 3 of the License, or + * (at your option) any later version. + */ + +// Simple ADT for timeout functions + +#include <fltk/run.h> +#include "timeout.hh" + +using namespace fltk; + + +// C++ functions with C linkage ---------------------------------------------- + +/* + * Hook a one-time timeout function 'cb' after 't' seconds + * with 'cbdata" as its data. + */ +void a_Timeout_add(float t, TimeoutCb_t cb, void *cbdata) +{ + add_timeout(t, cb, cbdata); +} + +/* + * To be called from iside the 'cb' function when it wants to keep running + */ +void a_Timeout_repeat(float t, TimeoutCb_t cb, void *cbdata) +{ + add_timeout(t, cb, cbdata); +} + +/* + * Stop running a timeout function + */ +void a_Timeout_remove() +{ + /* in FLTK, timeouts run one time by default */ +} + diff --git a/src/timeout.hh b/src/timeout.hh new file mode 100644 index 00000000..5b7f4759 --- /dev/null +++ b/src/timeout.hh @@ -0,0 +1,20 @@ +#ifndef __TIMEOUT_HH__ +#define __TIMEOUT_HH__ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef void (*TimeoutCb_t)(void *data); + +void a_Timeout_add(float t, TimeoutCb_t cb, void *cbdata); +void a_Timeout_repeat(float t, TimeoutCb_t cb, void *cbdata); +void a_Timeout_remove(); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __TIMEOUT_HH__ */ + diff --git a/src/ui.cc b/src/ui.cc new file mode 100644 index 00000000..54523b88 --- /dev/null +++ b/src/ui.cc @@ -0,0 +1,912 @@ +/* + * File: ui.cc + * + * Copyright (C) 2005 Jorge Arellano Cid <jcid@dillo.org> + * + * 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 3 of the License, or + * (at your option) any later version. + */ + +// UI for Dillo + +#include <stdlib.h> +#include <stdio.h> + +#include <fltk/Window.h> +#include <fltk/Widget.h> +#include <fltk/Button.h> +#include <fltk/HighlightButton.h> +#include <fltk/Input.h> +#include <fltk/Output.h> +#include <fltk/run.h> +#include <fltk/BarGroup.h> +#include <fltk/PackedGroup.h> +#include <fltk/xpmImage.h> +#include <fltk/MultiImage.h> +#include <fltk/events.h> // for mouse buttons +#include <fltk/Box.h> +#include <fltk/InvisibleBox.h> +#include <fltk/PopupMenu.h> +#include <fltk/Item.h> +#include <fltk/Divider.h> + +#include "ui.hh" +#include "msg.h" + +using namespace fltk; + + +// Include image data +#include "pixmaps.h" + +#include "uicmd.hh" + +/* + * Local sub class + * (Used to avoid certain shortcuts in the location bar) + */ + +class NewInput : public Input { +public: + NewInput (int x, int y, int w, int h, const char* l=0) : + Input(x,y,w,h,l) {}; + int handle(int e); +}; + +/* + * Disable: UpKey, DownKey, PageUpKey, PageDownKey and CTRL+{o,HomeKey,EndKey} + */ +int NewInput::handle(int e) +{ + int k = event_key(); + bool ctrl = event_state(CTRL); + + _MSG("NewInput::handle event=%d", e); + if (ctrl && (k == 'o' || k == HomeKey || k == EndKey)) + return 0; + if ((e == KEY || e == KEYUP) && + k == UpKey || k == DownKey || k == PageUpKey || k == PageDownKey) { + _MSG(" {Up|Down|PgUp|PgDn} ret = 1\n"); + // todo: one way to handle this is to send(SHORTCUT) to the viewport + // and return 1 here. A cleaner approach may be to ignore the event and + // only allow the location and render area to take focus. + // + // Currently, zero is returned, so the parent gets the event and moves + // focus to a button widget that's not interested in these keys; + // once this new widget is focused, Up and Down start to work. + return 0; + + //this->window()->send(SHORTCUT); + //this->window()->send(e); + //return 1; + } + _MSG("\n"); + + return Input::handle(e); +} + +//---------------------------------------------------------------------------- + +/* + * Used to handle "paste" within the toolbar's Clear button. + */ +class NewHighlightButton : public HighlightButton { +public: + NewHighlightButton(int x, int y, int w, int h, const char *l=0) : + HighlightButton(x,y,w,h,l) {}; + int handle(int e); +}; + +int NewHighlightButton::handle(int e) +{ + if (e == PASTE) { + const char* t = event_text(); + if (t && *t) { + a_UIcmd_set_location_text(this->window()->user_data(), t); + a_UIcmd_open_urlstr(this->window()->user_data(), t); + return 1; + } + } + return HighlightButton::handle(e); +} + +// +// Toolbar buttons ----------------------------------------------------------- +// +//static const char *button_names[] = { +// "Back", "Forward", "Home", "Reload", "Save", "Stop", "Bookmarks", +// "Clear", "Search" +//}; + +// +// Global event handler function --------------------------------------------- +// +int global_event_handler(int e, Window *win) +{ + int ret = 0; + + if (e == SHORTCUT) { + if (event_state(CTRL)) { + if (event_key() == 'l') { + a_UIcmd_open_url_dialog(win->user_data()); + ret = 1; + } else if (event_key() == 'n') { + a_UIcmd_browser_window_new(win->w(), win->h()); + ret = 1; + } else if (event_key() == 'o') { + a_UIcmd_open_file(win->user_data()); + ret = 1; + } else if (event_key() == 'q') { + a_UIcmd_close_bw(win->user_data()); + ret = 1; + } else if (event_key() == 's') { + a_UIcmd_search_dialog(win->user_data()); + ret = 1; + } + } + + if (event_state(ALT) && event_key() == 'q') { + a_UIcmd_close_all_bw(); + } + } + return ret; +} + +// +// Callback functions -------------------------------------------------------- +// + +/* + * Callback handler for the close window event. + */ +void close_window_cb(Widget *wid, void *data) +{ + a_UIcmd_close_bw(data); +} + +/* + * Callback for the search button. + */ +static void search_cb(Widget *wid, void *data) +{ + int k = event_key(); + if (k && k <= 7) + MSG("[Search], mouse button %d was pressed\n", k); + + if (k == 1) { + a_UIcmd_search_dialog(wid->window()->user_data()); + } else if (k == 2) { + ((UI*)data)->color_change_cb_i(); + } else if (k == 3) { + ((UI*)data)->panel_cb_i(); + } +} + +/* + * Callback for the location's clear-button. + */ +void clear_cb(Widget *w, void *data) +{ + UI *ui = (UI*)data; + + int k = event_key(); + if (k && k <= 7) + MSG("[Clear], mouse button %d was pressed\n", k); + if (k == 1) { + ui->set_location(""); + ui->focus_location(); + } if (k == 2) { + ui->paste_url(); + } +} + +/* + * Change the color of the location bar. + * +static void color_change_cb(Widget *wid, void *data) +{ + ((UI*)data)->color_change_cb_i(); +} + */ + + +/* + * Send the browser to the new URL in the location. + */ +void location_cb(Widget *wid, void *data) +{ + Input *i = (Input*)wid; + + /* This test is necessary because WHEN_ENTER_KEY also includes + * other events we're not interested in */ + if (event_key() == ReturnKey) { + a_UIcmd_open_urlstr(i->window()->user_data(), i->value()); + } +} + + +/* + * Callback handler for button press on the panel + */ +void b1_cb(Widget *wid, void *cb_data) +{ + int bn = (int)cb_data; + int k = event_key(); + if (k && k <= 7) { + _MSG("[%s], mouse button %d was pressed\n", button_names[bn], k); + MSG("mouse button %d was pressed\n", k); + } + switch (bn) { + case UI_BACK: + if (k == 1) { + a_UIcmd_back(wid->window()->user_data()); + } else if (k == 3) { + a_UIcmd_back_popup(wid->window()->user_data()); + } + break; + case UI_FORW: + if (k == 1) { + a_UIcmd_forw(wid->window()->user_data()); + } else if (k == 3) { + a_UIcmd_forw_popup(wid->window()->user_data()); + } + break; + case UI_HOME: + if (k == 1) { + a_UIcmd_home(wid->window()->user_data()); + } + break; + case UI_RELOAD: + if (k == 1) { + a_UIcmd_reload(wid->window()->user_data()); + } + break; + case UI_SAVE: + if (k == 1) { + a_UIcmd_save(wid->window()->user_data()); + } + break; + case UI_STOP: + if (k == 1) { + a_UIcmd_stop(wid->window()->user_data()); + } + break; + case UI_BOOK: + if (k == 1) { + a_UIcmd_book(wid->window()->user_data()); + } + break; + default: + break; + } +} + +/* + * Callback handler for fullscreen button press + */ +void fullscreen_cb(Widget *wid, void *data) +{ + ((UI*)data)->fullscreen_cb_i(); +} + +/* + * Callback for the bug meter button. + */ +void bugmeter_cb(Widget *w, void *data) +{ + int k = event_key(); + if (k && k <= 7) + MSG("[BugMeter], mouse button %d was pressed\n", k); + if (k == 1) { + a_UIcmd_view_page_bugs(((UI*)data)->user_data()); + } else if (k == 3) { + a_UIcmd_bugmeter_popup(((UI*)data)->user_data()); + } +} + +/* + * File menu item callback. + */ +void menu_cb(Widget* w, void*) +{ + Menu* menu = (Menu*)w; + Widget* item = menu->get_item(); + MSG("Callback for %s, item is %s\n", + menu->label() ? menu->label() : "menu bar", + item ? item->label() ? item->label() : "unnamed" : "none"); + //if (item) item->do_callback(); + menu->value(-1); +} + +////////////////////////////////////////////////////////////////////////////// +// UI class methods +// + +//---------------------------- +// Panel construction methods +//---------------------------- + +/* + * Create the archetipic browser buttons + */ +PackedGroup *UI::make_toolbar(int tw, int th) +{ + HighlightButton *b; + MultiImage *multi; + PackedGroup *p1=new PackedGroup(0,0,tw,th); + p1->begin(); + Back = b = new HighlightButton(xpos, 0, bw, bh, (lbl) ? "Back" : 0); + ImgLeftIns = new xpmImage(Small_Icons ? left_si_xpm : left_i_xpm); + ImgLeftSens = new xpmImage(Small_Icons ? left_s_xpm : left_xpm); + multi = new MultiImage(*ImgLeftSens, INACTIVE_R, *ImgLeftIns); + b->image(multi); + b->tooltip("Previous page"); + b->callback(b1_cb, (void *)UI_BACK); + HighlightButton::default_style->highlight_color(CuteColor); + + Forw = b = new HighlightButton(xpos, 0, bw, bh, (lbl) ? "Forw" : 0); + ImgRightIns = new xpmImage(Small_Icons ? right_si_xpm : right_i_xpm); + ImgRightSens = new xpmImage(Small_Icons ? right_s_xpm : right_xpm); + multi = new MultiImage(*ImgRightSens, INACTIVE_R, *ImgRightIns); + b->image(multi); + b->tooltip("Next page"); + b->callback(b1_cb, (void *)UI_FORW); + + Home = b = new HighlightButton(xpos, 0, bw, bh, (lbl) ? "Home" : 0); + b->image(new xpmImage(Small_Icons ? home_s_xpm : home_xpm)); + b->tooltip("Go to the Home page"); + b->callback(b1_cb, (void *)UI_HOME); + + Reload = b = new HighlightButton(xpos, 0, bw, bh, (lbl) ? "Reload" : 0); + b->image(new xpmImage(Small_Icons ? reload_s_xpm : reload_xpm)); + b->tooltip("Reload"); + b->callback(b1_cb, (void *)UI_RELOAD); + + Save = b = new HighlightButton(xpos, 0, bw, bh, (lbl) ? "Save" : 0); + b->image(new xpmImage(Small_Icons ? save_s_xpm : save_xpm)); + b->tooltip("Save this page"); + b->callback(b1_cb, (void *)UI_SAVE); + + Stop = b = new HighlightButton(xpos, 0, bw, bh, (lbl) ? "Stop" : 0); + ImgStopIns = new xpmImage(Small_Icons ? stop_si_xpm : stop_i_xpm); + ImgStopSens = new xpmImage(Small_Icons ? stop_s_xpm : stop_xpm); + multi = new MultiImage(*ImgStopSens, INACTIVE_R, *ImgStopIns); + b->image(multi); + b->tooltip("Stop loading"); + b->callback(b1_cb, (void *)UI_STOP); + + Bookmarks = b = new HighlightButton(xpos, 0, bw, bh, (lbl) ? "Book" : 0); + b->image(new xpmImage(Small_Icons ? bm_s_xpm : bm_xpm)); + b->tooltip("View bookmarks"); + b->callback(b1_cb, (void *)UI_BOOK); + + p1->type(PackedGroup::ALL_CHILDREN_VERTICAL); + p1->end(); + + return p1; +} + +/* + * Create the location box (Clear/Input/Search) + */ +PackedGroup *UI::make_location() +{ + Button *b; + PackedGroup *pg = new PackedGroup(0,0,0,0); + pg->begin(); + Clear = b = new NewHighlightButton(2,2,16,22,0); + b->image(new xpmImage(new_s_xpm)); + b->tooltip("Clear the URL box.\nMiddle-click to paste a URL."); + //b->callback(b1_cb, (void *)UI_CLEAR); + b->callback(clear_cb, (void *)this); + + Input *i = Location = new NewInput(0,0,0,0,0); + i->tooltip("Location"); + i->color(CuteColor); + i->when(WHEN_ENTER_KEY); + i->callback(location_cb, this); + + Search = b = new HighlightButton(0,0,16,22,0); + b->image(new xpmImage(search_xpm)); + b->tooltip("Search the Web"); + //b->callback(b1_cb, (void *)UI_SEARCH); + b->callback(search_cb, (void *)this); + + pg->type(PackedGroup::ALL_CHILDREN_VERTICAL); + pg->resizable(i); + pg->end(); + + return pg; +} + +/* + * Create the progress bars + */ +PackedGroup *UI::make_progress_bars(int wide, int thin_up) +{ + PackedGroup *pg = new PackedGroup(0,0,0,0); + pg->begin(); + // Images + IProg = new InvisibleBox(0,0,pr_w,0, + wide ? "Images\n0 of 0" : "0 of 0"); + IProg->box(thin_up ? THIN_UP_BOX : EMBOSSED_BOX); + IProg->labelcolor(GRAY10); + // Page + PProg = new InvisibleBox(0,0,pr_w,0, + wide ? "Page\n0.0KB" : "0.0KB"); + PProg->box(thin_up ? THIN_UP_BOX : EMBOSSED_BOX); + PProg->labelcolor(GRAY10); + + pg->type(PackedGroup::ALL_CHILDREN_VERTICAL); + pg->end(); + + return pg; +} + +/* + * Create the "File" menu + */ +Group *UI::make_menu(int tiny) +{ + Item *i; + + PopupMenu *pm = new PopupMenu(2,2,30,fh-4, tiny ? "&F" : "&File"); + pm->callback(menu_cb); + pm->begin(); + i = new Item("&New Browser"); + i->shortcut(CTRL+'n'); + i = new Item("&Open File..."); + i->shortcut(CTRL+'o'); + i = new Item("Open UR&L..."); + i->shortcut(CTRL+'l'); + i = new Item("Close Window"); + i->shortcut(CTRL+'q'); + new Divider(); + i = new Item("Exit Dillo"); + i->shortcut(ALT+'q'); + + pm->end(); + + return pm; +} + +/* + * Create the control panel + */ +Group *UI::make_panel(int ww) +{ + Widget *w; + Group *g1, *g2, *g3; + PackedGroup *pg; + + if (PanelSize > P_large) { + PanelSize = P_tiny; + Small_Icons = !Small_Icons; + } + + if (PanelSize == P_tiny) { + if (Small_Icons) + xpos = 0, bw = 22, bh = 22, fh = 0, lh = 22, lbl = 0, pr_w = 45; + else + xpos = 0, bw = 28, bh = 28, fh = 0, lh = 28, lbl = 0, pr_w = 45; + } else if (PanelSize == P_small) { + if (Small_Icons) + xpos = 0, bw = 20, bh = 20, fh = 0, lh = 20, lbl = 0, pr_w = 45; + else + xpos = 0, bw = 28, bh = 28, fh = 0, lh = 28, lbl = 0, pr_w = 45; + } else if (PanelSize == P_medium) { + if (Small_Icons) + xpos = 0, bw = 42, bh = 36, fh = 0, lh = 22, lbl = 1, pr_w = 60; + else + xpos = 0, bw = 45, bh = 45, fh = 0, lh = 28, lbl = 1, pr_w = 60; + } else { // P_large + if (Small_Icons) + xpos = 0, bw = 42, bh = 36, fh = 22, lh = 22, lbl = 1, pr_w = 60; + else + xpos = 0, bw = 45, bh = 45, fh = 28, lh = 28, lbl = 1, pr_w = 60; + } + + if (PanelSize == P_tiny) { + g1 = new Group(0,0,ww,bh); + g1->begin(); + // Toolbar + pg = make_toolbar(ww,bh); + pg->box(EMBOSSED_BOX); + //pg->box(BORDER_FRAME); + w = make_location(); + pg->add(w); + pg->resizable(w); + w = make_progress_bars(0,1); + pg->add(w); + + g1->resizable(pg); + g1->end(); + + } else { + g1 = new Group(0,0,ww,fh+lh+bh); + g1->begin(); + // File menu + if (PanelSize == P_large) { + g2 = new Group(0,0,ww,fh); + g2->begin(); + make_menu(0); + g2->box(EMBOSSED_BOX); + g2->end(); + } + + // Location + g2 = new Group(0,fh,ww,lh); + g2->begin(); + pg = make_location(); + pg->resize(ww,lh); + g2->resizable(pg); + g2->end(); + + // Toolbar + g3 = new Group(0,fh+lh,ww,bh); + g3->begin(); + pg = make_toolbar(ww,bh); + //w = new InvisibleBox(0,0,0,0,"i n v i s i b l e"); + w = new InvisibleBox(0,0,0,0,0); + pg->add(w); + pg->resizable(w); + + if (PanelSize == P_small) { + w = make_progress_bars(0,0); + } else { + w = make_progress_bars(1,0); + } + pg->add(w); + + g3->resizable(pg); // Better than 'w3' and it also works + pg->box(BORDER_FRAME); + //g3->box(EMBOSSED_BOX); + g3->end(); + + g1->resizable(g3); + g1->end(); + } + + return g1; +} + +/* + * User Interface constructor + */ +UI::UI(int win_w, int win_h, const char* label) : + Window(win_w, win_h, label) +{ + int s_h = 20; + resizable(this); + begin(); + TopGroup = this; + + // Set handler for the close window event + // (the argument is set later via user_data()) + TopGroup->callback(close_window_cb); + + // Set some default values + //PanelSize = P_tiny, CuteColor = 26, Small_Icons = 0; + PanelSize = P_medium, CuteColor = 206, Small_Icons = 0; + + // Control panel + Panel = make_panel(win_w); + + // Render area + Main = new Widget(0,Panel->h(),win_w,win_h-Panel->h()-s_h,"Welcome..."); + Main->box(FLAT_BOX); + Main->color(GRAY15); + Main->labelfont(HELVETICA_BOLD_ITALIC); + Main->labelsize(36); + Main->labeltype(SHADOW_LABEL); + Main->labelcolor(WHITE); + TopGroup->resizable(Main); + MainIdx = TopGroup->find(Main); + + // Status Panel + StatusPanel = new Group(0, win_h-s_h, win_w, s_h, 0); + StatusPanel->begin(); + // Status box + int bm_w = 16; + Status = new Output(0, 0, win_w-bm_w, s_h, 0); + Status->value(""); + //Status->box(UP_BOX); + Status->box(THIN_DOWN_BOX); + Status->clear_click_to_focus(); + Status->clear_tab_to_focus(); + //Status->throw_focus(); + + // Bug Meter + BugMeter = new HighlightButton(win_w-bm_w,0,bm_w,s_h,0); + ImgMeterOK = new xpmImage(mini_ok_xpm); + ImgMeterBug = new xpmImage(mini_bug_xpm); + BugMeter->image(ImgMeterOK); + BugMeter->box(THIN_DOWN_BOX); + BugMeter->align(ALIGN_INSIDE|ALIGN_CLIP|ALIGN_LEFT); + BugMeter->tooltip("Show HTML bugs\n(right-click for menu)"); + BugMeter->callback(bugmeter_cb, (void *)this); + + StatusPanel->resizable(Status); + StatusPanel->end(); + + end(); + + // Make the full screen button (to be attached to the viewport later) + // TODO: attach to the viewport + FullScreen = new HighlightButton(0,0,15,15); + ImgFullScreenOn = new xpmImage(full_screen_on_xpm); + ImgFullScreenOff = new xpmImage(full_screen_off_xpm); + FullScreen->image(ImgFullScreenOn); + FullScreen->tooltip("Hide Controls"); + FullScreen->callback(fullscreen_cb, (void *)this); + + add_event_handler(global_event_handler); + + //show(); +} + +/* + * FLTK event handler for this window. + */ +int UI::handle(int event) +{ + if (event == KEY || event == KEYUP) { + _MSG ("UI::handle %s key=%d\n", + event == KEY ? "KEY" : "KEYUP", event_key()); + return 0; + } + return Window::handle(event); +} + + +//---------------------------- +// API for the User Interface +//---------------------------- + +/* + * Get the text from the location input-box. + */ +const char *UI::get_location() +{ + return Location->value(); +} + +/* + * Set a new URL in the location input-box. + */ +void UI::set_location(const char *str) +{ + Location->static_value(""); + Location->insert(str); +} + +/* + * Focus location entry. + */ +void UI::focus_location() +{ + Location->take_focus(); +} + +/* + * Set a new message in the status bar. + */ +void UI::set_status(const char *str) +{ + Status->value(str); +} + +/* + * Set the page progress text + * cmd: 0 Deactivate, 1 Update, 2 Clear + */ +void UI::set_page_prog(size_t nbytes, int cmd) +{ + char str[32]; + + if (cmd == 0) { + PProg->deactivate(); + } else { + PProg->activate(); + if (cmd == 1) { + snprintf(str, 32, "%s%.1f KB", + (PanelSize == 0) ? "" : "Page\n", nbytes/1024.0); + } else if (cmd == 2) { + str[0] = '\0'; + } + PProg->copy_label(str); + PProg->redraw_label(); + } +} + +/* + * Set the image progress text + * cmd: 0 Deactivate, 1 Update, 2 Clear + */ +void UI::set_img_prog(int n_img, int t_img, int cmd) +{ + char str[32]; + + if (cmd == 0) { + IProg->deactivate(); + } else { + IProg->activate(); + if (cmd == 1) { + snprintf(str, 32, "%s%d of %d", + (PanelSize == 0) ? "" : "Images\n", n_img, t_img); + } else if (cmd == 2) { + str[0] = '\0'; + } + IProg->copy_label(str); + IProg->redraw_label(); + } +} + +/* + * Set the bug meter progress text + */ +void UI::set_bug_prog(int n_bug) +{ + char str[32]; + int new_w = 16; + + if (n_bug == 0) { + BugMeter->image(ImgMeterOK); + BugMeter->label(""); + } else if (n_bug >= 1) { + if (n_bug == 1) + BugMeter->image(ImgMeterBug); + snprintf(str, 32, "%d", n_bug); + BugMeter->copy_label(str); + BugMeter->redraw_label(); + new_w = strlen(str)*8 + 20; + } + Status->resize(0,0,StatusPanel->w()-new_w,Status->h()); + BugMeter->resize(StatusPanel->w()-new_w, 0, new_w, BugMeter->h()); + StatusPanel->init_sizes(); +} + +/* + * Customize the UI's panel (show/hide buttons) + */ +void UI::customize(int flags) +{ + Save->hide(); +} + +/* + * On-the-fly panel style change + */ +void UI::panel_cb_i() +{ + Group *NewPanel; + + // Create a new Panel + ++PanelSize; + NewPanel = make_panel(TopGroup->w()); + TopGroup->replace(*Panel, *NewPanel); + delete(Panel); + Panel = NewPanel; + // Scale the viewport + int p_h = Panel->h(); + Main->resize(0, p_h, TopGroup->w(), TopGroup->h() - p_h - Status->h()); + TopGroup->init_sizes(); + + Location->take_focus(); +} + +/* + * On-the-fly color style change + */ +void UI::color_change_cb_i() +{ + static int ncolor = 0, cols[] = {7,17,26,51,140,156,205,206,215,-1}; + + ncolor = (cols[ncolor+1] < 0) ? 0 : ncolor + 1; + CuteColor = cols[ncolor]; + MSG("Location color %d\n", CuteColor); + Location->color(CuteColor); + Location->redraw(); + HighlightButton::default_style->highlight_color(CuteColor); +} + +/* + * Toggle the Control Panel out of the way + */ +void UI::fullscreen_cb_i() +{ +#if 0 + // Works, but for unknown reasons it resizes hidden widgets on "Maximize" + if (Panel->visible_r()) { + Panel->hide(); + Status->hide(); + // Scale the viewport + Main->resize(0, 0, TopGroup->w(), TopGroup->h()); + TopGroup->init_sizes(); + } else { + // Scale the viewport + int p_h = Panel->h(); + Main->resize(0, p_h, TopGroup->w(), TopGroup->h() - p_h - Status->h()); + Panel->show(); + Status->show(); + TopGroup->init_sizes(); + } +#else + if (Panel->w() != 0) { + Panel_h = Panel->h(); + Status_h = Status->h(); + Panel->resize(0, 0); + StatusPanel->resize(0, 0); + // Scale the viewport + Main->resize(0, 0, TopGroup->w(), TopGroup->h()); + TopGroup->init_sizes(); + } else { + Panel->resize(TopGroup->w(), Panel_h); + Main->resize(0,Panel_h,TopGroup->w(),TopGroup->h()-Panel_h-Status_h); + StatusPanel->resize(0,TopGroup->h()-Status_h, TopGroup->w(), Status_h); + TopGroup->init_sizes(); + } +#endif +} + +/* + * Set 'nw' as the main render area widget + */ +void UI::set_render_layout(Widget &nw) +{ + // BUG: replace() is not working as it should. + // In our case, replacing the rendering area leaves the vertical + // scrollbar without events. + // + // We'll use a workaround in a_UIcmd_browser_window_new() instead. + + TopGroup->replace(MainIdx, nw); + delete(Main); + Main = &nw; + //TopGroup->box(DOWN_BOX); + //TopGroup->box(BORDER_FRAME); + TopGroup->resizable(TopGroup->child(MainIdx)); +} + +/* + * Set the window title + */ +void UI::set_page_title(const char *label) +{ + char title[128]; + + snprintf(title, 128, "Dillo: %s", label); + this->copy_label(title); + this->redraw_label(); +} + +/* + * Set button sensitivity (Back/Forw/Stop) + */ +void UI::button_set_sens(UIButton btn, int sens) +{ + switch (btn) { + case UI_BACK: + (sens) ? Back->activate() : Back->deactivate(); + break; + case UI_FORW: + (sens) ? Forw->activate() : Forw->deactivate(); + break; + case UI_STOP: + (sens) ? Stop->activate() : Stop->deactivate(); + break; + default: + break; + } +} + +/* + * Paste a middle-click-selection into "Clear" button as URL + */ +void UI::paste_url() +{ + paste(*Clear, false); +} + diff --git a/src/ui.hh b/src/ui.hh new file mode 100644 index 00000000..7e3527ab --- /dev/null +++ b/src/ui.hh @@ -0,0 +1,109 @@ +#ifndef __UI_HH__ +#define __UI_HH__ + +// UI for dillo -------------------------------------------------------------- + +#include <fltk/Window.h> +#include <fltk/Widget.h> +#include <fltk/Button.h> +#include <fltk/Input.h> +#include <fltk/PackedGroup.h> +#include <fltk/Output.h> +#include <fltk/xpmImage.h> + +using namespace fltk; + +// Panel sizes +enum { P_tiny = 0, P_small, P_medium, P_large }; + +typedef enum { + UI_BACK = 0, + UI_FORW, + UI_HOME, + UI_RELOAD, + UI_SAVE, + UI_STOP, + UI_BOOK, + UI_CLEAR, + UI_SEARCH +} UIButton; + +// +// UI class definition ------------------------------------------------------- +// +class UI : public fltk::Window { + Group *TopGroup; + Button *Back, *Forw, *Home, *Reload, *Save, *Stop, *Bookmarks, + *Clear, *Search, *FullScreen, *BugMeter; + Input *Location; + Widget *PProg, *IProg; + Image *ImgLeftIns, *ImgLeftSens, *ImgRightIns, *ImgRightSens, + *ImgStopIns, *ImgStopSens, *ImgFullScreenOn, *ImgFullScreenOff, + *ImgMeterOK, *ImgMeterBug; + Group *Panel, *StatusPanel; + Widget *Main; + Output *Status; + + int MainIdx; + // Panel customization variables + int PanelSize, CuteColor, Small_Icons; + int xpos, bw, bh, fh, lh, lbl, pr_w; + + // TODO: Hack for fullscreen mode + int Panel_h, Status_h; + + PackedGroup *make_toolbar(int tw, int th); + PackedGroup *make_location(); + PackedGroup *make_progress_bars(int wide, int thin_up); + Group *make_menu(int tiny); + Group *make_panel(int ww); + + +public: + + UI(int w, int h, const char* label = 0); + ~UI() {} // TODO: implement destructor + + // To manage what events to catch and which to let pass + int handle(int event); + + const char *get_location(); + void set_location(const char *str); + void focus_location(); + void set_status(const char *str); + void set_page_prog(size_t nbytes, int cmd); + void set_img_prog(int n_img, int t_img, int cmd); + void set_bug_prog(int n_bug); + void set_render_layout(Widget &nw); + void set_page_title(const char *label); + void customize(int flags); + void button_set_sens(UIButton btn, int sens); + void paste_url(); + + // Workaround functions for a non-working replace() in FLTK2 + void set_render_layout_begin() { + TopGroup->remove(MainIdx); + TopGroup->remove(TopGroup->find(StatusPanel)); + delete(Main); + Main = NULL; + TopGroup->begin(); + } + void set_render_layout_end() { + TopGroup->resizable(TopGroup->child(MainIdx)); + Main = TopGroup->child(MainIdx); + TopGroup->add(*StatusPanel); + TopGroup->end(); + } + int panel_h() { return Panel->h(); }; + int status_h() { return Status->h(); }; + Widget *fullscreen_button() { return FullScreen; } + void fullscreen_toggle() { FullScreen->do_callback(); } + + // Hooks to method callbacks + void panel_cb_i(); + void color_change_cb_i(); + void toggle_cb_i(); + void fullscreen_cb_i(); +}; + +#endif // __UI_HH__ diff --git a/src/uicmd.cc b/src/uicmd.cc new file mode 100644 index 00000000..d5f44e6b --- /dev/null +++ b/src/uicmd.cc @@ -0,0 +1,644 @@ +/* + * File: uicmd.cc + * + * Copyright (C) 2005 Jorge Arellano Cid <jcid@dillo.org> + * + * 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 3 of the License, or + * (at your option) any later version. + */ + +// Functions/Methods for commands triggered from the UI + + +#include <stdio.h> +#include <stdarg.h> +#include <fltk/Widget.h> + +#include "dir.h" +#include "ui.hh" +#include "uicmd.hh" +#include "timeout.hh" +#include "menu.hh" +#include "dialog.hh" +#include "bookmark.h" +#include "history.h" +#include "msg.h" +#include "prefs.h" + +#include "dw/fltkviewport.hh" + +#include "nav.h" + +// Platform idependent part +using namespace dw::core; +// FLTK related +using namespace dw::fltk; + +typedef struct { + UI *ui; + BrowserWindow *bw; +} Uibw; + +/* + * Local data + */ +// A matching table for all open ui/bw pairs +// BUG: must be dynamic. +static Uibw uibws[32]; +static int uibws_num = 0, uibws_max = 32; + +static char *save_dir = NULL; + +using namespace fltk; + + +/* + * Create a new UI and its associated BrowserWindow data structure. + */ +BrowserWindow *a_UIcmd_browser_window_new(int ww, int wh) +{ + if (ww <= 0 || wh <= 0) { + // TODO: set default geometry from dillorc. + ww = 780; + wh = 580; + } + + // Create and set the UI + UI *new_ui = new UI(ww, wh, "Dillo: UI"); + new_ui->set_status("http://www.dillo.org/"); + //new_ui->set_location("http://dillo.org/"); + //new_ui->customize(12); + + // Now create the Dw render layout and viewport + FltkPlatform *platform = new FltkPlatform (); + Layout *layout = new Layout (platform); + + // BUG: This is a workaround for FLTK's non-working replace(). + new_ui->set_render_layout_begin(); + int p_h = new_ui->panel_h(); + int s_h = new_ui->status_h(); + FltkViewport *viewport = new FltkViewport (0, p_h, ww, wh-p_h-s_h); + layout->attachView (viewport); + //viewport->addGadget(new_ui->fullscreen_button()); + new_ui->set_render_layout_end(); + // This was the original code. + // Set the render_layout widget into the UI + // new_ui->set_render_layout(*viewport); + + // Now, create a new browser window structure + BrowserWindow *new_bw = a_Bw_new(ww, wh, 0); + + // Set new_bw as callback data for UI + new_ui->user_data(new_bw); + // Reference the UI from the bw + new_bw->ui = (void *)new_ui; + // Copy the layout pointer into the bw data + new_bw->render_layout = (void*)layout; + + // insert the new ui/bw pair in the table + if (uibws_num < uibws_max) { + uibws[uibws_num].ui = new_ui; + uibws[uibws_num].bw = new_bw; + uibws_num++; + } + + new_ui->show(); + + return new_bw; +} + +/* + * Close one browser window + */ +void a_UIcmd_close_bw(void *vbw) +{ + BrowserWindow *bw = (BrowserWindow *)vbw; + UI *ui = (UI*)bw->ui; + Layout *layout = (Layout*)bw->render_layout; + + MSG("a_UIcmd_close_bw\n"); + ui->destroy(); + delete(layout); + a_Bw_free(bw); +} + +/* + * Close all the browser windows + */ +void a_UIcmd_close_all_bw() +{ + BrowserWindow *bw; + + while ((bw = a_Bw_get())) + a_UIcmd_close_bw((void*)bw); +} + +/* + * Open a new URL in the given browser window. + * + * our custom "file:" URIs are normalized here too. + */ +void a_UIcmd_open_urlstr(void *vbw, const char *urlstr) +{ + char *new_urlstr; + DilloUrl *url; + int ch; + BrowserWindow *bw = (BrowserWindow*)vbw; + + if (urlstr && *urlstr) { + /* Filter URL string */ + new_urlstr = a_Url_string_strip_delimiters(urlstr); + + if (!dStrncasecmp(new_urlstr, "file:", 5)) { + /* file URI */ + ch = new_urlstr[5]; + if (!ch || ch == '.') { + url = a_Url_new(a_Dir_get_owd(), "file:", 0, 0, 0); + } else if (ch == '~') { + url = a_Url_new(dGethomedir(), "file:", 0, 0, 0); + } else { + url = a_Url_new(new_urlstr, "file:", 0, 0, 0); + } + + } else { + /* common case */ + url = a_Url_new(new_urlstr, NULL, 0, 0, 0); + } + dFree(new_urlstr); + + if (url) { + a_Nav_push(bw, url); + a_Url_free(url); + } + } + + /* let the rendered area have focus */ + //gtk_widget_grab_focus(GTK_BIN(bw->render_main_scroll)->child); +} + +/* + * Open a new URL in the given browser window + */ +void a_UIcmd_open_url_nw(BrowserWindow *bw, DilloUrl *url) +{ + a_Nav_push_nw(bw, url); +} + +/* + * Send the browser back to previous page + */ +void a_UIcmd_back(void *vbw) +{ + a_Nav_back((BrowserWindow*)vbw); +} + +/* + * Popup the navigation menu of the Back button + */ +void a_UIcmd_back_popup(void *vbw) +{ + a_Menu_history_popup((BrowserWindow*)vbw, -1); +} + +/* + * Send the browser to next page in the history list + */ +void a_UIcmd_forw(void *vbw) +{ + a_Nav_forw((BrowserWindow*)vbw); +} + +/* + * Popup the navigation menu of the Forward button + */ +void a_UIcmd_forw_popup(void *vbw) +{ + a_Menu_history_popup((BrowserWindow*)vbw, 1); +} + +/* + * Send the browser to home URL + */ +void a_UIcmd_home(void *vbw) +{ + a_Nav_home((BrowserWindow*)vbw); +} + +/* + * Reload current URL + */ +void a_UIcmd_reload(void *vbw) +{ + a_Nav_reload((BrowserWindow*)vbw); +} + +/* + * Return a suitable filename for a given URL. + */ +char *UIcmd_make_save_filename(const char *urlstr) +{ + size_t MaxLen = 64; + char *FileName, *name; + const char *dir = a_UIcmd_get_save_dir(); + + if ((name = strrchr(urlstr, '/'))) { + if (strlen(++name) > MaxLen) { + name = name + strlen(name) - MaxLen; + } + FileName = dStrconcat(dir ? dir : "", name, NULL); + } else { + FileName = dStrconcat(dir ? dir : "", urlstr, NULL); + } + return FileName; +} + +/* + * Get the default directory for saving files. + */ +const char *a_UIcmd_get_save_dir() +{ + return save_dir; +} + +/* + * Set the default directory for saving files. + */ +void a_UIcmd_set_save_dir(const char *dir) +{ + char *p; + + if (dir && (p = strrchr(dir, '/'))) { + dFree(save_dir); + // assert a trailing '/' + save_dir = dStrconcat(dir, (p[1] != 0) ? "/" : "", NULL); + } +} + +/* + * Save current URL + */ +void a_UIcmd_save(void *vbw) +{ + const char *name; + char *SuggestedName, *urlstr; + DilloUrl *url; + + // BUG: this should be set by preferences. + a_UIcmd_set_save_dir("/tmp/k/"); + + urlstr = a_UIcmd_get_location_text((BrowserWindow*)vbw); + url = a_Url_new(urlstr, NULL, 0, 0, 0); + SuggestedName = UIcmd_make_save_filename(urlstr); + name = a_Dialog_save_file("Save Page as File", NULL, SuggestedName); + MSG("a_UIcmd_save: %s\n", name); + dFree(SuggestedName); + dFree(urlstr); + + if (name) { + a_Nav_save_url((BrowserWindow*)vbw, url, name); + } + + a_Url_free(url); +} + +/* + * Stop network activity on this bw. + * The stop button was pressed: stop page (and images) downloads. + */ +void a_UIcmd_stop(void *vbw) +{ + BrowserWindow *bw = (BrowserWindow *)vbw; + + MSG("a_UIcmd_stop()\n"); + a_Bw_stop_clients(bw, BW_Root + BW_Img + Bw_Force); + a_UIcmd_set_buttons_sens(bw); +} + +/* + * Open URL with dialog chooser + */ +void a_UIcmd_open_file(void *vbw) +{ + char *name; + DilloUrl *url; + + name = a_Dialog_open_file("Open File", NULL, ""); + + if (name) { + url = a_Url_new(name, "file:", 0, 0, 0); + a_Nav_push((BrowserWindow*)vbw, url); + a_Url_free(url); + dFree(name); + } +} + +/* + * Get an URL from a dialog and open it + */ +void a_UIcmd_open_url_dialog(void *vbw) +{ + const char *urlstr; + + if ((urlstr = a_Dialog_input("Please enter a URL:"))) { + a_UIcmd_open_urlstr(vbw, urlstr); + } +} + +/* + * Returns a newly allocated string holding a search url generated from + * a string of keywords (separarated by blanks) and prefs.search_url. + * The search string is urlencoded. + */ +char *UIcmd_make_search_str(const char *str) +{ + char *keys = a_Url_encode_hex_str(str), *c = prefs.search_url; + Dstr *ds = dStr_sized_new(128); + char *search_url; + + for (; *c; c++) { + if (*c == '%') + switch(*++c) { + case 's': + dStr_append(ds, keys); break;; + case '%': + dStr_append_c(ds, '%'); break;; + case 0: + MSG_WARN("search_url ends with '%%'\n"); c--; break;; + default: + MSG_WARN("illegal specifier '%%%c' in search_url\n", *c); + } + else + dStr_append_c(ds, *c); + } + dFree(keys); + + search_url = ds->str; + dStr_free(ds, 0); + return search_url; +} + +/* + * Get a query from a dialog and open it + */ +void a_UIcmd_search_dialog(void *vbw) +{ + const char *query, *url_str; + + if ((query = a_Dialog_input("Search the Web:"))) { + url_str = UIcmd_make_search_str(query); + a_UIcmd_open_urlstr(vbw, url_str); + } +} + +/* + * Save link URL + */ +void a_UIcmd_save_link(BrowserWindow *bw, const DilloUrl *url) +{ + const char *name; + char *SuggestedName; + + // BUG: this should be set by preferences. + a_UIcmd_set_save_dir("/tmp/k/"); + + SuggestedName = UIcmd_make_save_filename(URL_STR(url)); + name = a_Dialog_save_file("Save Link as File", NULL, SuggestedName); + MSG("a_UIcmd_save_link: %s\n", name); + dFree(SuggestedName); + + if (name) { + a_Nav_save_url(bw, url, name); + } +} + +/* + * Request the bookmarks page + */ +void a_UIcmd_book(void *vbw) +{ + DilloUrl *url = a_Url_new("dpi:/bm/", NULL, 0, 0, 0); + a_Nav_push((BrowserWindow*)vbw, url); + a_Url_free(url); +} + +/* + * Add a bookmark for a certain URL + */ +void a_UIcmd_add_bookmark(BrowserWindow *bw, DilloUrl *url) +{ + a_Bookmarks_add(bw, url); +} + + +/* + * Popup the page menu + */ +void a_UIcmd_page_popup(void *vbw, DilloUrl *url, const char *bugs_txt) +{ + a_Menu_page_popup((BrowserWindow*)vbw, url, bugs_txt); +} + +/* + * Popup the link menu + */ +void a_UIcmd_link_popup(void *vbw, DilloUrl *url) +{ + a_Menu_link_popup((BrowserWindow*)vbw, url); +} + +/* + * Show a text window with the URL's source + */ +void a_UIcmd_view_page_source(DilloUrl *url) +{ + char *buf; + int buf_size; + + if (a_Nav_get_buf(url, &buf, &buf_size)) { + a_Dialog_text_window(buf, "View Page source"); + } +} + +/* + * Show a text window with the URL's source + */ +void a_UIcmd_view_page_bugs(void *vbw) +{ + BrowserWindow *bw = (BrowserWindow*)vbw; + + if (bw->num_page_bugs > 0) { + a_Dialog_text_window(bw->page_bugs->str, "Detected HTML errors"); + } else { + a_Dialog_msg("Zero detected HTML errors!"); + } +} + +/* + * Popup the bug meter menu + */ +void a_UIcmd_bugmeter_popup(void *vbw) +{ + BrowserWindow *bw = (BrowserWindow*)vbw; + + a_Menu_bugmeter_popup(bw, a_History_get_url(NAV_TOP(bw))); +} + +/* + * Make a list of URL indexes for the history popup + * based on direction (-1 = back, 1 = forward) + */ +int *a_UIcmd_get_history(BrowserWindow *bw, int direction) +{ + int i, j, n; + int *hlist; + + // Count the number of URLs + i = a_Nav_stack_ptr(bw) + direction; + for (n = 0 ; i >= 0 && i < a_Nav_stack_size(bw); i+=direction) + ++n; + hlist = dNew(int, n + 1); + + // Fill the list + i = a_Nav_stack_ptr(bw) + direction; + for (j = 0 ; i >= 0 && i < a_Nav_stack_size(bw); i+=direction, j += 1) { + hlist[j] = NAV_IDX(bw,i); + } + hlist[j] = -1; + + return hlist; +} + +/* + * Jump to a certain URL in the navigation stack. + */ +void a_UIcmd_nav_jump(BrowserWindow *bw, int offset, int new_bw) +{ + a_Nav_jump(bw, offset, new_bw); +} + +// UI binding functions ------------------------------------------------------- + +#define BW2UI(bw) ((UI*)(bw->ui)) + +/* + * Return browser window width and height + */ +void a_UIcmd_get_wh(BrowserWindow *bw, int *w, int *h) +{ + *w = BW2UI(bw)->w(); + *h = BW2UI(bw)->h(); + _MSG("a_UIcmd_wh: w=%d, h=%d\n", *w, *h); +} + +/* + * Get location's text + */ +char *a_UIcmd_get_location_text(BrowserWindow *bw) +{ + return dStrdup(BW2UI(bw)->get_location()); +} + +/* + * Set location's text + */ +void a_UIcmd_set_location_text(void *vbw, const char *text) +{ + BrowserWindow *bw = (BrowserWindow*)vbw; + BW2UI(bw)->set_location(text); +} + +/* + * Set the page progress bar + * cmd: 0 Deactivate, 1 Update, 2 Clear + */ +void a_UIcmd_set_page_prog(BrowserWindow *bw, size_t nbytes, int cmd) +{ + BW2UI(bw)->set_page_prog(nbytes, cmd); +} + +/* + * Set the images progress bar + * cmd: 0 Deactivate, 1 Update, 2 Clear + */ +void a_UIcmd_set_img_prog(BrowserWindow *bw, int n_img, int t_img, int cmd) +{ + BW2UI(bw)->set_img_prog(n_img, t_img, cmd); +} + +/* + * Set the bug meter progress label + */ +void a_UIcmd_set_bug_prog(BrowserWindow *bw, int n_bug) +{ + BW2UI(bw)->set_bug_prog(n_bug); +} + +/* + * Set the page title. + * now it goes to the window titlebar (maybe to TAB label in the future). + */ +void a_UIcmd_set_page_title(BrowserWindow *bw, const char *label) +{ + BW2UI(bw)->set_page_title(label); +} + +/* + * Set a printf-like status string on the bottom of the dillo window. + * Beware: The safe way to set an arbitrary string is + * a_UIcmd_set_msg(bw, "%s", str) + */ +void a_UIcmd_set_msg(BrowserWindow *bw, const char *format, ...) +{ + va_list argp; + Dstr *ds = dStr_sized_new(128); + va_start(argp, format); + dStr_vsprintf(ds, format, argp); + va_end(argp); + BW2UI(bw)->set_status(ds->str); + dStr_free(ds, 1); +} + +/* + * Set the sensitivity of back/forw/stop buttons. + */ +static void a_UIcmd_set_buttons_sens_cb(void *vbw) +{ + int sens; + BrowserWindow *bw = (BrowserWindow*)vbw; + + // Stop + sens = (dList_length(bw->ImageClients) || dList_length(bw->RootClients)); + BW2UI(bw)->button_set_sens(UI_STOP, sens); + // Back + sens = (a_Nav_stack_ptr(bw) > 0); + BW2UI(bw)->button_set_sens(UI_BACK, sens); + // Forward + sens = (a_Nav_stack_ptr(bw) < a_Nav_stack_size(bw) - 1 && + !bw->nav_expecting); + BW2UI(bw)->button_set_sens(UI_FORW, sens); + + bw->sens_idle_up = 0; +} + + +/* + * Set the timeout function for button sensitivity + */ +void a_UIcmd_set_buttons_sens(BrowserWindow *bw) +{ + if (bw->sens_idle_up == 0) { + a_Timeout_add(0.0, a_UIcmd_set_buttons_sens_cb, bw); + bw->sens_idle_up = 1; + } +} + +/* + * Toggle control panel (aka. fullscreen) + */ +void a_UIcmd_fullscreen_toggle(BrowserWindow *bw) +{ + BW2UI(bw)->fullscreen_toggle(); +} + diff --git a/src/uicmd.hh b/src/uicmd.hh new file mode 100644 index 00000000..8ebbb086 --- /dev/null +++ b/src/uicmd.hh @@ -0,0 +1,62 @@ +#ifndef __UICMD_HH__ +#define __UICMD_HH__ + +#include "bw.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +BrowserWindow *a_UIcmd_browser_window_new(int ww, int wh); +void a_UIcmd_open_urlstr(void *vbw, const char *urlstr); +void a_UIcmd_open_url_nw(BrowserWindow *bw, DilloUrl *url); +void a_UIcmd_back(void *vbw); +void a_UIcmd_back_popup(void *vbw); +void a_UIcmd_forw(void *vbw); +void a_UIcmd_forw_popup(void *vbw); +void a_UIcmd_home(void *vbw); +void a_UIcmd_reload(void *vbw); +void a_UIcmd_save(void *vbw); +void a_UIcmd_stop(void *vbw); +void a_UIcmd_save_link(BrowserWindow *bw, const DilloUrl *url); +void a_UIcmd_open_file(void *vbw); +void a_UIcmd_open_url_dialog(void *vbw); +void a_UIcmd_search_dialog(void *vbw); +void a_UIcmd_book(void *vbw); +void a_UIcmd_add_bookmark(BrowserWindow *bw, DilloUrl *url); +void a_UIcmd_fullscreen_toggle(BrowserWindow *bw); +void a_UIcmd_page_popup(void *vbw, DilloUrl *url, const char *bugs_txt); +void a_UIcmd_link_popup(void *vbw, DilloUrl *url); +void a_UIcmd_view_page_source(DilloUrl *url); +void a_UIcmd_view_page_bugs(void *vbw); +void a_UIcmd_bugmeter_popup(void *vbw); +int *a_UIcmd_get_history(BrowserWindow *bw, int direction); +void a_UIcmd_nav_jump(BrowserWindow *bw, int offset, int new_bw); + +void a_UIcmd_close_bw(void *vbw); +void a_UIcmd_close_all_bw(); + +const char *a_UIcmd_get_save_dir(); +void a_UIcmd_set_save_dir(const char *dir); + + +// UI binding functions ------------------------------------------------------- + +void a_UIcmd_get_wh(BrowserWindow *bw, int *w, int *h); +char *a_UIcmd_get_location_text(BrowserWindow *bw); +void a_UIcmd_set_location_text(void *vbw, const char *text); +void a_UIcmd_set_page_prog(BrowserWindow *bw, size_t nbytes, int cmd); +void a_UIcmd_set_img_prog(BrowserWindow *bw, int n_img, int t_img, int cmd); +void a_UIcmd_set_bug_prog(BrowserWindow *bw, int n_bug); +void a_UIcmd_set_page_title(BrowserWindow *bw, const char *label); +void a_UIcmd_set_msg(BrowserWindow *bw, const char *format, ...); +void a_UIcmd_set_buttons_sens(BrowserWindow *bw); +void a_UIcmd_fullscreen_toggle(BrowserWindow *bw); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif // __UICMD_HH__ diff --git a/src/url.c b/src/url.c new file mode 100644 index 00000000..6e1805bf --- /dev/null +++ b/src/url.c @@ -0,0 +1,632 @@ +/* + * File: url.c + * + * Copyright (C) 2001 Jorge Arellano Cid <jcid@dillo.org> + * 2001 Livio Baldini Soares <livio@linux.ime.usp.br> + * + * 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 3 of the License, or + * (at your option) any later version. + */ + +/* + * Parse and normalize all URL's inside Dillo. + * - <scheme> <authority> <path> <query> and <fragment> point to 'buffer'. + * - 'url_string' is built upon demand (transparent to the caller). + * - 'hostname' and 'port' are also being handled on demand. + */ + +/* + * Regular Expression as given in RFC2396 for URL parsing. + * + * ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))? + * 12 3 4 5 6 7 8 9 + * + * scheme = $2 + * authority = $4 + * path = $5 + * query = $7 + * fragment = $9 + * + * + * RFC-2396 BNF: + * + * absoluteURI = scheme ":" (hier_part | opaque_part) + * hier_part = (net_path | abs_path) ["?" query] + * net_path = "//" authority[abs_path] + * abs_path = "/" path_segments + * + * Notes: + * - "undefined" means "preceeding separator does not appear". + * - path is never "undefined" though it may be "empty". + */ + + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> + +#include "url.h" + +//#define DEBUG_LEVEL 2 +#include "debug.h" + + +/* + * Return the url as a string. + * (initializing 'url_string' camp if necessary) + */ +char *a_Url_str(const DilloUrl *u) +{ + /* Internal url handling IS transparent to the caller */ + DilloUrl *url = (DilloUrl *) u; + + dReturn_val_if_fail (url != NULL, NULL); + + if (!url->url_string) { + url->url_string = dStr_sized_new(60); + dStr_sprintf( + url->url_string, "%s%s%s%s%s%s%s%s%s%s", + url->scheme ? url->scheme : "", + url->scheme ? ":" : "", + url->authority ? "//" : "", + url->authority ? url->authority : "", + // (url->path && url->path[0] != '/' && url->authority) ? "/" : "", + (url->authority && (!url->path || *url->path != '/')) ? "/" : "", + url->path ? url->path : "", + url->query ? "?" : "", + url->query ? url->query : "", + url->fragment ? "#" : "", + url->fragment ? url->fragment : ""); + } + + return url->url_string->str; +} + +/* + * Return the hostname as a string. + * (initializing 'hostname' and 'port' camps if necessary) + * Note: a similar approach can be taken for user:password auth. + */ +const char *a_Url_hostname(const DilloUrl *u) +{ + char *p; + /* Internal url handling IS transparent to the caller */ + DilloUrl *url = (DilloUrl *) u; + + if (!url->hostname && url->authority) { + if ((p = strchr(url->authority, ':'))) { + url->port = strtol(p + 1, NULL, 10); + url->hostname = dStrndup(url->authority,(uint_t)(p - url->authority)); + } else + url->hostname = url->authority; + } + + return url->hostname; +} + +/* + * Create a DilloUrl object and initialize it. + * (buffer, scheme, authority, path, query and fragment). + */ +static DilloUrl *Url_object_new(const char *uri_str) +{ + DilloUrl *url; + char *s, *p; + + dReturn_val_if_fail (uri_str != NULL, NULL); + + url = dNew0(DilloUrl, 1); + + /* remove leading & trailing space from buffer */ + url->buffer = dStrstrip(dStrdup(uri_str)); + + s = (char *) url->buffer; + p = strpbrk(s, ":/?#"); + if (p && p[0] == ':' && p > s) { /* scheme */ + *p = 0; + url->scheme = s; + s = ++p; + } + /* p = strpbrk(s, "/"); */ + if (p == s && p[0] == '/' && p[1] == '/') { /* authority */ + s = p + 2; + p = strpbrk(s, "/?#"); + if (p) { + memmove(s - 2, s, (size_t)MAX(p - s, 1)); + url->authority = s - 2; + p[-2] = 0; + s = p; + } else if (*s) { + url->authority = s; + return url; + } + } + + p = strpbrk(s, "?#"); + if (p) { /* path */ + url->path = (p > s) ? s : NULL; + s = p; + } else if (*s) { + url->path = s; + return url; + } + + p = strpbrk(s, "?#"); + if (p && p[0] == '?') { /* query */ + *p = 0; + s = p + 1; + url->query = s; + p = strpbrk(s, "#"); + } + if (p && p[0] == '#') { /* fragment */ + *p = 0; + s = p + 1; + url->fragment = s; + } + + return url; +} + +/* + * Free a DilloUrl + */ +void a_Url_free(DilloUrl *url) +{ + if (url) { + if (url->url_string) + dStr_free(url->url_string, TRUE); + if (url->hostname != url->authority) + dFree((char *)url->hostname); + dFree((char *)url->buffer); + dFree((char *)url->data); + dFree((char *)url->alt); + dFree(url); + } +} + +/* + * Resolve the URL as RFC2396 suggests. + */ +static Dstr *Url_resolve_relative(const char *RelStr, + DilloUrl *BaseUrlPar, + const char *BaseStr) +{ + char *p, *s, *e; + int i; + Dstr *SolvedUrl, *Path; + DilloUrl *RelUrl, *BaseUrl = NULL; + + /* parse relative URL */ + RelUrl = Url_object_new(RelStr); + + if (BaseUrlPar) { + BaseUrl = BaseUrlPar; + } else if (RelUrl->scheme == NULL) { + /* only required when there's no <scheme> in RelStr */ + BaseUrl = Url_object_new(BaseStr); + } + + SolvedUrl = dStr_sized_new(64); + Path = dStr_sized_new(64); + + /* path empty && scheme, authority and query undefined */ + if (!RelUrl->path && !RelUrl->scheme && + !RelUrl->authority && !RelUrl->query) { + dStr_append(SolvedUrl, BaseStr); + + if (RelUrl->fragment) { /* fragment */ + if (BaseUrl->fragment) + dStr_truncate(SolvedUrl, BaseUrl->fragment-BaseUrl->buffer-1); + dStr_append_c(SolvedUrl, '#'); + dStr_append(SolvedUrl, RelUrl->fragment); + } + goto done; + + } else if (RelUrl->scheme) { /* scheme */ + dStr_append(SolvedUrl, RelStr); + goto done; + + } else if (RelUrl->authority) { /* authority */ + // Set the Path buffer and goto "STEP 7"; + if (RelUrl->path) + dStr_append(Path, RelUrl->path); + + } else if (RelUrl->path && RelUrl->path[0] == '/') { /* path */ + dStr_append(Path, RelUrl->path); + + } else { + // solve relative path + if (BaseUrl->path) { + dStr_append(Path, BaseUrl->path); + for (i = Path->len; --i >= 0 && Path->str[i] != '/'; ); + if (Path->str[i] == '/') + dStr_truncate(Path, ++i); + } + if (RelUrl->path) + dStr_append(Path, RelUrl->path); + + // erase "./" + while ((p=strstr(Path->str, "./")) && + (p == Path->str || p[-1] == '/')) + dStr_erase(Path, p - Path->str, 2); + // erase last "." + if (Path->len && Path->str[Path->len - 1] == '.' && + (Path->len == 1 || Path->str[Path->len - 2] == '/')) + dStr_truncate(Path, Path->len - 1); + + // erase "<segment>/../" and "<segment>/.." + s = p = Path->str; + while ( (p = strstr(p, "/..")) != NULL ) { + if ((p[3] == '/' || !p[3]) && (p - s)) { // "/../" | "/.." + + for (e = p + 3 ; p[-1] != '/' && p > s; --p); + if (p[0] != '.' || p[1] != '.' || p[2] != '/') { + dStr_erase(Path, p - Path->str, e - p + (*e != 0)); + p -= (p > Path->str); + } else + p = e; + } else + p += 3; + } + } + + /* STEP 7 + */ + + /* scheme */ + if (BaseUrl->scheme) { + dStr_append(SolvedUrl, BaseUrl->scheme); + dStr_append_c(SolvedUrl, ':'); + } + + /* authority */ + if (RelUrl->authority) { + dStr_append(SolvedUrl, "//"); + dStr_append(SolvedUrl, RelUrl->authority); + } else if (BaseUrl->authority) { + dStr_append(SolvedUrl, "//"); + dStr_append(SolvedUrl, BaseUrl->authority); + } + + /* path */ + if ((RelUrl->authority || BaseUrl->authority) && + ((Path->len == 0 && (RelUrl->query || RelUrl->fragment)) || + (Path->len && Path->str[0] != '/'))) + dStr_append_c(SolvedUrl, '/'); /* hack? */ + dStr_append(SolvedUrl, Path->str); + + /* query */ + if (RelUrl->query) { + dStr_append_c(SolvedUrl, '?'); + dStr_append(SolvedUrl, RelUrl->query); + } + + /* fragment */ + if (RelUrl->fragment) { + dStr_append_c(SolvedUrl, '#'); + dStr_append(SolvedUrl, RelUrl->fragment); + } + +done: + dStr_free(Path, TRUE); + a_Url_free(RelUrl); + if (BaseUrl != BaseUrlPar) + a_Url_free(BaseUrl); + return SolvedUrl; +} + +/* + * Transform (and resolve) an URL string into the respective DilloURL. + * If URL = "http://dillo.sf.net:8080/index.html?long#part2" + * then the resulting DilloURL should be: + * DilloURL = { + * url_string = "http://dillo.sf.net:8080/index.html?long#part2" + * scheme = "http" + * authority = "dillo.sf.net:8080: + * path = "/index.html" + * query = "long" + * fragment = "part2" + * hostname = "dillo.sf.net" + * port = 8080 + * flags = 0 + * data = NULL + * alt = NULL + * ismap_url_len = 0 + * scrolling_position = 0 + * } + * + * Return NULL if URL is badly formed. + */ +DilloUrl* a_Url_new(const char *url_str, const char *base_url, + int flags, int32_t posx, int32_t posy) +{ + DilloUrl *url; + char *urlstr = (char *)url_str; /* auxiliar variable, don't free */ + char *p, *str1 = NULL, *str2 = NULL; + Dstr *SolvedUrl; + int i, n_ic, n_ic_spc; + + dReturn_val_if_fail (url_str != NULL, NULL); + + /* Count illegal characters (0x00-0x1F, 0x7F and space) */ + n_ic = n_ic_spc = 0; + for (p = (char*)url_str; *p; p++) { + n_ic_spc += (*p == ' ') ? 1 : 0; + n_ic += (*p != ' ' && *p > 0x1F && *p != 0x7F) ? 0 : 1; + } + if (n_ic) { + /* Strip illegal characters (they could also be encoded). + * There's no standard for illegal chars; we chose to strip. */ + p = str1 = dNew(char, strlen(url_str)); /* Yes, enough memory! */ + for (i = 0; url_str[i]; ++i) + if (url_str[i] > 0x1F && url_str[i] != 0x7F && url_str[i] != ' ') + *p++ = url_str[i]; + *p = 0; + urlstr = str1; + } + + /* let's use a heuristic to set http: as default */ + if (!base_url) { + base_url = "http:"; + if (urlstr[0] != '/') { + p = strpbrk(urlstr, "/#?:"); + if (!p || *p != ':') + urlstr = str2 = dStrconcat("//", urlstr, NULL); + } else if (urlstr[1] != '/') + urlstr = str2 = dStrconcat("/", urlstr, NULL); + } + + /* Resolve the URL */ + SolvedUrl = Url_resolve_relative(urlstr, NULL, base_url); + DEBUG_MSG(2, "SolvedUrl = %s\n", SolvedUrl->str); + + /* Fill url data */ + url = Url_object_new(SolvedUrl->str); + url->url_string = SolvedUrl; + url->flags = flags; + url->scrolling_position_x = posx; + url->scrolling_position_y = posy; + url->illegal_chars = n_ic; + url->illegal_chars_spc = n_ic_spc; + + dFree(str1); + dFree(str2); + return url; +} + + +/* + * Duplicate a Url structure + */ +DilloUrl* a_Url_dup(const DilloUrl *ori) +{ + DilloUrl *url; + + url = Url_object_new(URL_STR_(ori)); + dReturn_val_if_fail (url != NULL, NULL); + + url->url_string = dStr_new(URL_STR(ori)); + url->port = ori->port; + url->flags = ori->flags; + url->data = dStrdup(ori->data); + url->alt = dStrdup(ori->alt); + url->ismap_url_len = ori->ismap_url_len; + url->scrolling_position_x = ori->scrolling_position_x; + url->scrolling_position_y = ori->scrolling_position_y; + url->illegal_chars = ori->illegal_chars; + url->illegal_chars_spc = ori->illegal_chars_spc; + + return url; +} + +/* + * Compare two Url's to check if they're the same, or which one is bigger. + * + * The fields which are compared here are: + * <scheme>, <authority>, <path>, <query> and <data> + * Other fields are left for the caller to check + * + * Return value: 0 if equal, > 0 if A > B, < 0 if A < B. + * + * Note: this function defines a sorting order different from strcmp! + */ +int a_Url_cmp(const DilloUrl *A, const DilloUrl *B) +{ + int st; + + dReturn_val_if_fail(A && B, 1); + + if (A == B || + ((st = URL_STRCAMP_I_CMP(A->authority, B->authority)) == 0 && + (st = strcmp(A->path ? A->path + (*A->path == '/') : "", + B->path ? B->path + (*B->path == '/') : "")) == 0 && + //(st = URL_STRCAMP_CMP(A->path, B->path)) == 0 && + (st = URL_STRCAMP_CMP(A->query, B->query)) == 0 && + (st = URL_STRCAMP_CMP(A->data, B->data)) == 0 && + (st = URL_STRCAMP_I_CMP(A->scheme, B->scheme) == 0))) + return 0; + return st; +} + +/* + * Set DilloUrl flags + */ +void a_Url_set_flags(DilloUrl *u, int flags) +{ + if (u) + u->flags = flags; +} + +/* + * Set DilloUrl data (like POST info, etc.) + */ +void a_Url_set_data(DilloUrl *u, char *data) +{ + if (u) { + dFree((char *)u->data); + u->data = dStrdup(data); + } +} + +/* + * Set DilloUrl alt (alternate text to the URL. Used by image maps) + */ +void a_Url_set_alt(DilloUrl *u, const char *alt) +{ + if (u) { + dFree((char *)u->alt); + u->alt = dStrdup(alt); + } +} + +/* + * Set DilloUrl scrolling position + */ +void a_Url_set_pos(DilloUrl *u, int32_t posx, int32_t posy) +{ + if (u) { + u->scrolling_position_x = posx; + u->scrolling_position_y = posy; + } +} + +/* + * Set DilloUrl ismap coordinates + * (this is optimized for not hogging the CPU) + */ +void a_Url_set_ismap_coords(DilloUrl *u, char *coord_str) +{ + dReturn_if_fail (u && coord_str); + + if (!u->ismap_url_len) { + /* Save base-url length (without coords) */ + u->ismap_url_len = URL_STR_(u) ? u->url_string->len : 0; + a_Url_set_flags(u, URL_FLAGS(u) | URL_Ismap); + } + if (u->url_string) { + dStr_truncate(u->url_string, u->ismap_url_len); + dStr_append(u->url_string, coord_str); + u->query = u->url_string->str + u->ismap_url_len + 1; + } +} + +/* + * Given an hex octet (e.g., e3, 2F, 20), return the corresponding + * character if the octet is valid, and -1 otherwise + */ +static int Url_decode_hex_octet(const char *s) +{ + int hex_value; + char *tail, hex[3]; + + if (s && (hex[0] = s[0]) && (hex[1] = s[1])) { + hex[2] = 0; + hex_value = strtol(hex, &tail, 16); + if (tail - hex == 2) + return hex_value; + } + return -1; +} + +/* + * Parse possible hexadecimal octets in the URI path. + * Returns a new allocated string. + */ +char *a_Url_decode_hex_str(const char *str) +{ + char *new_str, *dest; + int i, val; + + if (!str) + return NULL; + + /* most cases won't have hex octets */ + if (!strchr(str, '%')) + return dStrdup(str); + + dest = new_str = dNew(char, strlen(str) + 1); + + for (i = 0; str[i]; i++) { + *dest++ = (str[i] == '%' && (val = Url_decode_hex_octet(str+i+1)) >= 0) ? + i+=2, val : str[i]; + } + *dest++ = 0; + + new_str = dRealloc(new_str, sizeof(char) * (dest - new_str)); + return new_str; +} + +/* + * Urlencode 'str' + * -RL :: According to the RFC 1738, only alphanumerics, the special + * characters "$-_.+!*'(),", and reserved characters ";/?:@=&" used + * for their *reserved purposes* may be used unencoded within a URL. + * We'll escape everything but alphanumeric and "-_.*" (as lynx). --Jcid + * + * Note: the content type "application/x-www-form-urlencoded" is used: + * i.e., ' ' -> '+' and '\n' -> CR LF (see HTML 4.01, Sec. 17.13.4) + */ +char *a_Url_encode_hex_str(const char *str) +{ + static const char *verbatim = "-_.*"; + static const char *hex = "0123456789ABCDEF"; + char *newstr, *c; + + if (!str) + return NULL; + + newstr = dNew(char, 6*strlen(str)+1); + + for (c = newstr; *str; str++) + if ((isalnum(*str) && !(*str & 0x80)) || strchr(verbatim, *str)) + /* we really need isalnum for the "C" locale */ + *c++ = *str; + else if (*str == ' ') + *c++ = '+'; + else if (*str == '\n') { + *c++ = '%'; + *c++ = '0'; + *c++ = 'D'; + *c++ = '%'; + *c++ = '0'; + *c++ = 'A'; + } else { + *c++ = '%'; + *c++ = hex[(*str >> 4) & 15]; + *c++ = hex[*str & 15]; + } + *c = 0; + + return newstr; +} + + +/* + * RFC-2396 suggests this stripping when "importing" URLs from other media. + * Strip: "URL:", enclosing < >, and embedded whitespace. + * (We also strip illegal chars: 00-1F and 7F) + */ +char *a_Url_string_strip_delimiters(const char *str) +{ + char *p, *new_str, *text; + + new_str = text = dStrdup(str); + + if (new_str) { + if (strncmp(new_str, "URL:", 4) == 0) + text += 4; + if (*text == '<') + text++; + + for (p = new_str; *text; text++) + if (*text > 0x1F && *text != 0x7F && *text != ' ') + *p++ = *text; + if (p > new_str && p[-1] == '>') + --p; + *p = 0; + } + return new_str; +} diff --git a/src/url.h b/src/url.h new file mode 100644 index 00000000..5f331881 --- /dev/null +++ b/src/url.h @@ -0,0 +1,144 @@ +/* + * File : url.h - Dillo + * + * Copyright (C) 2001 Jorge Arellano Cid <jcid@dillo.org> + * 2001 Livio Baldini Soares <livio@linux.ime.usp.br> + * + * Parse and normalize all URL's inside Dillo. + */ + +#ifndef __URL_H__ +#define __URL_H__ + +#include <string.h> /* for strcmp */ +#include "d_size.h" +#include "../dlib/dlib.h" + + +#define DILLO_URL_HTTP_PORT 80 +#define DILLO_URL_HTTPS_PORT 443 +#define DILLO_URL_FTP_PORT 21 +#define DILLO_URL_MAILTO_PORT 25 +#define DILLO_URL_NEWS_PORT 119 +#define DILLO_URL_TELNET_PORT 23 +#define DILLO_URL_GOPHER_PORT 70 + + +/* + * Values for DilloUrl->flags. + * Specifies which which action to perform with an URL. + */ +#define URL_Get (1 << 0) +#define URL_Post (1 << 1) +#define URL_ISindex (1 << 2) +#define URL_Ismap (1 << 3) +#define URL_RealmAccess (1 << 4) + +#define URL_E2EReload (1 << 5) +#define URL_ReloadImages (1 << 6) +#define URL_ReloadPage (1 << 7) +#define URL_ReloadFromCache (1 << 8) + +#define URL_ReloadIncomplete (1 << 9) +#define URL_SpamSafe (1 << 10) + +/* + * Access methods to fields inside DilloURL. + * (non '_'-ended macros MUST use these for initialization sake) + */ +/* these MAY return NULL: */ +#define URL_SCHEME_(u) u->scheme +#define URL_AUTHORITY_(u) u->authority +#define URL_PATH_(u) u->path +#define URL_QUERY_(u) u->query +#define URL_FRAGMENT_(u) u->fragment +#define URL_HOST_(u) a_Url_hostname(u) +#define URL_DATA_(u) u->data +#define URL_ALT_(u) u->alt +#define URL_STR_(u) a_Url_str(u) +/* these return an integer */ +#define URL_PORT_(u) (URL_HOST(u) ? u->port : u->port) +#define URL_FLAGS_(u) u->flags +#define URL_POSX_(u) u->scrolling_position_x +#define URL_POSY_(u) u->scrolling_position_y +#define URL_ILLEGAL_CHARS_(u) url->illegal_chars +#define URL_ILLEGAL_CHARS_SPC_(u) url->illegal_chars_spc + +/* + * Access methods that always return a string: + * When the "empty" and "undefined" concepts of RFC-2396 are irrelevant to + * the caller, and a string is required, use these methods instead: + */ +#define NPTR2STR(p) ((p) ? (p) : "") +#define URL_SCHEME(u) NPTR2STR(URL_SCHEME_(u)) +#define URL_AUTHORITY(u) NPTR2STR(URL_AUTHORITY_(u)) +#define URL_PATH(u) NPTR2STR(URL_PATH_(u)) +#define URL_QUERY(u) NPTR2STR(URL_QUERY_(u)) +#define URL_FRAGMENT(u) NPTR2STR(URL_FRAGMENT_(u)) +#define URL_HOST(u) NPTR2STR(URL_HOST_(u)) +#define URL_DATA(u) NPTR2STR(URL_DATA_(u)) +#define URL_ALT(u) NPTR2STR(URL_ALT_(u)) +#define URL_STR(u) NPTR2STR(URL_STR_(u)) +#define URL_PORT(u) URL_PORT_(u) +#define URL_FLAGS(u) URL_FLAGS_(u) +#define URL_POSX(u) URL_POSX_(u) +#define URL_POSY(u) URL_POSY_(u) +#define URL_ILLEGAL_CHARS(u) URL_ILLEGAL_CHARS_(u) +#define URL_ILLEGAL_CHARS_SPC(u) URL_ILLEGAL_CHARS_SPC_(u) + + +/* URL-camp compare methods */ +#define URL_STRCAMP_CMP(s1,s2) \ + (s1) && (s2) ? strcmp(s1,s2) : !(s1) && !(s2) ? 0 : (s1) ? 1 : -1 +#define URL_STRCAMP_I_CMP(s1,s2) \ + (s1) && (s2) ? dStrcasecmp(s1,s2) : !(s1) && !(s2) ? 0 : (s1) ? 1 : -1 + + +typedef struct _DilloUrl DilloUrl; + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +struct _DilloUrl { + Dstr *url_string; + const char *buffer; + const char *scheme; // + const char *authority; // + const char *path; // These are references only + const char *query; // (no need to free them) + const char *fragment; // + const char *hostname; // + int port; + int flags; + const char *data; /* POST */ + const char *alt; /* "alt" text (used by image maps) */ + int ismap_url_len; /* Used by server side image maps */ + int32_t scrolling_position_x, /* remember position of visited urls */ + scrolling_position_y; + int illegal_chars; /* number of illegal chars */ + int illegal_chars_spc; /* number of illegal space chars */ +}; + + +DilloUrl* a_Url_new(const char *url_str, const char *base_url, + int flags, int32_t posx, int32_t posy); +void a_Url_free(DilloUrl *u); +char *a_Url_str(const DilloUrl *url); +const char *a_Url_hostname(const DilloUrl *u); +DilloUrl* a_Url_dup(const DilloUrl *u); +int a_Url_cmp(const DilloUrl *A, const DilloUrl *B); +void a_Url_set_flags(DilloUrl *u, int flags); +void a_Url_set_data(DilloUrl *u, char *data); +void a_Url_set_alt(DilloUrl *u, const char *alt); +void a_Url_set_pos(DilloUrl *u, int32_t posx, int32_t posy); +void a_Url_set_ismap_coords(DilloUrl *u, char *coord_str); +char *a_Url_decode_hex_str(const char *str); +char *a_Url_encode_hex_str(const char *str); +char *a_Url_string_strip_delimiters(const char *str); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __URL_H__ */ diff --git a/src/web.cc b/src/web.cc new file mode 100644 index 00000000..345a3263 --- /dev/null +++ b/src/web.cc @@ -0,0 +1,175 @@ +/* + * File: web.cc + * + * Copyright 2005 Jorge Arellano Cid <jcid@dillo.org> + * + * 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 3 of the License, or + * (at your option) any later version. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <math.h> /* for rint */ + +#include "msg.h" +#include "nav.h" + +#include "uicmd.hh" + +#include "IO/IO.h" +#include "IO/mime.h" + +#include "dw/core.hh" +#include "prefs.h" +#include "web.hh" + +#define DEBUG_LEVEL 5 +#include "debug.h" + +// Platform idependent part +using namespace dw::core; + + +/* + * Local data + */ +static Dlist *ValidWebs; /* Active web structures list; it holds + * pointers to DilloWeb structures. */ + +/* + * Initialize local data + */ +void a_Web_init(void) +{ + ValidWebs = dList_new(32); +} + +/* + * Given the MIME content type, and a fd to read it from, + * this function connects the proper MIME viewer to it. + * Return value: + * 1 on success (and Call and Data properly set). + * -1 for unhandled MIME types (and Call and Data untouched). + */ +int a_Web_dispatch_by_type (const char *Type, DilloWeb *Web, + CA_Callback_t *Call, void **Data) +{ + Widget *dw = NULL; + style::StyleAttrs styleAttrs; + style::Style *widgetStyle; + style::FontAttrs fontAttrs; + + DEBUG_MSG(1, "a_Web_dispatch_by_type\n"); + + dReturn_val_if_fail(Web->bw != NULL, -1); + + // get the Layout object from the bw structure. + Layout *layout = (Layout*)Web->bw->render_layout; + + if (Web->flags & WEB_RootUrl) { + /* We have RootUrl! */ + dw = (Widget*) a_Mime_set_viewer(Type, Web, Call, Data); + if (dw == NULL) + return -1; + + /* Set a style for the widget */ + fontAttrs.name = "Bitstream Charter"; + fontAttrs.size = (int) rint(12.0 * prefs.font_factor); + fontAttrs.weight = 400; + fontAttrs.style = style::FONT_STYLE_NORMAL; + + styleAttrs.initValues (); + styleAttrs.margin.setVal (5); + styleAttrs.font = style::Font::create (layout, &fontAttrs); + styleAttrs.color = style::Color::createSimple (layout, 0xff0000); + styleAttrs.backgroundColor = + style::Color::createSimple (layout, 0xdcd1ba); + widgetStyle = style::Style::create (layout, &styleAttrs); + dw->setStyle (widgetStyle); + widgetStyle->unref (); + + /* This method frees the old dw if any */ + layout->setWidget(dw); + + if (URL_POSX(Web->url) || URL_POSY(Web->url)) { + layout->scrollTo(HPOS_LEFT, VPOS_TOP, + URL_POSX(Web->url), URL_POSY(Web->url), + 0, 0); + } else { + char *pf = a_Url_decode_hex_str(URL_FRAGMENT_(Web->url)); + if (pf) { + layout->setAnchor(pf); + dFree(pf); + } + } + + /* Clear the title bar for pages without a <TITLE> tag */ + a_UIcmd_set_page_title(Web->bw, ""); + a_UIcmd_set_location_text(Web->bw, URL_STR(Web->url)); + /* Reset both progress bars */ + a_UIcmd_set_page_prog(Web->bw, 0, 2); + a_UIcmd_set_img_prog(Web->bw, 0, 0, 2); + /* Reset the bug meter */ + a_UIcmd_set_bug_prog(Web->bw, 0); + + /* Let the Nav module know... */ + a_Nav_expect_done(Web->bw); + + } else { + /* A non-RootUrl. At this moment we only handle image-children */ + if (!dStrncasecmp(Type, "image/", 6)) + dw = (Widget*) a_Mime_set_viewer(Type, Web, Call, Data); + } + + if (!dw) { + MSG_HTTP("unhandled MIME type: \"%s\"\n", Type); + } + return (dw ? 1 : -1); +} + + +/* + * Allocate and set safe values for a DilloWeb structure + */ +DilloWeb* a_Web_new(const DilloUrl *url) +{ + DilloWeb *web= dNew(DilloWeb, 1); + + _MSG(" a_Web_new: ValidWebs ==> %d\n", dList_length(ValidWebs)); + web->url = a_Url_dup(url); + web->bw = NULL; + web->flags = 0; + web->Image = NULL; + web->filename = NULL; + web->stream = NULL; + web->SavedBytes = 0; + + dList_append(ValidWebs, (void *)web); + return web; +} + +/* + * Validate a DilloWeb pointer + */ +int a_Web_valid(DilloWeb *web) +{ + return (dList_find(ValidWebs, web) != NULL); +} + +/* + * Deallocate a DilloWeb structure + */ +void a_Web_free(DilloWeb *web) +{ + if (!web) return; + if (web->url) + a_Url_free(web->url); + if (web->Image) + a_Image_unref(web->Image); + dFree(web->filename); + dList_remove(ValidWebs, (void *)web); + dFree(web); +} + diff --git a/src/web.hh b/src/web.hh new file mode 100644 index 00000000..a5e05a2f --- /dev/null +++ b/src/web.hh @@ -0,0 +1,45 @@ +#ifndef __WEB_H__ +#define __WEB_H__ + +#include <stdio.h> /* for FILE */ +#include "bw.h" /* for BrowserWindow */ +#include "cache.h" /* for CA_Callback_t */ +#include "image.hh" /* for DilloImage */ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* + * Flag defines + */ +#define WEB_RootUrl 1 +#define WEB_Image 2 +#define WEB_Download 4 /* Half implemented... */ + + +typedef struct _DilloWeb DilloWeb; + +struct _DilloWeb { + DilloUrl *url; /* Requested URL */ + BrowserWindow *bw; /* The requesting browser window [reference] */ + int flags; /* Additional info */ + + DilloImage *Image; /* For image urls [reference] */ + + char *filename; /* Variables for Local saving */ + FILE *stream; + int SavedBytes; +}; + +void a_Web_init(void); +DilloWeb* a_Web_new (const DilloUrl* url); +int a_Web_valid(DilloWeb *web); +void a_Web_free (DilloWeb*); +int a_Web_dispatch_by_type (const char *Type, DilloWeb *web, + CA_Callback_t *Call, void **Data); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* __WEB_H__ */ |