summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjcid <devnull@localhost>2007-10-07 00:36:34 +0200
committerjcid <devnull@localhost>2007-10-07 00:36:34 +0200
commit93715c46a99c96d6c866968312691ec9ab0f6a03 (patch)
tree573f19ec6aa740844f53a7c0eb7114f04096bf64
Initial revision
-rw-r--r--AUTHORS68
-rw-r--r--COPYING674
-rw-r--r--ChangeLog1416
-rw-r--r--INSTALL176
-rw-r--r--Makefile.am5
-rw-r--r--NEWS33
-rw-r--r--README76
-rw-r--r--README-port145
-rw-r--r--config.h.in106
-rw-r--r--configure.in475
-rw-r--r--dillorc2194
-rw-r--r--dlib/Makefile.am6
-rw-r--r--dlib/dlib.c750
-rw-r--r--dlib/dlib.h158
-rw-r--r--doc/Cache.txt185
-rw-r--r--doc/Cookies.txt85
-rw-r--r--doc/Dillo.txt103
-rw-r--r--doc/Dpid.txt454
-rw-r--r--doc/Dw.txt383
-rw-r--r--doc/DwImage.txt201
-rw-r--r--doc/DwPage.txt152
-rw-r--r--doc/DwRender.txt620
-rw-r--r--doc/DwStyle.txt310
-rw-r--r--doc/DwTable.txt205
-rw-r--r--doc/DwWidget.txt339
-rw-r--r--doc/HtmlParser.txt116
-rw-r--r--doc/IO.txt468
-rw-r--r--doc/Images.txt114
-rw-r--r--doc/Imgbuf.txt177
-rw-r--r--doc/Makefile.am19
-rw-r--r--doc/NC_design.txt80
-rw-r--r--doc/README53
-rw-r--r--doc/Selection.txt149
-rw-r--r--dpi/Makefile.am37
-rw-r--r--dpi/bookmarks.c1716
-rw-r--r--dpi/cookies.c1466
-rw-r--r--dpi/datauri.c323
-rw-r--r--dpi/downloads.cc1133
-rw-r--r--dpi/dpiutil.c270
-rw-r--r--dpi/dpiutil.h97
-rw-r--r--dpi/file.c975
-rw-r--r--dpi/ftp.c301
-rw-r--r--dpi/hello.c161
-rw-r--r--dpi/https.c713
-rw-r--r--dpid/Makefile.am26
-rw-r--r--dpid/TODO32
-rw-r--r--dpid/dpi.c97
-rw-r--r--dpid/dpi.h54
-rw-r--r--dpid/dpi_service.c114
-rw-r--r--dpid/dpi_service.h20
-rw-r--r--dpid/dpi_socket_dir.c128
-rw-r--r--dpid/dpi_socket_dir.h17
-rw-r--r--dpid/dpid.c734
-rw-r--r--dpid/dpid.h103
-rw-r--r--dpid/dpid_common.c43
-rw-r--r--dpid/dpid_common.h61
-rw-r--r--dpid/dpidc31
-rw-r--r--dpid/main.c398
-rw-r--r--dpid/misc_new.c172
-rw-r--r--dpid/misc_new.h12
-rw-r--r--dpip/Makefile.am5
-rw-r--r--dpip/dpip.c168
-rw-r--r--dpip/dpip.h34
-rwxr-xr-xinstall-dpi-local39
-rw-r--r--src/IO/IO.c412
-rw-r--r--src/IO/IO.h45
-rw-r--r--src/IO/Makefile.am14
-rw-r--r--src/IO/Url.h40
-rw-r--r--src/IO/about.c344
-rw-r--r--src/IO/dpi.c779
-rw-r--r--src/IO/http.c494
-rw-r--r--src/IO/iowatch.cc35
-rw-r--r--src/IO/iowatch.hh25
-rw-r--r--src/IO/mime.c152
-rw-r--r--src/IO/mime.h58
-rw-r--r--src/IO/proto.c13
-rw-r--r--src/Makefile.am87
-rw-r--r--src/binaryconst.h38
-rw-r--r--src/bitvec.c59
-rw-r--r--src/bitvec.h36
-rw-r--r--src/bookmark.c89
-rw-r--r--src/bookmark.h19
-rw-r--r--src/bw.c248
-rw-r--r--src/bw.h96
-rw-r--r--src/cache.c932
-rw-r--r--src/cache.h75
-rw-r--r--src/capi.c587
-rw-r--r--src/capi.h29
-rw-r--r--src/chain.c128
-rw-r--r--src/chain.h69
-rwxr-xr-xsrc/chg28
-rw-r--r--src/colors.c366
-rw-r--r--src/colors.h15
-rw-r--r--src/cookies.c332
-rw-r--r--src/cookies.h24
-rw-r--r--src/debug.h149
-rw-r--r--src/dialog.cc116
-rw-r--r--src/dialog.hh22
-rw-r--r--src/dicache.c451
-rw-r--r--src/dicache.h70
-rw-r--r--src/dillo.cc108
-rw-r--r--src/dir.c48
-rw-r--r--src/dir.h19
-rw-r--r--src/dns.c535
-rw-r--r--src/dns.h31
-rw-r--r--src/dpiapi.c82
-rw-r--r--src/dpiapi.h3
-rw-r--r--src/form.cc98
-rw-r--r--src/form.hh87
-rw-r--r--src/gif.c1054
-rw-r--r--src/history.c125
-rw-r--r--src/history.h24
-rw-r--r--src/html.cc5123
-rw-r--r--src/html.hh279
-rw-r--r--src/image.cc226
-rw-r--r--src/image.hh79
-rw-r--r--src/jpeg.c334
-rw-r--r--src/klist.c118
-rw-r--r--src/klist.h40
-rw-r--r--src/list.h49
-rw-r--r--src/menu.cc358
-rw-r--r--src/menu.hh34
-rw-r--r--src/misc.c271
-rw-r--r--src/misc.h25
-rw-r--r--src/msg.h42
-rw-r--r--src/nav.c427
-rw-r--r--src/nav.h40
-rw-r--r--src/pixmaps.h1652
-rw-r--r--src/plain.cc233
-rw-r--r--src/png.c472
-rw-r--r--src/prefs.c434
-rw-r--r--src/prefs.h130
-rwxr-xr-xsrc/srch33
-rw-r--r--src/timeout.cc46
-rw-r--r--src/timeout.hh20
-rw-r--r--src/ui.cc912
-rw-r--r--src/ui.hh109
-rw-r--r--src/uicmd.cc644
-rw-r--r--src/uicmd.hh62
-rw-r--r--src/url.c632
-rw-r--r--src/url.h144
-rw-r--r--src/web.cc175
-rw-r--r--src/web.hh45
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 &#xA1; (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
diff --git a/NEWS b/NEWS
new file mode 100644
index 00000000..a275571c
--- /dev/null
+++ b/NEWS
@@ -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! :-)
+
diff --git a/README b/README
new file mode 100644
index 00000000..9048612d
--- /dev/null
+++ b/README
@@ -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 '&lt;' and '&gt;' 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>&nbsp;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>&nbsp;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&nbsp;an&nbsp;operation&nbsp;</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>,&nbsp;mark&nbsp;its&nbsp;operands,&nbsp;and&nbsp;</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"
+" &nbsp;&nbsp;&nbsp;%s&nbsp;&nbsp;&nbsp;</b></font></td>\n"
+" <td bgcolor='white' width='100%%'>&nbsp;</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"
+" &nbsp;&nbsp;&nbsp;%s&nbsp;&nbsp;&nbsp;</b></font></td>\n"
+" <td bgcolor='white' width='100%%'>&nbsp;</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'>&nbsp;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&nbsp;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'>&nbsp;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'>&nbsp;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&nbsp;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 &nbsp;
+ */
+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, "&nbsp;");
+ 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&nbsp;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&nbsp;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[] =
+ { "&amp;", "&lt;", "&gt;", "&quot;", "&#39;" };
+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&nbsp;%.2s&nbsp;%.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&nbsp;%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,
+ "&nbsp;&nbsp;<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&nbsp;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>&nbsp;Welcome to Dillo " VERSION "&nbsp;</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>&nbsp;&nbsp;\n"
+" <td>\n"
+" <a href='http://www.dillo.org/'>Home</a>\n"
+" <tr>\n"
+" <td>&nbsp;&nbsp;\n"
+" <td>\n"
+" <a href='http://www.dillo.org/funding/objectives.html'>\n"
+" Objectives</a>\n"
+" <tr>\n"
+" <td>&nbsp;&nbsp;\n"
+" <td>\n"
+" <a href='http://www.dillo.org/ChangeLog.html'>\n"
+" ChangeLog</a>\n"
+" <tr>\n"
+" <td>&nbsp;&nbsp;\n"
+" <td>\n"
+" <a href='http://www.dillo.org/interview.html'>\n"
+" Interview</a>\n"
+" <tr>\n"
+" <td>&nbsp;&nbsp;\n"
+" <td>\n"
+" <a href='http://www.dillo.org/D_authors.html'>\n"
+" Authors</a>\n"
+" <tr>\n"
+" <td>&nbsp;&nbsp;\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>&nbsp;&nbsp;\n"
+" <td>\n"
+" <a href='http://lwn.net/'>LWN</a>\n"
+" <tr>\n"
+" <td>&nbsp;&nbsp;\n"
+" <td>\n"
+" <a href='http://slashdot.org/'>Slashdot</a>\n"
+" <tr>\n"
+" <td>&nbsp;&nbsp;\n"
+" <td>\n"
+" <a href='http://www.kuro5hin.org/?op=section;section=__all__'>KuroShin</a>\n"
+" <tr>\n"
+" <td>&nbsp;&nbsp;\n"
+" <td>\n"
+" <a href='http://www.nexusmagazine.com/'>Nexus&nbsp;M.</a>\n"
+" <tr>\n"
+" <td>&nbsp;&nbsp;\n"
+" <td>\n"
+" <a href='http://www.gnu-darwin.org/update.html'>Monster News</a>\n"
+" <tr>\n"
+" <td>&nbsp;&nbsp;\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>&nbsp;&nbsp;\n"
+" <td><a href='http://www.google.com/'>Google</a>\n"
+" <tr>\n"
+" <td>&nbsp;&nbsp;\n"
+" <td><a href='http://www.wikipedia.org/'>Wikipedia</a>\n"
+" <tr>\n"
+" <td>&nbsp;&nbsp;\n"
+" <td><a href='http://www.gutenberg.org/'>P. Gutenberg</a>\n"
+" <tr>\n"
+" <td>&nbsp;&nbsp;\n"
+" <td><a href='http://freshmeat.net/'>FreshMeat</a>\n"
+" <tr>\n"
+" <td>&nbsp;&nbsp;\n"
+" <td><a href='http://www.gnu.org/gnu/thegnuproject.html'>GNU\n"
+" project</a>\n"
+" <tr>\n"
+" <td>&nbsp;&nbsp;\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>&nbsp;&nbsp;\n"
+" <td><a href='http://www.violence.de'>Peace&amp;Violence</a>\n"
+" <tr><td>&nbsp;&nbsp;\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 &nbsp;) */
+ 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__ */