diff --git a/ABOUT-NLS b/ABOUT-NLS new file mode 100644 index 0000000..36bc78d --- /dev/null +++ b/ABOUT-NLS @@ -0,0 +1,285 @@ +Some of this discussion is obsolete - lynx does not bundle the "intl" +directory, and consequently the "--with-included-gettext" configure option is +not supported. +------------------------------------------------------------------------------ + +Notes on the Free Translation Project +************************************* + + Free software is going international! The Free Translation Project +is a way to get maintainers of free software, translators, and users all +together, so that will gradually become able to speak many languages. +A few packages already provide translations for their messages. + + If you found this `ABOUT-NLS' file inside a distribution, you may +assume that the distributed package does use GNU `gettext' internally, +itself available at your nearest GNU archive site. But you do *not* +need to install GNU `gettext' prior to configuring, installing or using +this package with messages translated. + + Installers will find here some useful hints. These notes also +explain how users should proceed for getting the programs to use the +available translations. They tell how people wanting to contribute and +work at translations should contact the appropriate team. + + When reporting bugs in the `intl/' directory or bugs which may be +related to internationalization, you should tell about the version of +`gettext' which is used. The information can be found in the +`intl/VERSION' file, in internationalized packages. + +One advise in advance +===================== + + If you want to exploit the full power of internationalization, you +should configure it using + + ./configure --with-included-gettext + +to force usage of internationalizing routines provided within this +package, despite the existence of internationalizing capabilities in the +operating system where this package is being installed. So far, only +the `gettext' implementation in the GNU C library version 2 provides as +many features (such as locale alias or message inheritance) as the +implementation here. It is also not possible to offer this additional +functionality on top of a `catgets' implementation. Future versions of +GNU `gettext' will very likely convey even more functionality. So it +might be a good idea to change to GNU `gettext' as soon as possible. + + So you need not provide this option if you are using GNU libc 2 or +you have installed a recent copy of the GNU gettext package with the +included `libintl'. + +INSTALL Matters +=============== + + Some packages are "localizable" when properly installed; the +programs they contain can be made to speak your own native language. +Most such packages use GNU `gettext'. Other packages have their own +ways to internationalization, predating GNU `gettext'. + + By default, this package will be installed to allow translation of +messages. It will automatically detect whether the system provides +usable `catgets' (if using this is selected by the installer) or +`gettext' functions. If neither is available, the GNU `gettext' own +library will be used. This library is wholly contained within this +package, usually in the `intl/' subdirectory, so prior installation of +the GNU `gettext' package is *not* required. Installers may use +special options at configuration time for changing the default +behaviour. The commands: + + ./configure --with-included-gettext + ./configure --with-catgets + ./configure --disable-nls + +will respectively bypass any pre-existing `catgets' or `gettext' to use +the internationalizing routines provided within this package, enable +the use of the `catgets' functions (if found on the locale system), or +else, *totally* disable translation of messages. + + When you already have GNU `gettext' installed on your system and run +configure without an option for your new package, `configure' will +probably detect the previously built and installed `libintl.a' file and +will decide to use this. This might be not what is desirable. You +should use the more recent version of the GNU `gettext' library. I.e. +if the file `intl/VERSION' shows that the library which comes with this +package is more recent, you should use + + ./configure --with-included-gettext + +to prevent auto-detection. + + By default the configuration process will not test for the `catgets' +function and therefore they will not be used. The reasons are already +given above: the emulation on top of `catgets' cannot provide all the +extensions provided by the GNU `gettext' library. If you nevertheless +want to use the `catgets' functions use + + ./configure --with-catgets + +to enable the test for `catgets' (this causes no harm if `catgets' is +not available on your system). If you really select this option we +would like to hear about the reasons because we cannot think of any +good one ourself. + + Internationalized packages have usually many `po/LL.po' files, where +LL gives an ISO 639 two-letter code identifying the language. Unless +translations have been forbidden at `configure' time by using the +`--disable-nls' switch, all available translations are installed +together with the package. However, the environment variable `LINGUAS' +may be set, prior to configuration, to limit the installed set. +`LINGUAS' should then contain a space separated list of two-letter +codes, stating which languages are allowed. + +Using This Package +================== + + As a user, if your language has been installed for this package, you +only have to set the `LANG' environment variable to the appropriate +ISO 639 `LL' two-letter code prior to using the programs in the +package. For example, let's suppose that you speak German. At the +shell prompt, merely execute `setenv LANG de' (in `csh'), +`export LANG; LANG=de' (in `sh') or `export LANG=de' (in `bash'). This +can be done from your `.login' or `.profile' file, once and for all. + + An operating system might already offer message localization for +many of its programs, while other programs have been installed locally +with the full capabilities of GNU `gettext'. Just using `gettext' +extended syntax for `LANG' would break proper localization of already +available operating system programs. In this case, users should set +both `LANGUAGE' and `LANG' variables in their environment, as programs +using GNU `gettext' give preference to `LANGUAGE'. For example, some +Swedish users would rather read translations in German than English for +when Swedish is not available. This is easily accomplished by setting +`LANGUAGE' to `sv:de' while leaving `LANG' to `sv'. + +Translating Teams +================= + + For the Free Translation Project to be a success, we need interested +people who like their own language and write it well, and who are also +able to synergize with other translators speaking the same language. +Each translation team has its own mailing list, courtesy of Linux +International. You may reach your translation team at the address +`LL@li.org', replacing LL by the two-letter ISO 639 code for your +language. Language codes are *not* the same as the country codes given +in ISO 3166. The following translation teams exist, as of August 1998: + + Chinese `zh', Czech `cs', Danish `da', Dutch `nl', English `en', + Esperanto `eo', Finnish `fi', French `fr', German `de', Hungarian + `hu', Irish `ga', Italian `it', Indonesian `id', Japanese `ja', + Korean `ko', Latin `la', Norwegian `no', Persian `fa', Polish + `pl', Portuguese `pt', Russian `ru', Slovenian `sl', Spanish `es', + Swedish `sv', and Turkish `tr'. + +For example, you may reach the Chinese translation team by writing to +`zh@li.org'. + + If you'd like to volunteer to *work* at translating messages, you +should become a member of the translating team for your own language. +The subscribing address is *not* the same as the list itself, it has +`-request' appended. For example, speakers of Swedish can send a +message to `sv-request@li.org', having this message body: + + subscribe + + Keep in mind that team members are expected to participate +*actively* in translations, or at solving translational difficulties, +rather than merely lurking around. If your team does not exist yet and +you want to start one, or if you are unsure about what to do or how to +get started, please write to `translation@iro.umontreal.ca' to reach the +coordinator for all translator teams. + + The English team is special. It works at improving and uniformizing +the terminology in use. Proven linguistic skill are praised more than +programming skill, here. + +Available Packages +================== + + Languages are not equally supported in all packages. The following +matrix shows the current state of internationalization, as of August +1998. The matrix shows, in regard of each package, for which languages +PO files have been submitted to translation coordination. + + Ready PO files cs da de el en es fi fr it + .----------------------------. + bash | [] [] | + bison | [] [] | + clisp | [] [] [] [] | + cpio | [] [] [] | + diffutils | [] [] [] | + enscript | [] [] [] [] | + fileutils | [] [] [] [] | + findutils | [] [] [] [] | + flex | [] [] | + gcal | [] [] | + gettext | [] [] [] [] [] | + grep | [] [] [] [] | + hello | [] [] [] [] [] | + id-utils | [] [] | + indent | [] [] | + libc | [] [] [] | + m4 | [] [] | + make | [] [] [] | + music | [] | + ptx | [] [] [] | + recode | [] [] [] [] | + sed | | + sh-utils | [] [] [] | + sharutils | [] [] [] [] [] | + tar | [] [] [] [] | + texinfo | [] [] [] | + textutils | [] [] [] [] | + wdiff | [] [] [] [] | + wget | [] [] [] [] | + `----------------------------' + cs da de el en es fi fr it + 7 4 26 4 1 18 1 26 4 + + ja ko nl no pl pt ru sl sv + .----------------------------. + bash | [] | 3 + bison | [] | 3 + clisp | | 4 + cpio | [] [] [] | 6 + diffutils | [] [] | 5 + enscript | [] [] | 6 + fileutils | [] [] [] [] [] [] [] | 11 + findutils | [] [] [] [] [] | 9 + flex | [] [] | 4 + gcal | [] [] [] | 5 + gettext | [] [] [] [] [] [] [] | 13 + grep | [] [] [] [] [] [] [] | 11 + hello | [] [] [] [] [] [] [] | 12 + id-utils | [] | 3 + indent | [] [] [] | 5 + libc | [] [] [] [] [] | 8 + m4 | [] [] [] [] | 6 + make | [] [] [] | 6 + music | [] | 2 + ptx | [] [] [] [] [] | 8 + recode | [] [] [] [] [] | 9 + sed | | 0 + sh-utils | [] [] [] [] [] | 8 + sharutils | [] [] | 7 + tar | [] [] [] [] [] [] [] | 11 + texinfo | [] | 4 + textutils | [] [] [] [] [] | 9 + wdiff | [] [] [] [] | 8 + wget | [] | 5 + `----------------------------' + 18 teams ja ko nl no pl pt ru sl sv + 29 domains 1 12 21 11 19 7 5 7 17 191 + + Some counters in the preceding matrix are higher than the number of +visible blocks let us expect. This is because a few extra PO files are +used for implementing regional variants of languages, or language +dialects. + + For a PO file in the matrix above to be effective, the package to +which it applies should also have been internationalized and +distributed as such by its maintainer. There might be an observable +lag between the mere existence a PO file and its wide availability in a +distribution. + + If August 1998 seems to be old, you may fetch a more recent copy of +this `ABOUT-NLS' file on most GNU archive sites. + +Using `gettext' in new packages +=============================== + + If you are writing a freely available program and want to +internationalize it you are welcome to use GNU `gettext' in your +package. Of course the GNU General Public License applies to your +sources from then if you include `gettext' directly in your distribution +on but since you are writing free software anyway this is no +restriction. + + Once the sources are change appropriately and the setup can handle to +use of `gettext' the only thing missing are the translations. The Free +Translation Project is also available for packages which are not +developed inside the GNU project. Therefore the information given above +applies also for every other Free Software Project. Contact +`translation@iro.umontreal.ca' to make the `.pot' files available to +the translation teams. + diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..99a8967 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,56 @@ +-- $LynxId: AUTHORS,v 1.10 2019/01/10 22:36:37 tom Exp $ +-- vile:txtmode +Most of the people who have contributed more than one patch to Lynx (as well as +a few who have only one) are noted in the changelogs by their initials (to keep +the changelog short). Here is a summary of those initials and the +corresponding full names: + +AAC Andrey A Chernov +AJL Alex J Lyons +BD Binh Do +BJP Brian J Pardy +BL Bela Lubkin +CK Charles Karney +DK Doug Kaufman +DSB Scott Bigham +DW David Woolley +FLWM Frederic L W Meunier +FM Foteos Macrides +GN Glenn Nielsen +GV Gisle Vanem +HL Hiram Lester Jr +HM Hynek Med +HN Henry Nelson +IC Ismael Cordeiro +IZ Ilya Zakharevich +JB John Bley +JED John E Davis +JES James E Spath +JKT J Kevin Ternes +JN John Nowlin +KED Kim DeVaughn +KH Kihara Hideto +KW Klaus Weide +LE Laura Eaves +LP Leonid Pauzner +LV Larry W Virden +NSH nsh@horae.dti.ne.jp +PBM Paul B Mahol +PC Peter Canning +PDS Paul D Smith +PG Paul Gilmartin +PHDM Philippe De Muyter +PW Philip Webb +RN Ryan Nielsen +RP Robert J Partington +RS Rado Smiljanic +SC Stefan Caunter +SH Hiroyuki Senshu +SKY Sinan Kaan Yerli +SS Sergey Svishchev +TD Thomas E Dickey +TG Thorsten Glaser +TH Hataguchi Takeshi (patakuti) +VH Vlad Harchev +WB Wayne Buttles +WS Bill Schiavo diff --git a/BUILD/README b/BUILD/README new file mode 100644 index 0000000..0821cec --- /dev/null +++ b/BUILD/README @@ -0,0 +1,4 @@ +-- $LynxId: README,v 1.1 2018/03/18 20:42:49 tom Exp $ +Lynx is usually built on the command-line, e.g., using make-msc.bat +However, IDEs are occasionally useful for debugging. Here are project +files used for different versions of Visual Studio. diff --git a/BUILD/VS2003/clean.bat b/BUILD/VS2003/clean.bat new file mode 100644 index 0000000..5920e4a --- /dev/null +++ b/BUILD/VS2003/clean.bat @@ -0,0 +1,27 @@ +@echo off +@rem $LynxId: clean.bat,v 1.1 2007/06/30 14:17:02 tom Exp $ +@rem Remove all build-products in subdirectories, leaving only sources (and unrecognized types) + +del/f/s/q *.aps +del/f/s/q *.bsc +del/f/s/q *.exe +del/f/s/q *.exp +del/f/s/q *.idb +del/f/s/q *.ilk +del/f/s/q *.lib +del/f/s/q *.ncb +del/f/s/q *.obj +del/f/s/q *.opt +del/f/s/q *.pch +del/f/s/q *.pdb +del/f/s/q *.plg +del/f/s/q *.res +del/f/s/q *.sbr +del/f/s/q *.suo + +del/f/s/q BuildLog.htm + +attrib +r *.h /s +attrib +r *.bat /s +attrib +r *.sln /s +attrib +r *.vcproj /s diff --git a/BUILD/VS2003/develop.bat b/BUILD/VS2003/develop.bat new file mode 100644 index 0000000..93509a6 --- /dev/null +++ b/BUILD/VS2003/develop.bat @@ -0,0 +1,7 @@ +@echo off +@rem $LynxId: develop.bat,v 1.2 2007/06/29 00:22:25 tom Exp $ +@rem ensure that all IDE files are writable + +attrib -r *.bat /s +attrib -r *.sln /s +attrib -r *.vcproj /s \ No newline at end of file diff --git a/BUILD/VS2003/lynx/lynx.sln b/BUILD/VS2003/lynx/lynx.sln new file mode 100644 index 0000000..68ee500 --- /dev/null +++ b/BUILD/VS2003/lynx/lynx.sln @@ -0,0 +1,30 @@ +Microsoft Visual Studio Solution File, Format Version 8.00 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "lynx", "lynx.vcproj", "{0CC852D4-5C5A-475B-9BBB-41A33309B0E5}" + ProjectSection(ProjectDependencies) = postProject + {C6C72FCE-5049-4D45-A7B2-586A80B6CC20} = {C6C72FCE-5049-4D45-A7B2-586A80B6CC20} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "makeuctb", "..\makeuctb\makeuctb.vcproj", "{C6C72FCE-5049-4D45-A7B2-586A80B6CC20}" + ProjectSection(ProjectDependencies) = postProject + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfiguration) = preSolution + Debug = Debug + Release = Release + EndGlobalSection + GlobalSection(ProjectConfiguration) = postSolution + {0CC852D4-5C5A-475B-9BBB-41A33309B0E5}.Debug.ActiveCfg = Debug|Win32 + {0CC852D4-5C5A-475B-9BBB-41A33309B0E5}.Debug.Build.0 = Debug|Win32 + {0CC852D4-5C5A-475B-9BBB-41A33309B0E5}.Release.ActiveCfg = Release|Win32 + {0CC852D4-5C5A-475B-9BBB-41A33309B0E5}.Release.Build.0 = Release|Win32 + {C6C72FCE-5049-4D45-A7B2-586A80B6CC20}.Debug.ActiveCfg = Debug|Win32 + {C6C72FCE-5049-4D45-A7B2-586A80B6CC20}.Debug.Build.0 = Debug|Win32 + {C6C72FCE-5049-4D45-A7B2-586A80B6CC20}.Release.ActiveCfg = Release|Win32 + {C6C72FCE-5049-4D45-A7B2-586A80B6CC20}.Release.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + EndGlobalSection + GlobalSection(ExtensibilityAddIns) = postSolution + EndGlobalSection +EndGlobal diff --git a/BUILD/VS2003/lynx/lynx.vcproj b/BUILD/VS2003/lynx/lynx.vcproj new file mode 100644 index 0000000..a75648f --- /dev/null +++ b/BUILD/VS2003/lynx/lynx.vcproj @@ -0,0 +1,708 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/BUILD/VS2003/lynx/lynx_cfg.h b/BUILD/VS2003/lynx/lynx_cfg.h new file mode 100644 index 0000000..2861e1f --- /dev/null +++ b/BUILD/VS2003/lynx/lynx_cfg.h @@ -0,0 +1,68 @@ +// $LynxId: lynx_cfg.h,v 1.6 2011/05/28 13:07:55 tom Exp $ +// definitions abstracted from makefile.msc + +#ifndef LYNX_CFG_H +#define LYNX_CFG_H 1 + +#define __WIN32__ +#define _WINDOWS +#define _WIN32_WINNT 0x0400 + +#define ACCESS_AUTH 1 +#define CJK_EX 1 +#define DIRED_SUPPORT 1 +#define DISP_PARTIAL 1 +#define DOSPATH 1 +#define USE_ALT_BINDINGS 1 +#define EXP_NESTED_TABLES 1 +#define HAVE_KEYPAD 1 +#define HAVE_PUTENV 1 +#define LONG_LIST 1 +#define NDEBUG 1 +#define NOSIGHUP 1 +#define NOUSERS 1 +#define NO_CONFIG_INFO 1 +#define NO_CUSERID 1 +#define NO_FILIO_H 1 +#define NO_TTYTYPE 1 +#define NO_UNISTD_H 1 +#define NO_UTMP 1 +#define OK_OVERRIDE 1 +#define SH_EX 1 +#define USE_CMD_LOGGING 1 +#define USE_EXTERNALS 1 +#define USE_FILE_UPLOAD 1 +#define USE_JUSTIFY_ELTS 1 +#define USE_MULTIBYTE_CURSES 1 +#define USE_PERSISTENT_COOKIES 1 +#define USE_PRETTYSRC 1 +#define USE_READPROGRESS 1 +#define USE_SCROLLBAR 1 +#define USE_SOURCE_CACHE 1 +#define USE_ZLIB 1 +#define WIN_EX 1 + +// definitions to account for using this file (see HTUtils.h, userdefs.h) +#define ANSI_VARARGS 1 +#define HAVE_GETCWD 1 +#define HAVE_STDARG_H 1 +#define HAVE_STDLIB_H 1 +#define LYNX_CFG_FILE "./lynx.cfg" +#define UNDERLINE_LINKS FALSE +#define socklen_t int + +// configuration choices +#define PDCURSES 1 +#define USE_WINSOCK2_H 1 + +#ifdef PDCURSES +#define USE_COLOR_STYLE 1 +#define COLOR_CURSES 1 +#define FANCY_CURSES 1 +#endif + +#pragma warning (disable : 4244) /* conversion from 'xxx' to 'yyy', possible loss of data */ +#pragma warning (disable : 4267) /* conversion from 'xxx' to 'yyy', possible loss of data */ +#pragma warning (disable : 4311) /* 'type cast': pointer truncation from 'xxx' to 'yyy' FIXME */ + +#endif /* LYNX_CFG_H */ diff --git a/BUILD/VS2003/makeuctb/makeuctb.sln b/BUILD/VS2003/makeuctb/makeuctb.sln new file mode 100644 index 0000000..f8cfdbd --- /dev/null +++ b/BUILD/VS2003/makeuctb/makeuctb.sln @@ -0,0 +1,21 @@ +Microsoft Visual Studio Solution File, Format Version 8.00 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "makeuctb", "makeuctb.vcproj", "{C6C72FCE-5049-4D45-A7B2-586A80B6CC20}" + ProjectSection(ProjectDependencies) = postProject + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfiguration) = preSolution + Debug = Debug + Release = Release + EndGlobalSection + GlobalSection(ProjectConfiguration) = postSolution + {C6C72FCE-5049-4D45-A7B2-586A80B6CC20}.Debug.ActiveCfg = Debug|Win32 + {C6C72FCE-5049-4D45-A7B2-586A80B6CC20}.Debug.Build.0 = Debug|Win32 + {C6C72FCE-5049-4D45-A7B2-586A80B6CC20}.Release.ActiveCfg = Release|Win32 + {C6C72FCE-5049-4D45-A7B2-586A80B6CC20}.Release.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + EndGlobalSection + GlobalSection(ExtensibilityAddIns) = postSolution + EndGlobalSection +EndGlobal diff --git a/BUILD/VS2003/makeuctb/makeuctb.vcproj b/BUILD/VS2003/makeuctb/makeuctb.vcproj new file mode 100644 index 0000000..f217c67 --- /dev/null +++ b/BUILD/VS2003/makeuctb/makeuctb.vcproj @@ -0,0 +1,179 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/BUILD/VS2005X/clean.bat b/BUILD/VS2005X/clean.bat new file mode 100644 index 0000000..84d35a1 --- /dev/null +++ b/BUILD/VS2005X/clean.bat @@ -0,0 +1,31 @@ +@echo off +@rem $LynxId: clean.bat,v 1.1 2007/06/30 14:16:07 tom Exp $ +@rem Remove all build-products in subdirectories, leaving only sources (and unrecognized types) + +del/f/s/q *.aps +del/f/s/q *.bsc +del/f/s/q *.dep +del/f/s/q *.exe +del/f/s/q *.exp +del/f/s/q *.i +del/f/s/q *.idb +del/f/s/q *.ilk +del/f/s/q *.lib +del/f/s/q *.ncb +del/f/s/q *.obj +del/f/s/q *.opt +del/f/s/q *.pch +del/f/s/q *.pdb +del/f/s/q *.plg +del/f/s/q *.res +del/f/s/q *.sbr +del/f/s/q *.suo + +del/f/s/q *.manifest +del/f/s/q *.user +del/f/s/q BuildLog.htm + +attrib +r *.h /s +attrib +r *.bat /s +attrib +r *.sln /s +attrib +r *.vcproj /s diff --git a/BUILD/VS2005X/develop.bat b/BUILD/VS2005X/develop.bat new file mode 100644 index 0000000..93509a6 --- /dev/null +++ b/BUILD/VS2005X/develop.bat @@ -0,0 +1,7 @@ +@echo off +@rem $LynxId: develop.bat,v 1.2 2007/06/29 00:22:25 tom Exp $ +@rem ensure that all IDE files are writable + +attrib -r *.bat /s +attrib -r *.sln /s +attrib -r *.vcproj /s \ No newline at end of file diff --git a/BUILD/VS2005X/lynx/lynx.sln b/BUILD/VS2005X/lynx/lynx.sln new file mode 100644 index 0000000..b4d9bc4 --- /dev/null +++ b/BUILD/VS2005X/lynx/lynx.sln @@ -0,0 +1,29 @@ + +Microsoft Visual Studio Solution File, Format Version 9.00 +# Visual C++ Express 2005 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "lynx", "lynx.vcproj", "{77B6BED2-257D-4F7D-AA1B-D180875BD3BF}" + ProjectSection(ProjectDependencies) = postProject + {3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E} = {3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "makeuctb", "..\makeuctb\makeuctb.vcproj", "{3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {77B6BED2-257D-4F7D-AA1B-D180875BD3BF}.Debug|Win32.ActiveCfg = Debug|Win32 + {77B6BED2-257D-4F7D-AA1B-D180875BD3BF}.Debug|Win32.Build.0 = Debug|Win32 + {77B6BED2-257D-4F7D-AA1B-D180875BD3BF}.Release|Win32.ActiveCfg = Release|Win32 + {77B6BED2-257D-4F7D-AA1B-D180875BD3BF}.Release|Win32.Build.0 = Release|Win32 + {3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E}.Debug|Win32.ActiveCfg = Debug|Win32 + {3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E}.Debug|Win32.Build.0 = Debug|Win32 + {3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E}.Release|Win32.ActiveCfg = Release|Win32 + {3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/BUILD/VS2005X/lynx/lynx.vcproj b/BUILD/VS2005X/lynx/lynx.vcproj new file mode 100644 index 0000000..032e3b7 --- /dev/null +++ b/BUILD/VS2005X/lynx/lynx.vcproj @@ -0,0 +1,950 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/BUILD/VS2005X/lynx/lynx_cfg.h b/BUILD/VS2005X/lynx/lynx_cfg.h new file mode 100644 index 0000000..7059d49 --- /dev/null +++ b/BUILD/VS2005X/lynx/lynx_cfg.h @@ -0,0 +1,70 @@ +// $LynxId: lynx_cfg.h,v 1.5 2011/05/28 13:07:55 tom Exp $ +// definitions abstracted from makefile.msc + +#ifndef LYNX_CFG_H +#define LYNX_CFG_H 1 + +#define __WIN32__ +#define _WINDOWS +#define _WIN32_WINNT 0x0400 +#define WIN32_LEAN_AND_MEAN 1 // fixes redefinition of winsock2.h + +#define ACCESS_AUTH 1 +#define CJK_EX 1 +#define DIRED_SUPPORT 1 +#define DISP_PARTIAL 1 +#define DOSPATH 1 +#define USE_ALT_BINDINGS 1 +#define EXP_NESTED_TABLES 1 +#define HAVE_KEYPAD 1 +#define HAVE_PUTENV 1 +#define LONG_LIST 1 +#define NDEBUG 1 +#define NOSIGHUP 1 +#define NOUSERS 1 +#define NO_CONFIG_INFO 1 +#define NO_CUSERID 1 +#define NO_FILIO_H 1 +#define NO_TTYTYPE 1 +#define NO_UNISTD_H 1 +#define NO_UTMP 1 +#define OK_OVERRIDE 1 +#define SH_EX 1 +#define USE_CMD_LOGGING 1 +#define USE_EXTERNALS 1 +#define USE_FILE_UPLOAD 1 +#define USE_JUSTIFY_ELTS 1 +#define USE_MULTIBYTE_CURSES 1 +#define USE_PERSISTENT_COOKIES 1 +#define USE_PRETTYSRC 1 +#define USE_READPROGRESS 1 +#define USE_SCROLLBAR 1 +#define USE_SOURCE_CACHE 1 +#define USE_ZLIB 1 +#define WIN_EX 1 + +// definitions to account for using this file (see HTUtils.h, userdefs.h) +#define ANSI_VARARGS 1 +#define HAVE_GETCWD 1 +#define HAVE_STDARG_H 1 +#define HAVE_STDLIB_H 1 +#define LYNX_CFG_FILE "./lynx.cfg" +#define UNDERLINE_LINKS FALSE +#define socklen_t int + +// configuration choices +#define PDCURSES 1 +#define USE_WINSOCK2_H 1 + +#ifdef PDCURSES +#define USE_COLOR_STYLE 1 +#define COLOR_CURSES 1 +#define FANCY_CURSES 1 +#endif + +#pragma warning (disable : 4244) /* conversion from 'xxx' to 'yyy', possible loss of data */ +#pragma warning (disable : 4267) /* conversion from 'xxx' to 'yyy', possible loss of data */ +#pragma warning (disable : 4311) /* 'type cast': pointer truncation from 'xxx' to 'yyy' FIXME */ +#pragma warning (disable : 4996) /* 'xxx': This function or variable may be unsafe. Consider using yyy instead. */ + +#endif /* LYNX_CFG_H */ diff --git a/BUILD/VS2005X/makeuctb/makeuctb.sln b/BUILD/VS2005X/makeuctb/makeuctb.sln new file mode 100644 index 0000000..ebcb3e2 --- /dev/null +++ b/BUILD/VS2005X/makeuctb/makeuctb.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 9.00 +# Visual C++ Express 2005 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "makeuctb", "makeuctb.vcproj", "{3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E}.Debug|Win32.ActiveCfg = Debug|Win32 + {3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E}.Debug|Win32.Build.0 = Debug|Win32 + {3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E}.Release|Win32.ActiveCfg = Release|Win32 + {3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/BUILD/VS2005X/makeuctb/makeuctb.vcproj b/BUILD/VS2005X/makeuctb/makeuctb.vcproj new file mode 100644 index 0000000..fce60eb --- /dev/null +++ b/BUILD/VS2005X/makeuctb/makeuctb.vcproj @@ -0,0 +1,227 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/BUILD/VS2008X/clean.bat b/BUILD/VS2008X/clean.bat new file mode 100644 index 0000000..7b11001 --- /dev/null +++ b/BUILD/VS2008X/clean.bat @@ -0,0 +1,41 @@ +@echo off +@rem $LynxId: clean.bat,v 1.4 2018/03/18 23:12:17 tom Exp $ +@rem Remove all build-products in subdirectories, leaving only sources +@rem (and unrecognized types) +setlocal + +FOR /D %%d IN (*) DO call :dosoln %%d + +attrib -h *.suo /s + +del/f/s/q *.ncb +del/f/s/q *.old +del/f/s/q *.suo +del/f/s/q *.sdf +del/f/s/q *.user +del/f/s/q *.xml + +attrib +r *.h /s +attrib +r *.bat /s +attrib +r *.sln /s +attrib +r *.vcproj* /s +endlocal +goto :eof + +:dosoln + setlocal + echo Cleanup %* + cd %1 + + set SOLN= + FOR %%d IN ( *.sln ) DO set SOLN=%%d + if not "x%SOLN%"=="x" goto :dosoln2 + + echo ?? Not a solution directory + goto :dosolnx + +:dosoln2 + FOR /D %%d IN (*) DO rmdir /s /q %%d +:dosolnx + endlocal + goto :eof diff --git a/BUILD/VS2008X/develop.bat b/BUILD/VS2008X/develop.bat new file mode 100644 index 0000000..93509a6 --- /dev/null +++ b/BUILD/VS2008X/develop.bat @@ -0,0 +1,7 @@ +@echo off +@rem $LynxId: develop.bat,v 1.2 2007/06/29 00:22:25 tom Exp $ +@rem ensure that all IDE files are writable + +attrib -r *.bat /s +attrib -r *.sln /s +attrib -r *.vcproj /s \ No newline at end of file diff --git a/BUILD/VS2008X/lynx/lynx.sln b/BUILD/VS2008X/lynx/lynx.sln new file mode 100644 index 0000000..d6d0a39 --- /dev/null +++ b/BUILD/VS2008X/lynx/lynx.sln @@ -0,0 +1,29 @@ + +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual C++ Express 2008 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "lynx", "lynx.vcproj", "{77B6BED2-257D-4F7D-AA1B-D180875BD3BF}" + ProjectSection(ProjectDependencies) = postProject + {3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E} = {3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "makeuctb", "..\makeuctb\makeuctb.vcproj", "{3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {77B6BED2-257D-4F7D-AA1B-D180875BD3BF}.Debug|Win32.ActiveCfg = Debug|Win32 + {77B6BED2-257D-4F7D-AA1B-D180875BD3BF}.Debug|Win32.Build.0 = Debug|Win32 + {77B6BED2-257D-4F7D-AA1B-D180875BD3BF}.Release|Win32.ActiveCfg = Release|Win32 + {77B6BED2-257D-4F7D-AA1B-D180875BD3BF}.Release|Win32.Build.0 = Release|Win32 + {3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E}.Debug|Win32.ActiveCfg = Debug|Win32 + {3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E}.Debug|Win32.Build.0 = Debug|Win32 + {3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E}.Release|Win32.ActiveCfg = Release|Win32 + {3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/BUILD/VS2008X/lynx/lynx.vcproj b/BUILD/VS2008X/lynx/lynx.vcproj new file mode 100644 index 0000000..72687b1 --- /dev/null +++ b/BUILD/VS2008X/lynx/lynx.vcproj @@ -0,0 +1,955 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/BUILD/VS2008X/lynx/lynx_cfg.h b/BUILD/VS2008X/lynx/lynx_cfg.h new file mode 100644 index 0000000..7059d49 --- /dev/null +++ b/BUILD/VS2008X/lynx/lynx_cfg.h @@ -0,0 +1,70 @@ +// $LynxId: lynx_cfg.h,v 1.5 2011/05/28 13:07:55 tom Exp $ +// definitions abstracted from makefile.msc + +#ifndef LYNX_CFG_H +#define LYNX_CFG_H 1 + +#define __WIN32__ +#define _WINDOWS +#define _WIN32_WINNT 0x0400 +#define WIN32_LEAN_AND_MEAN 1 // fixes redefinition of winsock2.h + +#define ACCESS_AUTH 1 +#define CJK_EX 1 +#define DIRED_SUPPORT 1 +#define DISP_PARTIAL 1 +#define DOSPATH 1 +#define USE_ALT_BINDINGS 1 +#define EXP_NESTED_TABLES 1 +#define HAVE_KEYPAD 1 +#define HAVE_PUTENV 1 +#define LONG_LIST 1 +#define NDEBUG 1 +#define NOSIGHUP 1 +#define NOUSERS 1 +#define NO_CONFIG_INFO 1 +#define NO_CUSERID 1 +#define NO_FILIO_H 1 +#define NO_TTYTYPE 1 +#define NO_UNISTD_H 1 +#define NO_UTMP 1 +#define OK_OVERRIDE 1 +#define SH_EX 1 +#define USE_CMD_LOGGING 1 +#define USE_EXTERNALS 1 +#define USE_FILE_UPLOAD 1 +#define USE_JUSTIFY_ELTS 1 +#define USE_MULTIBYTE_CURSES 1 +#define USE_PERSISTENT_COOKIES 1 +#define USE_PRETTYSRC 1 +#define USE_READPROGRESS 1 +#define USE_SCROLLBAR 1 +#define USE_SOURCE_CACHE 1 +#define USE_ZLIB 1 +#define WIN_EX 1 + +// definitions to account for using this file (see HTUtils.h, userdefs.h) +#define ANSI_VARARGS 1 +#define HAVE_GETCWD 1 +#define HAVE_STDARG_H 1 +#define HAVE_STDLIB_H 1 +#define LYNX_CFG_FILE "./lynx.cfg" +#define UNDERLINE_LINKS FALSE +#define socklen_t int + +// configuration choices +#define PDCURSES 1 +#define USE_WINSOCK2_H 1 + +#ifdef PDCURSES +#define USE_COLOR_STYLE 1 +#define COLOR_CURSES 1 +#define FANCY_CURSES 1 +#endif + +#pragma warning (disable : 4244) /* conversion from 'xxx' to 'yyy', possible loss of data */ +#pragma warning (disable : 4267) /* conversion from 'xxx' to 'yyy', possible loss of data */ +#pragma warning (disable : 4311) /* 'type cast': pointer truncation from 'xxx' to 'yyy' FIXME */ +#pragma warning (disable : 4996) /* 'xxx': This function or variable may be unsafe. Consider using yyy instead. */ + +#endif /* LYNX_CFG_H */ diff --git a/BUILD/VS2008X/makeuctb/makeuctb.sln b/BUILD/VS2008X/makeuctb/makeuctb.sln new file mode 100644 index 0000000..9e3257d --- /dev/null +++ b/BUILD/VS2008X/makeuctb/makeuctb.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual C++ Express 2008 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "makeuctb", "makeuctb.vcproj", "{3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E}.Debug|Win32.ActiveCfg = Debug|Win32 + {3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E}.Debug|Win32.Build.0 = Debug|Win32 + {3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E}.Release|Win32.ActiveCfg = Release|Win32 + {3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/BUILD/VS2008X/makeuctb/makeuctb.vcproj b/BUILD/VS2008X/makeuctb/makeuctb.vcproj new file mode 100644 index 0000000..74ca626 --- /dev/null +++ b/BUILD/VS2008X/makeuctb/makeuctb.vcproj @@ -0,0 +1,226 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/BUILD/VS2010X32/clean.bat b/BUILD/VS2010X32/clean.bat new file mode 100644 index 0000000..ce85cd2 --- /dev/null +++ b/BUILD/VS2010X32/clean.bat @@ -0,0 +1,40 @@ +@echo off +@rem $LynxId: clean.bat,v 1.4 2018/03/18 23:17:43 tom Exp $ +@rem Remove all build-products in subdirectories, leaving only sources +@rem (and unrecognized types) +setlocal + +FOR /D %%d IN (*) DO call :dosoln %%d + +attrib -h *.suo /s + +del/f/s/q *.old +del/f/s/q *.suo +del/f/s/q *.sdf +del/f/s/q *.user +del/f/s/q *.xml + +attrib +r *.h /s +attrib +r *.bat /s +attrib +r *.sln /s +attrib +r *.vcxproj* /s +endlocal +goto :eof + +:dosoln + setlocal + echo Cleanup %* + cd %1 + + set SOLN= + FOR %%d IN ( *.sln ) DO set SOLN=%%d + if not "x%SOLN%"=="x" goto :dosoln2 + + echo ?? Not a solution directory + goto :dosolnx + +:dosoln2 + FOR /D %%d IN (*) DO rmdir /s /q %%d +:dosolnx + endlocal + goto :eof diff --git a/BUILD/VS2010X32/develop.bat b/BUILD/VS2010X32/develop.bat new file mode 100644 index 0000000..7cde5d6 --- /dev/null +++ b/BUILD/VS2010X32/develop.bat @@ -0,0 +1,7 @@ +@echo off +@rem $LynxId: develop.bat,v 1.2 2018/03/18 23:13:30 tom Exp $ +@rem ensure that all IDE files are writable + +attrib -r *.bat /s +attrib -r *.sln /s +attrib -r *.vcxproj /s \ No newline at end of file diff --git a/BUILD/VS2010X32/lynx/lynx.sln b/BUILD/VS2010X32/lynx/lynx.sln new file mode 100644 index 0000000..b84abfd --- /dev/null +++ b/BUILD/VS2010X32/lynx/lynx.sln @@ -0,0 +1,26 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual C++ Express 2010 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "lynx", "lynx.vcxproj", "{77B6BED2-257D-4F7D-AA1B-D180875BD3BF}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "makeuctb", "..\makeuctb\makeuctb.vcxproj", "{3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {77B6BED2-257D-4F7D-AA1B-D180875BD3BF}.Debug|Win32.ActiveCfg = Debug|Win32 + {77B6BED2-257D-4F7D-AA1B-D180875BD3BF}.Debug|Win32.Build.0 = Debug|Win32 + {77B6BED2-257D-4F7D-AA1B-D180875BD3BF}.Release|Win32.ActiveCfg = Release|Win32 + {77B6BED2-257D-4F7D-AA1B-D180875BD3BF}.Release|Win32.Build.0 = Release|Win32 + {3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E}.Debug|Win32.ActiveCfg = Debug|Win32 + {3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E}.Debug|Win32.Build.0 = Debug|Win32 + {3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E}.Release|Win32.ActiveCfg = Release|Win32 + {3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/BUILD/VS2010X32/lynx/lynx.vcxproj b/BUILD/VS2010X32/lynx/lynx.vcxproj new file mode 100644 index 0000000..923977d --- /dev/null +++ b/BUILD/VS2010X32/lynx/lynx.vcxproj @@ -0,0 +1,307 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {77B6BED2-257D-4F7D-AA1B-D180875BD3BF} + lynx + Win32Proj + + + + Application + Unicode + true + + + Application + Unicode + + + + + + + + + + + + + <_ProjectFileVersion>10.0.40219.1 + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + true + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + false + + + + Disabled + ..\lynx;$(SolutionDir)..\..\..;$(SolutionDir)..\..\..\lib;$(SolutionDir)..\..\..\WWW\Library\Implementation;$(SolutionDir)..\..\..\src;$(SolutionDir)..\..\..\src\chrtrans;%(AdditionalIncludeDirectories) + WIN32;_CONSOLE;HAVE_CONFIG_H;DONT_HAVE_TM_GMTOFF;_WIN_CC + false + false + true + EnableFastChecks + MultiThreadedDebug + + + Level3 + EditAndContinue + + + pdcurses.lib;zlib.lib;wsock32.lib;user32.lib;advapi32.lib;%(AdditionalDependencies) + $(VcInstallDir)lib;$(SolutionDir)\lib;$(SolutionDir)..\..\..\lib;%(AdditionalLibraryDirectories) + libc;libcmt;%(IgnoreSpecificDefaultLibraries) + true + Console + false + + + MachineX86 + + + + + ..\lynx;$(SolutionDir)..\..\..;$(SolutionDir)..\..\..\lib;$(SolutionDir)..\..\..\WWW\Library\Implementation;$(SolutionDir)..\..\..\src;$(SolutionDir)..\..\..\src\chrtrans;%(AdditionalIncludeDirectories) + WIN32;_CONSOLE;_MBCS;HAVE_CONFIG_H;DONT_HAVE_TM_GMTOFF;_WIN_CC + MultiThreaded + + + Level3 + ProgramDatabase + + + pdcurses.lib;zlib.lib;wsock32.lib;user32.lib;advapi32.lib;%(AdditionalDependencies) + $(VcInstallDir)lib;$(SolutionDir)\lib;$(SolutionDir)..\..\..\lib;%(AdditionalLibraryDirectories) + %(IgnoreSpecificDefaultLibraries) + true + Console + true + true + false + + + MachineX86 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {3ef8c45c-fc77-47b8-a5b6-5f9034ece06e} + false + + + + + + \ No newline at end of file diff --git a/BUILD/VS2010X32/lynx/lynx.vcxproj.filters b/BUILD/VS2010X32/lynx/lynx.vcxproj.filters new file mode 100644 index 0000000..2da4473 --- /dev/null +++ b/BUILD/VS2010X32/lynx/lynx.vcxproj.filters @@ -0,0 +1,609 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/BUILD/VS2010X32/lynx/lynx_cfg.h b/BUILD/VS2010X32/lynx/lynx_cfg.h new file mode 100644 index 0000000..095c1a0 --- /dev/null +++ b/BUILD/VS2010X32/lynx/lynx_cfg.h @@ -0,0 +1,70 @@ +// $LynxId: lynx_cfg.h,v 1.1 2011/05/28 13:07:55 tom Exp $ +// definitions abstracted from makefile.msc + +#ifndef LYNX_CFG_H +#define LYNX_CFG_H 1 + +#define __WIN32__ +#define _WINDOWS +#define _WIN32_WINNT 0x0400 +#define WIN32_LEAN_AND_MEAN 1 // fixes redefinition of winsock2.h + +#define ACCESS_AUTH 1 +#define CJK_EX 1 +#define DIRED_SUPPORT 1 +#define DISP_PARTIAL 1 +#define DOSPATH 1 +#define USE_ALT_BINDINGS 1 +#define EXP_NESTED_TABLES 1 +#define HAVE_KEYPAD 1 +#define HAVE_PUTENV 1 +#define LONG_LIST 1 +#define NDEBUG 1 +#define NOSIGHUP 1 +#define NOUSERS 1 +#define NO_CONFIG_INFO 1 +#define NO_CUSERID 1 +#define NO_FILIO_H 1 +#define NO_TTYTYPE 1 +#define NO_UNISTD_H 1 +#define NO_UTMP 1 +#define OK_OVERRIDE 1 +#define SH_EX 1 +#define USE_CMD_LOGGING 1 +#define USE_EXTERNALS 1 +#define USE_FILE_UPLOAD 1 +#define USE_JUSTIFY_ELTS 1 +#define USE_MULTIBYTE_CURSES 1 +#define USE_PERSISTENT_COOKIES 1 +#define USE_PRETTYSRC 1 +#define USE_READPROGRESS 1 +#define USE_SCROLLBAR 1 +#define USE_SOURCE_CACHE 1 +#define USE_ZLIB 1 +#define WIN_EX 1 + +// definitions to account for using this file (see HTUtils.h, userdefs.h) +#define ANSI_VARARGS 1 +#define HAVE_GETCWD 1 +#define HAVE_STDARG_H 1 +#define HAVE_STDLIB_H 1 +#define LYNX_CFG_FILE "./lynx.cfg" +#define UNDERLINE_LINKS FALSE +#define socklen_t int + +// configuration choices +#define PDCURSES 1 +#define USE_WINSOCK2_H 1 + +#ifdef PDCURSES +#define USE_COLOR_STYLE 1 +#define COLOR_CURSES 1 +#define FANCY_CURSES 1 +#endif + +#pragma warning (disable : 4244) /* conversion from 'xxx' to 'yyy', possible loss of data */ +#pragma warning (disable : 4267) /* conversion from 'xxx' to 'yyy', possible loss of data */ +#pragma warning (disable : 4311) /* 'type cast': pointer truncation from 'xxx' to 'yyy' FIXME */ +#pragma warning (disable : 4996) /* 'xxx': This function or variable may be unsafe. Consider using yyy instead. */ + +#endif /* LYNX_CFG_H */ diff --git a/BUILD/VS2010X32/makeuctb/makeuctb.sln b/BUILD/VS2010X32/makeuctb/makeuctb.sln new file mode 100644 index 0000000..9e3257d --- /dev/null +++ b/BUILD/VS2010X32/makeuctb/makeuctb.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual C++ Express 2008 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "makeuctb", "makeuctb.vcproj", "{3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E}.Debug|Win32.ActiveCfg = Debug|Win32 + {3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E}.Debug|Win32.Build.0 = Debug|Win32 + {3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E}.Release|Win32.ActiveCfg = Release|Win32 + {3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/BUILD/VS2010X32/makeuctb/makeuctb.vcxproj b/BUILD/VS2010X32/makeuctb/makeuctb.vcxproj new file mode 100644 index 0000000..a84cb27 --- /dev/null +++ b/BUILD/VS2010X32/makeuctb/makeuctb.vcxproj @@ -0,0 +1,124 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E} + makeuctb + Win32Proj + + + + Application + Unicode + true + + + Application + Unicode + + + + + + + + + + + + + <_ProjectFileVersion>10.0.40219.1 + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + true + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + false + + + + setlocal +set MYDST=..\..\..\src\chrtrans +copy $(TargetPath) %MYDST% +cd %MYDST% +call makehdrs +endlocal + + ..\..\..\src\chrtrans\makeuctb.exe;..\..\..\src\chrtrans\def7_uni.h;..\..\..\src\chrtrans\cp1250_uni.h;..\..\..\src\chrtrans\cp1251_uni.h;..\..\..\src\chrtrans\cp1252_uni.h;..\..\..\src\chrtrans\cp1253_uni.h;..\..\..\src\chrtrans\cp1255_uni.h;..\..\..\src\chrtrans\cp1256_uni.h;..\..\..\src\chrtrans\cp1257_uni.h;..\..\..\src\chrtrans\cp437_uni.h;..\..\..\src\chrtrans\cp737_uni.h;..\..\..\src\chrtrans\cp775_uni.h;..\..\..\src\chrtrans\cp850_uni.h;..\..\..\src\chrtrans\cp852_uni.h;..\..\..\src\chrtrans\cp857_uni.h;..\..\..\src\chrtrans\cp862_uni.h;..\..\..\src\chrtrans\cp864_uni.h;..\..\..\src\chrtrans\cp866_uni.h;..\..\..\src\chrtrans\cp866u_uni.h;..\..\..\src\chrtrans\cp869_uni.h;..\..\..\src\chrtrans\dmcs_uni.h;..\..\..\src\chrtrans\hp_uni.h;..\..\..\src\chrtrans\iso01_uni.h;..\..\..\src\chrtrans\iso02_uni.h;..\..\..\src\chrtrans\iso03_uni.h;..\..\..\src\chrtrans\iso04_uni.h;..\..\..\src\chrtrans\iso05_uni.h;..\..\..\src\chrtrans\iso06_uni.h;..\..\..\src\chrtrans\iso07_uni.h;..\..\..\src\chrtrans\iso08_uni.h;..\..\..\src\chrtrans\iso09_uni.h;..\..\..\src\chrtrans\iso10_uni.h;..\..\..\src\chrtrans\iso13_uni.h;..\..\..\src\chrtrans\iso14_uni.h;..\..\..\src\chrtrans\iso15_uni.h;..\..\..\src\chrtrans\koi8r_uni.h;..\..\..\src\chrtrans\koi8u_uni.h;..\..\..\src\chrtrans\mac_uni.h;..\..\..\src\chrtrans\mnem2_suni.h;..\..\..\src\chrtrans\mnem_suni.h;..\..\..\src\chrtrans\next_uni.h;..\..\..\src\chrtrans\pt154_uni.h;..\..\..\src\chrtrans\rfc_suni.h;..\..\..\src\chrtrans\utf8_uni.h;..\..\..\src\chrtrans\viscii_uni.h;%(Outputs) + + + Disabled + ..\lynx;$(SolutionDir)..\..\..;$(SolutionDir)..\..\..\lib;$(SolutionDir)..\..\..\WWW\Library\Implementation;$(SolutionDir)..\..\..\src;$(SolutionDir)..\..\..\src\chrtrans;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_CONSOLE;_MBCS;HAVE_CONFIG_H;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebugDLL + + + Level3 + EditAndContinue + + + true + Console + false + + + MachineX86 + + + + + setlocal +set MYDST=..\..\..\src\chrtrans +copy $(TargetPath) %MYDST% +cd %MYDST% +call makehdrs +endlocal + + ..\..\..\src\chrtrans\makeuctb.exe;..\..\..\src\chrtrans\def7_uni.h;..\..\..\src\chrtrans\cp1250_uni.h;..\..\..\src\chrtrans\cp1251_uni.h;..\..\..\src\chrtrans\cp1252_uni.h;..\..\..\src\chrtrans\cp1253_uni.h;..\..\..\src\chrtrans\cp1255_uni.h;..\..\..\src\chrtrans\cp1256_uni.h;..\..\..\src\chrtrans\cp1257_uni.h;..\..\..\src\chrtrans\cp437_uni.h;..\..\..\src\chrtrans\cp737_uni.h;..\..\..\src\chrtrans\cp775_uni.h;..\..\..\src\chrtrans\cp850_uni.h;..\..\..\src\chrtrans\cp852_uni.h;..\..\..\src\chrtrans\cp857_uni.h;..\..\..\src\chrtrans\cp862_uni.h;..\..\..\src\chrtrans\cp864_uni.h;..\..\..\src\chrtrans\cp866_uni.h;..\..\..\src\chrtrans\cp866u_uni.h;..\..\..\src\chrtrans\cp869_uni.h;..\..\..\src\chrtrans\dmcs_uni.h;..\..\..\src\chrtrans\hp_uni.h;..\..\..\src\chrtrans\iso01_uni.h;..\..\..\src\chrtrans\iso02_uni.h;..\..\..\src\chrtrans\iso03_uni.h;..\..\..\src\chrtrans\iso04_uni.h;..\..\..\src\chrtrans\iso05_uni.h;..\..\..\src\chrtrans\iso06_uni.h;..\..\..\src\chrtrans\iso07_uni.h;..\..\..\src\chrtrans\iso08_uni.h;..\..\..\src\chrtrans\iso09_uni.h;..\..\..\src\chrtrans\iso10_uni.h;..\..\..\src\chrtrans\iso13_uni.h;..\..\..\src\chrtrans\iso14_uni.h;..\..\..\src\chrtrans\iso15_uni.h;..\..\..\src\chrtrans\koi8r_uni.h;..\..\..\src\chrtrans\koi8u_uni.h;..\..\..\src\chrtrans\mac_uni.h;..\..\..\src\chrtrans\mnem2_suni.h;..\..\..\src\chrtrans\mnem_suni.h;..\..\..\src\chrtrans\next_uni.h;..\..\..\src\chrtrans\pt154_uni.h;..\..\..\src\chrtrans\rfc_suni.h;..\..\..\src\chrtrans\utf8_uni.h;..\..\..\src\chrtrans\viscii_uni.h;%(Outputs) + + + ..\lynx;$(SolutionDir)..\..\..;$(SolutionDir)..\..\..\lib;$(SolutionDir)..\..\..\WWW\Library\Implementation;$(SolutionDir)..\..\..\src;$(SolutionDir)..\..\..\src\chrtrans;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_CONSOLE;HAVE_CONFIG_H;%(PreprocessorDefinitions) + MultiThreadedDLL + + + Level3 + ProgramDatabase + + + true + Console + true + true + false + + + MachineX86 + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BUILD/VS2010X32/makeuctb/makeuctb.vcxproj.filters b/BUILD/VS2010X32/makeuctb/makeuctb.vcxproj.filters new file mode 100644 index 0000000..87c5d40 --- /dev/null +++ b/BUILD/VS2010X32/makeuctb/makeuctb.vcxproj.filters @@ -0,0 +1,42 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav + + + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/BUILD/VS2012X32/clean.bat b/BUILD/VS2012X32/clean.bat new file mode 100644 index 0000000..5b8e305 --- /dev/null +++ b/BUILD/VS2012X32/clean.bat @@ -0,0 +1,40 @@ +@echo off +@rem $LynxId: clean.bat,v 1.3 2018/03/18 23:17:43 tom Exp $ +@rem Remove all build-products in subdirectories, leaving only sources +@rem (and unrecognized types) +setlocal + +FOR /D %%d IN (*) DO call :dosoln %%d + +attrib -h *.suo /s + +del/f/s/q *.old +del/f/s/q *.suo +del/f/s/q *.sdf +del/f/s/q *.user +del/f/s/q *.xml + +attrib +r *.h /s +attrib +r *.bat /s +attrib +r *.sln /s +attrib +r *.vcxproj* /s +endlocal +goto :eof + +:dosoln + setlocal + echo Cleanup %* + cd %1 + + set SOLN= + FOR %%d IN ( *.sln ) DO set SOLN=%%d + if not "x%SOLN%"=="x" goto :dosoln2 + + echo ?? Not a solution directory + goto :dosolnx + +:dosoln2 + FOR /D %%d IN (*) DO rmdir /s /q %%d +:dosolnx + endlocal + goto :eof diff --git a/BUILD/VS2012X32/develop.bat b/BUILD/VS2012X32/develop.bat new file mode 100644 index 0000000..7cde5d6 --- /dev/null +++ b/BUILD/VS2012X32/develop.bat @@ -0,0 +1,7 @@ +@echo off +@rem $LynxId: develop.bat,v 1.2 2018/03/18 23:13:30 tom Exp $ +@rem ensure that all IDE files are writable + +attrib -r *.bat /s +attrib -r *.sln /s +attrib -r *.vcxproj /s \ No newline at end of file diff --git a/BUILD/VS2012X32/lynx/lynx.sln b/BUILD/VS2012X32/lynx/lynx.sln new file mode 100644 index 0000000..b84abfd --- /dev/null +++ b/BUILD/VS2012X32/lynx/lynx.sln @@ -0,0 +1,26 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual C++ Express 2010 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "lynx", "lynx.vcxproj", "{77B6BED2-257D-4F7D-AA1B-D180875BD3BF}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "makeuctb", "..\makeuctb\makeuctb.vcxproj", "{3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {77B6BED2-257D-4F7D-AA1B-D180875BD3BF}.Debug|Win32.ActiveCfg = Debug|Win32 + {77B6BED2-257D-4F7D-AA1B-D180875BD3BF}.Debug|Win32.Build.0 = Debug|Win32 + {77B6BED2-257D-4F7D-AA1B-D180875BD3BF}.Release|Win32.ActiveCfg = Release|Win32 + {77B6BED2-257D-4F7D-AA1B-D180875BD3BF}.Release|Win32.Build.0 = Release|Win32 + {3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E}.Debug|Win32.ActiveCfg = Debug|Win32 + {3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E}.Debug|Win32.Build.0 = Debug|Win32 + {3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E}.Release|Win32.ActiveCfg = Release|Win32 + {3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/BUILD/VS2012X32/lynx/lynx.vcxproj b/BUILD/VS2012X32/lynx/lynx.vcxproj new file mode 100644 index 0000000..8f3da7a --- /dev/null +++ b/BUILD/VS2012X32/lynx/lynx.vcxproj @@ -0,0 +1,309 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {77B6BED2-257D-4F7D-AA1B-D180875BD3BF} + lynx + Win32Proj + + + + Application + Unicode + true + v120 + + + Application + Unicode + v120 + + + + + + + + + + + + + <_ProjectFileVersion>10.0.40219.1 + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + true + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + false + + + + Disabled + ..\lynx;$(SolutionDir)..\..\..;$(SolutionDir)..\..\..\lib;$(SolutionDir)..\..\..\WWW\Library\Implementation;$(SolutionDir)..\..\..\src;$(SolutionDir)..\..\..\src\chrtrans;%(AdditionalIncludeDirectories) + WIN32;_CONSOLE;HAVE_CONFIG_H;DONT_HAVE_TM_GMTOFF;_WIN_CC + false + false + true + EnableFastChecks + MultiThreadedDebug + + + Level3 + EditAndContinue + + + pdcurses.lib;zlib.lib;wsock32.lib;user32.lib;advapi32.lib;%(AdditionalDependencies) + $(VcInstallDir)lib;$(SolutionDir)\lib;$(SolutionDir)..\..\..\lib;%(AdditionalLibraryDirectories) + libc;libcmt;%(IgnoreSpecificDefaultLibraries) + true + Console + false + + + MachineX86 + + + + + ..\lynx;$(SolutionDir)..\..\..;$(SolutionDir)..\..\..\lib;$(SolutionDir)..\..\..\WWW\Library\Implementation;$(SolutionDir)..\..\..\src;$(SolutionDir)..\..\..\src\chrtrans;%(AdditionalIncludeDirectories) + WIN32;_CONSOLE;_MBCS;HAVE_CONFIG_H;DONT_HAVE_TM_GMTOFF;_WIN_CC + MultiThreaded + + + Level3 + ProgramDatabase + + + pdcurses.lib;zlib.lib;wsock32.lib;user32.lib;advapi32.lib;%(AdditionalDependencies) + $(VcInstallDir)lib;$(SolutionDir)\lib;$(SolutionDir)..\..\..\lib;%(AdditionalLibraryDirectories) + %(IgnoreSpecificDefaultLibraries) + true + Console + true + true + false + + + MachineX86 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {3ef8c45c-fc77-47b8-a5b6-5f9034ece06e} + false + + + + + + \ No newline at end of file diff --git a/BUILD/VS2012X32/lynx/lynx.vcxproj.filters b/BUILD/VS2012X32/lynx/lynx.vcxproj.filters new file mode 100644 index 0000000..2da4473 --- /dev/null +++ b/BUILD/VS2012X32/lynx/lynx.vcxproj.filters @@ -0,0 +1,609 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/BUILD/VS2012X32/lynx/lynx_cfg.h b/BUILD/VS2012X32/lynx/lynx_cfg.h new file mode 100644 index 0000000..095c1a0 --- /dev/null +++ b/BUILD/VS2012X32/lynx/lynx_cfg.h @@ -0,0 +1,70 @@ +// $LynxId: lynx_cfg.h,v 1.1 2011/05/28 13:07:55 tom Exp $ +// definitions abstracted from makefile.msc + +#ifndef LYNX_CFG_H +#define LYNX_CFG_H 1 + +#define __WIN32__ +#define _WINDOWS +#define _WIN32_WINNT 0x0400 +#define WIN32_LEAN_AND_MEAN 1 // fixes redefinition of winsock2.h + +#define ACCESS_AUTH 1 +#define CJK_EX 1 +#define DIRED_SUPPORT 1 +#define DISP_PARTIAL 1 +#define DOSPATH 1 +#define USE_ALT_BINDINGS 1 +#define EXP_NESTED_TABLES 1 +#define HAVE_KEYPAD 1 +#define HAVE_PUTENV 1 +#define LONG_LIST 1 +#define NDEBUG 1 +#define NOSIGHUP 1 +#define NOUSERS 1 +#define NO_CONFIG_INFO 1 +#define NO_CUSERID 1 +#define NO_FILIO_H 1 +#define NO_TTYTYPE 1 +#define NO_UNISTD_H 1 +#define NO_UTMP 1 +#define OK_OVERRIDE 1 +#define SH_EX 1 +#define USE_CMD_LOGGING 1 +#define USE_EXTERNALS 1 +#define USE_FILE_UPLOAD 1 +#define USE_JUSTIFY_ELTS 1 +#define USE_MULTIBYTE_CURSES 1 +#define USE_PERSISTENT_COOKIES 1 +#define USE_PRETTYSRC 1 +#define USE_READPROGRESS 1 +#define USE_SCROLLBAR 1 +#define USE_SOURCE_CACHE 1 +#define USE_ZLIB 1 +#define WIN_EX 1 + +// definitions to account for using this file (see HTUtils.h, userdefs.h) +#define ANSI_VARARGS 1 +#define HAVE_GETCWD 1 +#define HAVE_STDARG_H 1 +#define HAVE_STDLIB_H 1 +#define LYNX_CFG_FILE "./lynx.cfg" +#define UNDERLINE_LINKS FALSE +#define socklen_t int + +// configuration choices +#define PDCURSES 1 +#define USE_WINSOCK2_H 1 + +#ifdef PDCURSES +#define USE_COLOR_STYLE 1 +#define COLOR_CURSES 1 +#define FANCY_CURSES 1 +#endif + +#pragma warning (disable : 4244) /* conversion from 'xxx' to 'yyy', possible loss of data */ +#pragma warning (disable : 4267) /* conversion from 'xxx' to 'yyy', possible loss of data */ +#pragma warning (disable : 4311) /* 'type cast': pointer truncation from 'xxx' to 'yyy' FIXME */ +#pragma warning (disable : 4996) /* 'xxx': This function or variable may be unsafe. Consider using yyy instead. */ + +#endif /* LYNX_CFG_H */ diff --git a/BUILD/VS2012X32/makeuctb/makeuctb.sln b/BUILD/VS2012X32/makeuctb/makeuctb.sln new file mode 100644 index 0000000..9e3257d --- /dev/null +++ b/BUILD/VS2012X32/makeuctb/makeuctb.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual C++ Express 2008 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "makeuctb", "makeuctb.vcproj", "{3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E}.Debug|Win32.ActiveCfg = Debug|Win32 + {3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E}.Debug|Win32.Build.0 = Debug|Win32 + {3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E}.Release|Win32.ActiveCfg = Release|Win32 + {3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/BUILD/VS2012X32/makeuctb/makeuctb.vcxproj b/BUILD/VS2012X32/makeuctb/makeuctb.vcxproj new file mode 100644 index 0000000..54e0d01 --- /dev/null +++ b/BUILD/VS2012X32/makeuctb/makeuctb.vcxproj @@ -0,0 +1,126 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E} + makeuctb + Win32Proj + + + + Application + Unicode + true + v120 + + + Application + Unicode + v120 + + + + + + + + + + + + + <_ProjectFileVersion>10.0.40219.1 + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + true + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + false + + + + setlocal +set MYDST=..\..\..\src\chrtrans +copy $(TargetPath) %MYDST% +cd %MYDST% +call makehdrs +endlocal + + ..\..\..\src\chrtrans\makeuctb.exe;..\..\..\src\chrtrans\def7_uni.h;..\..\..\src\chrtrans\cp1250_uni.h;..\..\..\src\chrtrans\cp1251_uni.h;..\..\..\src\chrtrans\cp1252_uni.h;..\..\..\src\chrtrans\cp1253_uni.h;..\..\..\src\chrtrans\cp1255_uni.h;..\..\..\src\chrtrans\cp1256_uni.h;..\..\..\src\chrtrans\cp1257_uni.h;..\..\..\src\chrtrans\cp437_uni.h;..\..\..\src\chrtrans\cp737_uni.h;..\..\..\src\chrtrans\cp775_uni.h;..\..\..\src\chrtrans\cp850_uni.h;..\..\..\src\chrtrans\cp852_uni.h;..\..\..\src\chrtrans\cp857_uni.h;..\..\..\src\chrtrans\cp862_uni.h;..\..\..\src\chrtrans\cp864_uni.h;..\..\..\src\chrtrans\cp866_uni.h;..\..\..\src\chrtrans\cp866u_uni.h;..\..\..\src\chrtrans\cp869_uni.h;..\..\..\src\chrtrans\dmcs_uni.h;..\..\..\src\chrtrans\hp_uni.h;..\..\..\src\chrtrans\iso01_uni.h;..\..\..\src\chrtrans\iso02_uni.h;..\..\..\src\chrtrans\iso03_uni.h;..\..\..\src\chrtrans\iso04_uni.h;..\..\..\src\chrtrans\iso05_uni.h;..\..\..\src\chrtrans\iso06_uni.h;..\..\..\src\chrtrans\iso07_uni.h;..\..\..\src\chrtrans\iso08_uni.h;..\..\..\src\chrtrans\iso09_uni.h;..\..\..\src\chrtrans\iso10_uni.h;..\..\..\src\chrtrans\iso13_uni.h;..\..\..\src\chrtrans\iso14_uni.h;..\..\..\src\chrtrans\iso15_uni.h;..\..\..\src\chrtrans\koi8r_uni.h;..\..\..\src\chrtrans\koi8u_uni.h;..\..\..\src\chrtrans\mac_uni.h;..\..\..\src\chrtrans\mnem2_suni.h;..\..\..\src\chrtrans\mnem_suni.h;..\..\..\src\chrtrans\next_uni.h;..\..\..\src\chrtrans\pt154_uni.h;..\..\..\src\chrtrans\rfc_suni.h;..\..\..\src\chrtrans\utf8_uni.h;..\..\..\src\chrtrans\viscii_uni.h;%(Outputs) + + + Disabled + ..\lynx;$(SolutionDir)..\..\..;$(SolutionDir)..\..\..\lib;$(SolutionDir)..\..\..\WWW\Library\Implementation;$(SolutionDir)..\..\..\src;$(SolutionDir)..\..\..\src\chrtrans;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_CONSOLE;_MBCS;HAVE_CONFIG_H;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebugDLL + + + Level3 + EditAndContinue + + + true + Console + false + + + MachineX86 + + + + + setlocal +set MYDST=..\..\..\src\chrtrans +copy $(TargetPath) %MYDST% +cd %MYDST% +call makehdrs +endlocal + + ..\..\..\src\chrtrans\makeuctb.exe;..\..\..\src\chrtrans\def7_uni.h;..\..\..\src\chrtrans\cp1250_uni.h;..\..\..\src\chrtrans\cp1251_uni.h;..\..\..\src\chrtrans\cp1252_uni.h;..\..\..\src\chrtrans\cp1253_uni.h;..\..\..\src\chrtrans\cp1255_uni.h;..\..\..\src\chrtrans\cp1256_uni.h;..\..\..\src\chrtrans\cp1257_uni.h;..\..\..\src\chrtrans\cp437_uni.h;..\..\..\src\chrtrans\cp737_uni.h;..\..\..\src\chrtrans\cp775_uni.h;..\..\..\src\chrtrans\cp850_uni.h;..\..\..\src\chrtrans\cp852_uni.h;..\..\..\src\chrtrans\cp857_uni.h;..\..\..\src\chrtrans\cp862_uni.h;..\..\..\src\chrtrans\cp864_uni.h;..\..\..\src\chrtrans\cp866_uni.h;..\..\..\src\chrtrans\cp866u_uni.h;..\..\..\src\chrtrans\cp869_uni.h;..\..\..\src\chrtrans\dmcs_uni.h;..\..\..\src\chrtrans\hp_uni.h;..\..\..\src\chrtrans\iso01_uni.h;..\..\..\src\chrtrans\iso02_uni.h;..\..\..\src\chrtrans\iso03_uni.h;..\..\..\src\chrtrans\iso04_uni.h;..\..\..\src\chrtrans\iso05_uni.h;..\..\..\src\chrtrans\iso06_uni.h;..\..\..\src\chrtrans\iso07_uni.h;..\..\..\src\chrtrans\iso08_uni.h;..\..\..\src\chrtrans\iso09_uni.h;..\..\..\src\chrtrans\iso10_uni.h;..\..\..\src\chrtrans\iso13_uni.h;..\..\..\src\chrtrans\iso14_uni.h;..\..\..\src\chrtrans\iso15_uni.h;..\..\..\src\chrtrans\koi8r_uni.h;..\..\..\src\chrtrans\koi8u_uni.h;..\..\..\src\chrtrans\mac_uni.h;..\..\..\src\chrtrans\mnem2_suni.h;..\..\..\src\chrtrans\mnem_suni.h;..\..\..\src\chrtrans\next_uni.h;..\..\..\src\chrtrans\pt154_uni.h;..\..\..\src\chrtrans\rfc_suni.h;..\..\..\src\chrtrans\utf8_uni.h;..\..\..\src\chrtrans\viscii_uni.h;%(Outputs) + + + ..\lynx;$(SolutionDir)..\..\..;$(SolutionDir)..\..\..\lib;$(SolutionDir)..\..\..\WWW\Library\Implementation;$(SolutionDir)..\..\..\src;$(SolutionDir)..\..\..\src\chrtrans;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_CONSOLE;HAVE_CONFIG_H;%(PreprocessorDefinitions) + MultiThreadedDLL + + + Level3 + ProgramDatabase + + + true + Console + true + true + false + + + MachineX86 + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BUILD/VS2012X32/makeuctb/makeuctb.vcxproj.filters b/BUILD/VS2012X32/makeuctb/makeuctb.vcxproj.filters new file mode 100644 index 0000000..87c5d40 --- /dev/null +++ b/BUILD/VS2012X32/makeuctb/makeuctb.vcxproj.filters @@ -0,0 +1,42 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav + + + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/BUILD/VS2012X64/clean.bat b/BUILD/VS2012X64/clean.bat new file mode 100644 index 0000000..d353c45 --- /dev/null +++ b/BUILD/VS2012X64/clean.bat @@ -0,0 +1,40 @@ +@echo off +@rem $LynxId: clean.bat,v 1.2 2018/03/18 23:17:43 tom Exp $ +@rem Remove all build-products in subdirectories, leaving only sources +@rem (and unrecognized types) +setlocal + +FOR /D %%d IN (*) DO call :dosoln %%d + +attrib -h *.suo /s + +del/f/s/q *.old +del/f/s/q *.suo +del/f/s/q *.sdf +del/f/s/q *.user +del/f/s/q *.xml + +attrib +r *.h /s +attrib +r *.bat /s +attrib +r *.sln /s +attrib +r *.vcxproj* /s +endlocal +goto :eof + +:dosoln + setlocal + echo Cleanup %* + cd %1 + + set SOLN= + FOR %%d IN ( *.sln ) DO set SOLN=%%d + if not "x%SOLN%"=="x" goto :dosoln2 + + echo ?? Not a solution directory + goto :dosolnx + +:dosoln2 + FOR /D %%d IN (*) DO rmdir /s /q %%d +:dosolnx + endlocal + goto :eof diff --git a/BUILD/VS2012X64/develop.bat b/BUILD/VS2012X64/develop.bat new file mode 100644 index 0000000..7cde5d6 --- /dev/null +++ b/BUILD/VS2012X64/develop.bat @@ -0,0 +1,7 @@ +@echo off +@rem $LynxId: develop.bat,v 1.2 2018/03/18 23:13:30 tom Exp $ +@rem ensure that all IDE files are writable + +attrib -r *.bat /s +attrib -r *.sln /s +attrib -r *.vcxproj /s \ No newline at end of file diff --git a/BUILD/VS2012X64/lynx/lynx.sln b/BUILD/VS2012X64/lynx/lynx.sln new file mode 100644 index 0000000..5934e66 --- /dev/null +++ b/BUILD/VS2012X64/lynx/lynx.sln @@ -0,0 +1,38 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Express 2013 for Windows Desktop +VisualStudioVersion = 12.0.40629.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "lynx", "lynx.vcxproj", "{77B6BED2-257D-4F7D-AA1B-D180875BD3BF}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "makeuctb", "..\makeuctb\makeuctb.vcxproj", "{3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {77B6BED2-257D-4F7D-AA1B-D180875BD3BF}.Debug|Win32.ActiveCfg = Debug|Win32 + {77B6BED2-257D-4F7D-AA1B-D180875BD3BF}.Debug|Win32.Build.0 = Debug|Win32 + {77B6BED2-257D-4F7D-AA1B-D180875BD3BF}.Debug|x64.ActiveCfg = Debug|x64 + {77B6BED2-257D-4F7D-AA1B-D180875BD3BF}.Debug|x64.Build.0 = Debug|x64 + {77B6BED2-257D-4F7D-AA1B-D180875BD3BF}.Release|Win32.ActiveCfg = Release|Win32 + {77B6BED2-257D-4F7D-AA1B-D180875BD3BF}.Release|Win32.Build.0 = Release|Win32 + {77B6BED2-257D-4F7D-AA1B-D180875BD3BF}.Release|x64.ActiveCfg = Release|x64 + {77B6BED2-257D-4F7D-AA1B-D180875BD3BF}.Release|x64.Build.0 = Release|x64 + {3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E}.Debug|Win32.ActiveCfg = Debug|Win32 + {3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E}.Debug|Win32.Build.0 = Debug|Win32 + {3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E}.Debug|x64.ActiveCfg = Debug|x64 + {3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E}.Debug|x64.Build.0 = Debug|x64 + {3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E}.Release|Win32.ActiveCfg = Release|Win32 + {3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E}.Release|Win32.Build.0 = Release|Win32 + {3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E}.Release|x64.ActiveCfg = Release|x64 + {3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/BUILD/VS2012X64/lynx/lynx.vcxproj b/BUILD/VS2012X64/lynx/lynx.vcxproj new file mode 100644 index 0000000..f3def43 --- /dev/null +++ b/BUILD/VS2012X64/lynx/lynx.vcxproj @@ -0,0 +1,384 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {77B6BED2-257D-4F7D-AA1B-D180875BD3BF} + lynx + Win32Proj + + + + Application + Unicode + true + v120 + + + Application + Unicode + true + v120 + + + Application + Unicode + v120 + + + Application + Unicode + v120 + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.40219.1 + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + true + true + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + false + false + + + + Disabled + ..\lynx;$(SolutionDir)..\..\..;$(SolutionDir)..\..\..\lib;$(SolutionDir)..\..\..\WWW\Library\Implementation;$(SolutionDir)..\..\..\src;$(SolutionDir)..\..\..\src\chrtrans;%(AdditionalIncludeDirectories) + WIN32;_CONSOLE;HAVE_CONFIG_H;DONT_HAVE_TM_GMTOFF;_WIN_CC + false + false + true + EnableFastChecks + MultiThreadedDebug + + + Level3 + EditAndContinue + + + pdcurses.lib;zlib.lib;wsock32.lib;user32.lib;advapi32.lib;%(AdditionalDependencies) + $(VcInstallDir)lib;$(SolutionDir)lib;$(SolutionDir)..\..\lib;$(SolutionDir)..\..\..\lib;%(AdditionalLibraryDirectories) + libc;libcmt;%(IgnoreSpecificDefaultLibraries) + true + Console + false + + + MachineX86 + + + + + Disabled + ..\lynx;$(SolutionDir)..\..\..;$(SolutionDir)..\..\..\lib;$(SolutionDir)..\..\..\WWW\Library\Implementation;$(SolutionDir)..\..\..\src;$(SolutionDir)..\..\..\src\chrtrans;%(AdditionalIncludeDirectories) + WIN32;_CONSOLE;HAVE_CONFIG_H;DONT_HAVE_TM_GMTOFF;_WIN_CC + false + false + EnableFastChecks + MultiThreadedDebug + + + Level3 + ProgramDatabase + + + pdcurses.lib;zlib.lib;wsock32.lib;user32.lib;advapi32.lib;%(AdditionalDependencies) + $(VcInstallDir)lib\amd64;$(SolutionDir)\lib;$(SolutionDir)..\..\..\lib;%(AdditionalLibraryDirectories) + libc;libcmt;%(IgnoreSpecificDefaultLibraries) + true + Console + false + + + + + + + ..\lynx;$(SolutionDir)..\..\..;$(SolutionDir)..\..\..\lib;$(SolutionDir)..\..\..\WWW\Library\Implementation;$(SolutionDir)..\..\..\src;$(SolutionDir)..\..\..\src\chrtrans;%(AdditionalIncludeDirectories) + WIN32;_CONSOLE;_MBCS;HAVE_CONFIG_H;DONT_HAVE_TM_GMTOFF;_WIN_CC + MultiThreaded + + + Level3 + ProgramDatabase + + + pdcurses.lib;zlib.lib;wsock32.lib;user32.lib;advapi32.lib;%(AdditionalDependencies) + $(VcInstallDir)lib;$(SolutionDir)\lib;$(SolutionDir)..\..\..\lib;%(AdditionalLibraryDirectories) + %(IgnoreSpecificDefaultLibraries) + true + Console + true + true + false + + + MachineX86 + + + + + ..\lynx;$(SolutionDir)..\..\..;$(SolutionDir)..\..\..\lib;$(SolutionDir)..\..\..\WWW\Library\Implementation;$(SolutionDir)..\..\..\src;$(SolutionDir)..\..\..\src\chrtrans;%(AdditionalIncludeDirectories) + WIN32;_CONSOLE;_MBCS;HAVE_CONFIG_H;DONT_HAVE_TM_GMTOFF;_WIN_CC + MultiThreaded + + + Level3 + ProgramDatabase + + + pdcurses.lib;zlib.lib;wsock32.lib;user32.lib;advapi32.lib;%(AdditionalDependencies) + $(VcInstallDir)lib\amd64;$(SolutionDir)\lib;$(SolutionDir)..\..\..\lib;%(AdditionalLibraryDirectories) + %(IgnoreSpecificDefaultLibraries) + true + Console + true + true + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {3ef8c45c-fc77-47b8-a5b6-5f9034ece06e} + false + + + + + + \ No newline at end of file diff --git a/BUILD/VS2012X64/lynx/lynx.vcxproj.filters b/BUILD/VS2012X64/lynx/lynx.vcxproj.filters new file mode 100644 index 0000000..2da4473 --- /dev/null +++ b/BUILD/VS2012X64/lynx/lynx.vcxproj.filters @@ -0,0 +1,609 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/BUILD/VS2012X64/lynx/lynx_cfg.h b/BUILD/VS2012X64/lynx/lynx_cfg.h new file mode 100644 index 0000000..095c1a0 --- /dev/null +++ b/BUILD/VS2012X64/lynx/lynx_cfg.h @@ -0,0 +1,70 @@ +// $LynxId: lynx_cfg.h,v 1.1 2011/05/28 13:07:55 tom Exp $ +// definitions abstracted from makefile.msc + +#ifndef LYNX_CFG_H +#define LYNX_CFG_H 1 + +#define __WIN32__ +#define _WINDOWS +#define _WIN32_WINNT 0x0400 +#define WIN32_LEAN_AND_MEAN 1 // fixes redefinition of winsock2.h + +#define ACCESS_AUTH 1 +#define CJK_EX 1 +#define DIRED_SUPPORT 1 +#define DISP_PARTIAL 1 +#define DOSPATH 1 +#define USE_ALT_BINDINGS 1 +#define EXP_NESTED_TABLES 1 +#define HAVE_KEYPAD 1 +#define HAVE_PUTENV 1 +#define LONG_LIST 1 +#define NDEBUG 1 +#define NOSIGHUP 1 +#define NOUSERS 1 +#define NO_CONFIG_INFO 1 +#define NO_CUSERID 1 +#define NO_FILIO_H 1 +#define NO_TTYTYPE 1 +#define NO_UNISTD_H 1 +#define NO_UTMP 1 +#define OK_OVERRIDE 1 +#define SH_EX 1 +#define USE_CMD_LOGGING 1 +#define USE_EXTERNALS 1 +#define USE_FILE_UPLOAD 1 +#define USE_JUSTIFY_ELTS 1 +#define USE_MULTIBYTE_CURSES 1 +#define USE_PERSISTENT_COOKIES 1 +#define USE_PRETTYSRC 1 +#define USE_READPROGRESS 1 +#define USE_SCROLLBAR 1 +#define USE_SOURCE_CACHE 1 +#define USE_ZLIB 1 +#define WIN_EX 1 + +// definitions to account for using this file (see HTUtils.h, userdefs.h) +#define ANSI_VARARGS 1 +#define HAVE_GETCWD 1 +#define HAVE_STDARG_H 1 +#define HAVE_STDLIB_H 1 +#define LYNX_CFG_FILE "./lynx.cfg" +#define UNDERLINE_LINKS FALSE +#define socklen_t int + +// configuration choices +#define PDCURSES 1 +#define USE_WINSOCK2_H 1 + +#ifdef PDCURSES +#define USE_COLOR_STYLE 1 +#define COLOR_CURSES 1 +#define FANCY_CURSES 1 +#endif + +#pragma warning (disable : 4244) /* conversion from 'xxx' to 'yyy', possible loss of data */ +#pragma warning (disable : 4267) /* conversion from 'xxx' to 'yyy', possible loss of data */ +#pragma warning (disable : 4311) /* 'type cast': pointer truncation from 'xxx' to 'yyy' FIXME */ +#pragma warning (disable : 4996) /* 'xxx': This function or variable may be unsafe. Consider using yyy instead. */ + +#endif /* LYNX_CFG_H */ diff --git a/BUILD/VS2012X64/makeuctb/makeuctb.sln b/BUILD/VS2012X64/makeuctb/makeuctb.sln new file mode 100644 index 0000000..9e3257d --- /dev/null +++ b/BUILD/VS2012X64/makeuctb/makeuctb.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual C++ Express 2008 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "makeuctb", "makeuctb.vcproj", "{3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E}.Debug|Win32.ActiveCfg = Debug|Win32 + {3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E}.Debug|Win32.Build.0 = Debug|Win32 + {3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E}.Release|Win32.ActiveCfg = Release|Win32 + {3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/BUILD/VS2012X64/makeuctb/makeuctb.vcxproj b/BUILD/VS2012X64/makeuctb/makeuctb.vcxproj new file mode 100644 index 0000000..2a9d7f3 --- /dev/null +++ b/BUILD/VS2012X64/makeuctb/makeuctb.vcxproj @@ -0,0 +1,213 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {3EF8C45C-FC77-47B8-A5B6-5F9034ECE06E} + makeuctb + Win32Proj + + + + Application + Unicode + true + v120 + + + Application + Unicode + true + v120 + + + Application + Unicode + v120 + + + Application + Unicode + v120 + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.40219.1 + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + true + true + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + false + false + + + + setlocal +set MYDST=..\..\..\src\chrtrans +copy $(TargetPath) %MYDST% +cd %MYDST% +call makehdrs +endlocal + + ..\..\..\src\chrtrans\makeuctb.exe;..\..\..\src\chrtrans\def7_uni.h;..\..\..\src\chrtrans\cp1250_uni.h;..\..\..\src\chrtrans\cp1251_uni.h;..\..\..\src\chrtrans\cp1252_uni.h;..\..\..\src\chrtrans\cp1253_uni.h;..\..\..\src\chrtrans\cp1255_uni.h;..\..\..\src\chrtrans\cp1256_uni.h;..\..\..\src\chrtrans\cp1257_uni.h;..\..\..\src\chrtrans\cp437_uni.h;..\..\..\src\chrtrans\cp737_uni.h;..\..\..\src\chrtrans\cp775_uni.h;..\..\..\src\chrtrans\cp850_uni.h;..\..\..\src\chrtrans\cp852_uni.h;..\..\..\src\chrtrans\cp857_uni.h;..\..\..\src\chrtrans\cp862_uni.h;..\..\..\src\chrtrans\cp864_uni.h;..\..\..\src\chrtrans\cp866_uni.h;..\..\..\src\chrtrans\cp866u_uni.h;..\..\..\src\chrtrans\cp869_uni.h;..\..\..\src\chrtrans\dmcs_uni.h;..\..\..\src\chrtrans\hp_uni.h;..\..\..\src\chrtrans\iso01_uni.h;..\..\..\src\chrtrans\iso02_uni.h;..\..\..\src\chrtrans\iso03_uni.h;..\..\..\src\chrtrans\iso04_uni.h;..\..\..\src\chrtrans\iso05_uni.h;..\..\..\src\chrtrans\iso06_uni.h;..\..\..\src\chrtrans\iso07_uni.h;..\..\..\src\chrtrans\iso08_uni.h;..\..\..\src\chrtrans\iso09_uni.h;..\..\..\src\chrtrans\iso10_uni.h;..\..\..\src\chrtrans\iso13_uni.h;..\..\..\src\chrtrans\iso14_uni.h;..\..\..\src\chrtrans\iso15_uni.h;..\..\..\src\chrtrans\koi8r_uni.h;..\..\..\src\chrtrans\koi8u_uni.h;..\..\..\src\chrtrans\mac_uni.h;..\..\..\src\chrtrans\mnem2_suni.h;..\..\..\src\chrtrans\mnem_suni.h;..\..\..\src\chrtrans\next_uni.h;..\..\..\src\chrtrans\pt154_uni.h;..\..\..\src\chrtrans\rfc_suni.h;..\..\..\src\chrtrans\utf8_uni.h;..\..\..\src\chrtrans\viscii_uni.h;%(Outputs) + + + Disabled + ..\lynx;$(SolutionDir)..\..\..;$(SolutionDir)..\..\..\lib;$(SolutionDir)..\..\..\WWW\Library\Implementation;$(SolutionDir)..\..\..\src;$(SolutionDir)..\..\..\src\chrtrans;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_CONSOLE;_MBCS;HAVE_CONFIG_H;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebugDLL + + + Level3 + EditAndContinue + + + true + Console + false + + + MachineX86 + + + + + setlocal +set MYDST=..\..\..\src\chrtrans +copy $(TargetPath) %MYDST% +cd %MYDST% +call makehdrs +endlocal + + ..\..\..\src\chrtrans\makeuctb.exe;..\..\..\src\chrtrans\def7_uni.h;..\..\..\src\chrtrans\cp1250_uni.h;..\..\..\src\chrtrans\cp1251_uni.h;..\..\..\src\chrtrans\cp1252_uni.h;..\..\..\src\chrtrans\cp1253_uni.h;..\..\..\src\chrtrans\cp1255_uni.h;..\..\..\src\chrtrans\cp1256_uni.h;..\..\..\src\chrtrans\cp1257_uni.h;..\..\..\src\chrtrans\cp437_uni.h;..\..\..\src\chrtrans\cp737_uni.h;..\..\..\src\chrtrans\cp775_uni.h;..\..\..\src\chrtrans\cp850_uni.h;..\..\..\src\chrtrans\cp852_uni.h;..\..\..\src\chrtrans\cp857_uni.h;..\..\..\src\chrtrans\cp862_uni.h;..\..\..\src\chrtrans\cp864_uni.h;..\..\..\src\chrtrans\cp866_uni.h;..\..\..\src\chrtrans\cp866u_uni.h;..\..\..\src\chrtrans\cp869_uni.h;..\..\..\src\chrtrans\dmcs_uni.h;..\..\..\src\chrtrans\hp_uni.h;..\..\..\src\chrtrans\iso01_uni.h;..\..\..\src\chrtrans\iso02_uni.h;..\..\..\src\chrtrans\iso03_uni.h;..\..\..\src\chrtrans\iso04_uni.h;..\..\..\src\chrtrans\iso05_uni.h;..\..\..\src\chrtrans\iso06_uni.h;..\..\..\src\chrtrans\iso07_uni.h;..\..\..\src\chrtrans\iso08_uni.h;..\..\..\src\chrtrans\iso09_uni.h;..\..\..\src\chrtrans\iso10_uni.h;..\..\..\src\chrtrans\iso13_uni.h;..\..\..\src\chrtrans\iso14_uni.h;..\..\..\src\chrtrans\iso15_uni.h;..\..\..\src\chrtrans\koi8r_uni.h;..\..\..\src\chrtrans\koi8u_uni.h;..\..\..\src\chrtrans\mac_uni.h;..\..\..\src\chrtrans\mnem2_suni.h;..\..\..\src\chrtrans\mnem_suni.h;..\..\..\src\chrtrans\next_uni.h;..\..\..\src\chrtrans\pt154_uni.h;..\..\..\src\chrtrans\rfc_suni.h;..\..\..\src\chrtrans\utf8_uni.h;..\..\..\src\chrtrans\viscii_uni.h;%(Outputs) + + + Disabled + ..\lynx;$(SolutionDir)..\..\..;$(SolutionDir)..\..\..\lib;$(SolutionDir)..\..\..\WWW\Library\Implementation;$(SolutionDir)..\..\..\src;$(SolutionDir)..\..\..\src\chrtrans;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_CONSOLE;_MBCS;HAVE_CONFIG_H;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebugDLL + + + Level3 + ProgramDatabase + + + true + Console + false + + + + + + + setlocal +set MYDST=..\..\..\src\chrtrans +copy $(TargetPath) %MYDST% +cd %MYDST% +call makehdrs +endlocal + + ..\..\..\src\chrtrans\makeuctb.exe;..\..\..\src\chrtrans\def7_uni.h;..\..\..\src\chrtrans\cp1250_uni.h;..\..\..\src\chrtrans\cp1251_uni.h;..\..\..\src\chrtrans\cp1252_uni.h;..\..\..\src\chrtrans\cp1253_uni.h;..\..\..\src\chrtrans\cp1255_uni.h;..\..\..\src\chrtrans\cp1256_uni.h;..\..\..\src\chrtrans\cp1257_uni.h;..\..\..\src\chrtrans\cp437_uni.h;..\..\..\src\chrtrans\cp737_uni.h;..\..\..\src\chrtrans\cp775_uni.h;..\..\..\src\chrtrans\cp850_uni.h;..\..\..\src\chrtrans\cp852_uni.h;..\..\..\src\chrtrans\cp857_uni.h;..\..\..\src\chrtrans\cp862_uni.h;..\..\..\src\chrtrans\cp864_uni.h;..\..\..\src\chrtrans\cp866_uni.h;..\..\..\src\chrtrans\cp866u_uni.h;..\..\..\src\chrtrans\cp869_uni.h;..\..\..\src\chrtrans\dmcs_uni.h;..\..\..\src\chrtrans\hp_uni.h;..\..\..\src\chrtrans\iso01_uni.h;..\..\..\src\chrtrans\iso02_uni.h;..\..\..\src\chrtrans\iso03_uni.h;..\..\..\src\chrtrans\iso04_uni.h;..\..\..\src\chrtrans\iso05_uni.h;..\..\..\src\chrtrans\iso06_uni.h;..\..\..\src\chrtrans\iso07_uni.h;..\..\..\src\chrtrans\iso08_uni.h;..\..\..\src\chrtrans\iso09_uni.h;..\..\..\src\chrtrans\iso10_uni.h;..\..\..\src\chrtrans\iso13_uni.h;..\..\..\src\chrtrans\iso14_uni.h;..\..\..\src\chrtrans\iso15_uni.h;..\..\..\src\chrtrans\koi8r_uni.h;..\..\..\src\chrtrans\koi8u_uni.h;..\..\..\src\chrtrans\mac_uni.h;..\..\..\src\chrtrans\mnem2_suni.h;..\..\..\src\chrtrans\mnem_suni.h;..\..\..\src\chrtrans\next_uni.h;..\..\..\src\chrtrans\pt154_uni.h;..\..\..\src\chrtrans\rfc_suni.h;..\..\..\src\chrtrans\utf8_uni.h;..\..\..\src\chrtrans\viscii_uni.h;%(Outputs) + + + ..\lynx;$(SolutionDir)..\..\..;$(SolutionDir)..\..\..\lib;$(SolutionDir)..\..\..\WWW\Library\Implementation;$(SolutionDir)..\..\..\src;$(SolutionDir)..\..\..\src\chrtrans;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_CONSOLE;HAVE_CONFIG_H;%(PreprocessorDefinitions) + MultiThreadedDLL + + + Level3 + ProgramDatabase + + + true + Console + true + true + false + + + MachineX86 + + + + + setlocal +set MYDST=..\..\..\src\chrtrans +copy $(TargetPath) %MYDST% +cd %MYDST% +call makehdrs +endlocal + + ..\..\..\src\chrtrans\makeuctb.exe;..\..\..\src\chrtrans\def7_uni.h;..\..\..\src\chrtrans\cp1250_uni.h;..\..\..\src\chrtrans\cp1251_uni.h;..\..\..\src\chrtrans\cp1252_uni.h;..\..\..\src\chrtrans\cp1253_uni.h;..\..\..\src\chrtrans\cp1255_uni.h;..\..\..\src\chrtrans\cp1256_uni.h;..\..\..\src\chrtrans\cp1257_uni.h;..\..\..\src\chrtrans\cp437_uni.h;..\..\..\src\chrtrans\cp737_uni.h;..\..\..\src\chrtrans\cp775_uni.h;..\..\..\src\chrtrans\cp850_uni.h;..\..\..\src\chrtrans\cp852_uni.h;..\..\..\src\chrtrans\cp857_uni.h;..\..\..\src\chrtrans\cp862_uni.h;..\..\..\src\chrtrans\cp864_uni.h;..\..\..\src\chrtrans\cp866_uni.h;..\..\..\src\chrtrans\cp866u_uni.h;..\..\..\src\chrtrans\cp869_uni.h;..\..\..\src\chrtrans\dmcs_uni.h;..\..\..\src\chrtrans\hp_uni.h;..\..\..\src\chrtrans\iso01_uni.h;..\..\..\src\chrtrans\iso02_uni.h;..\..\..\src\chrtrans\iso03_uni.h;..\..\..\src\chrtrans\iso04_uni.h;..\..\..\src\chrtrans\iso05_uni.h;..\..\..\src\chrtrans\iso06_uni.h;..\..\..\src\chrtrans\iso07_uni.h;..\..\..\src\chrtrans\iso08_uni.h;..\..\..\src\chrtrans\iso09_uni.h;..\..\..\src\chrtrans\iso10_uni.h;..\..\..\src\chrtrans\iso13_uni.h;..\..\..\src\chrtrans\iso14_uni.h;..\..\..\src\chrtrans\iso15_uni.h;..\..\..\src\chrtrans\koi8r_uni.h;..\..\..\src\chrtrans\koi8u_uni.h;..\..\..\src\chrtrans\mac_uni.h;..\..\..\src\chrtrans\mnem2_suni.h;..\..\..\src\chrtrans\mnem_suni.h;..\..\..\src\chrtrans\next_uni.h;..\..\..\src\chrtrans\pt154_uni.h;..\..\..\src\chrtrans\rfc_suni.h;..\..\..\src\chrtrans\utf8_uni.h;..\..\..\src\chrtrans\viscii_uni.h;%(Outputs) + + + ..\lynx;$(SolutionDir)..\..\..;$(SolutionDir)..\..\..\lib;$(SolutionDir)..\..\..\WWW\Library\Implementation;$(SolutionDir)..\..\..\src;$(SolutionDir)..\..\..\src\chrtrans;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_CONSOLE;HAVE_CONFIG_H;%(PreprocessorDefinitions) + MultiThreadedDLL + + + Level3 + ProgramDatabase + + + true + Console + true + true + false + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BUILD/VS2012X64/makeuctb/makeuctb.vcxproj.filters b/BUILD/VS2012X64/makeuctb/makeuctb.vcxproj.filters new file mode 100644 index 0000000..87c5d40 --- /dev/null +++ b/BUILD/VS2012X64/makeuctb/makeuctb.vcxproj.filters @@ -0,0 +1,42 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav + + + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/BUILD/VS6/clean.bat b/BUILD/VS6/clean.bat new file mode 100644 index 0000000..5b51985 --- /dev/null +++ b/BUILD/VS6/clean.bat @@ -0,0 +1,27 @@ +@echo off +@rem $LynxId: clean.bat,v 1.1 2007/07/01 21:52:45 tom Exp $ +@rem Remove all build-products in subdirectories, leaving only sources (and unrecognized types) + +del/f/s/q *.aps +del/f/s/q *.bsc +del/f/s/q *.exe +del/f/s/q *.exp +del/f/s/q *.idb +del/f/s/q *.ilk +del/f/s/q *.lib +del/f/s/q *.ncb +del/f/s/q *.obj +del/f/s/q *.opt +del/f/s/q *.pch +del/f/s/q *.pdb +del/f/s/q *.plg +del/f/s/q *.res +del/f/s/q *.sbr +del/f/s/q *.suo + +del/f/s/q ne*.h +del/f/s/q BuildLog.htm + +attrib +r *.bat /s +attrib +r *.dsp /s +attrib +r *.dsw /s diff --git a/BUILD/VS6/develop.bat b/BUILD/VS6/develop.bat new file mode 100644 index 0000000..70c0c24 --- /dev/null +++ b/BUILD/VS6/develop.bat @@ -0,0 +1,7 @@ +@echo off +@rem $LynxId: develop.bat,v 1.1 2007/07/01 21:52:50 tom Exp $ +@rem ensure that all IDE files are writable + +attrib -r *.bat /s +attrib -r *.dsp /s +attrib -r *.dsw /s \ No newline at end of file diff --git a/BUILD/VS6/lynx/lynx.dsw b/BUILD/VS6/lynx/lynx.dsw new file mode 100644 index 0000000..3ef547d --- /dev/null +++ b/BUILD/VS6/lynx/lynx.dsw @@ -0,0 +1,44 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "lynx"=".\lynx\lynx.dsp" - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ + Begin Project Dependency + Project_Dep_Name makeuctb + End Project Dependency +}}} + +############################################################################### + +Project: "makeuctb"=".\makeuctb\makeuctb.dsp" - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/BUILD/VS6/lynx/lynx/lynx.dsp b/BUILD/VS6/lynx/lynx/lynx.dsp new file mode 100644 index 0000000..b7f56f9 --- /dev/null +++ b/BUILD/VS6/lynx/lynx/lynx.dsp @@ -0,0 +1,849 @@ +# Microsoft Developer Studio Project File - Name="lynx" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=lynx - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "lynx.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "lynx.mak" CFG="lynx - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "lynx - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "lynx - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "lynx - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /W3 /GX /O2 /I "..\..\..\..\src" /I "..\..\..\..\src\chrtrans" /I "..\..\..\..\WWW\Library\Implementation" /I "..\..\..\..\lib" /I "..\..\..\.." /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /D "_WINDOWS" /D "DOSPATH" /D "NO_UNISTD_H" /D "__WIN32__" /D "WIN_EX" /D "NOUSERS" /D "DIRED_SUPPORT" /D "DISP_PARTIAL" /D "DONT_HAVE_TM_GMTOFF" /D "HAVE_KEYPAD" /D "NOSIGHUP" /D "NO_TTYTYPE" /D "NO_UTMP" /D "SH_EX" /D "USE_EXTERNALS" /D "USE_FILE_UPLOAD" /D "USE_MULTIBYTE_CURSES" /D "USE_PERSISTENT_COOKIES" /D "USE_PRETTYSRC" /D "USE_READPROGRESS" /D "USE_SCROLLBAR" /D "USE_SOURCE_CACHE" /D "USE_ZLIB" /D "PDCURSES" /D "COLOR_CURSES" /D "FANCY_CURSES" /D "USE_COLOR_STYLE" /D "USE_WINSOCK2_H" /D _WIN32_WINNT=0x0400 /YX /FD /c +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib wsock32.lib pdcurses.lib zlib.lib /nologo /subsystem:console /machine:I386 /nodefaultlib:"libcmt" /libpath:"..\..\..\..\lib" + +!ELSEIF "$(CFG)" == "lynx - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /I "..\..\..\..\src" /I "..\..\..\..\src\chrtrans" /I "..\..\..\..\WWW\Library\Implementation" /I "..\..\..\..\lib" /I "..\..\..\.." /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /D "_WINDOWS" /D "DOSPATH" /D "NO_UNISTD_H" /D "__WIN32__" /D "WIN_EX" /D "NOUSERS" /D "DIRED_SUPPORT" /D "DISP_PARTIAL" /D "DONT_HAVE_TM_GMTOFF" /D "HAVE_KEYPAD" /D "NOSIGHUP" /D "NO_TTYTYPE" /D "NO_UTMP" /D "SH_EX" /D "USE_EXTERNALS" /D "USE_FILE_UPLOAD" /D "USE_MULTIBYTE_CURSES" /D "USE_PERSISTENT_COOKIES" /D "USE_PRETTYSRC" /D "USE_READPROGRESS" /D "USE_SCROLLBAR" /D "USE_SOURCE_CACHE" /D "USE_ZLIB" /D "PDCURSES" /D "COLOR_CURSES" /D "FANCY_CURSES" /D "USE_COLOR_STYLE" /D "USE_WINSOCK2_H" /D _WIN32_WINNT=0x0400 /YX /FD /GZ /c +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib wsock32.lib pdcurses.lib zlib.lib /nologo /subsystem:console /debug /machine:I386 /nodefaultlib:"libc" /nodefaultlib:"libcmt" /pdbtype:sept /libpath:"..\..\..\..\lib" + +!ENDIF + +# Begin Target + +# Name "lynx - Win32 Release" +# Name "lynx - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=..\..\..\..\src\DefaultStyle.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\lib\dirent.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\GridText.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTAABrow.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTAAProt.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTAAUtil.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTAccess.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\HTAlert.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTAnchor.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTAssoc.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTAtom.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTBTree.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTChunk.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTDOS.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTFile.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTFinger.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTFormat.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTFTP.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\HTFWriter.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTGopher.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTGroup.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\HTInit.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTLex.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTList.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTMIME.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\HTML.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTMLDTD.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTMLGen.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTNews.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTParse.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTPlain.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTRules.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTString.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTStyle.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTTCP.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTTelnet.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTTP.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTUU.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTWSRC.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYBookmark.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYCgi.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYCharSets.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYCharUtils.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYClean.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYCookie.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYCurses.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYDownload.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYEdit.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYEditmap.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYexit.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYExtern.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYForms.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYGetFile.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYHash.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYHistory.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYJump.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYKeymap.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYLeaks.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYList.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYLocal.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYMail.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYMain.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYMainLoop.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYMap.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYmktime.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYNews.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYOptions.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYPrettySrc.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYPrint.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYrcFile.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYReadCFG.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYSearch.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYShowInfo.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYStrings.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYStyle.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYTraversal.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYUpload.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYUtils.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\mktime.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\parsdate.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\SGML.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\strstr.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\TRSTable.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\UCAuto.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\UCAux.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\UCdomap.c +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\Xsystem.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=..\..\..\..\src\AttrList.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\GridText.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTAABrow.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTAAProt.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTAAUtil.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTAccess.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\HTAlert.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTAnchor.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTAssoc.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTAtom.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTBTree.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTChunk.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTCJK.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTDOS.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HText.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTFile.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTFinger.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\HTFont.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTFormat.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\HTForms.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTFTP.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTFWriter.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTGopher.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTGroup.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTInit.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTioctl.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTLex.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTList.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTMIME.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\HTML.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTMLDTD.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTMLGen.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\HTNestedList.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTNews.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTParse.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTPlain.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTRules.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\HTSaveToFile.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTStream.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTString.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTStyle.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTTCP.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTTelnet.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTTP.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\htutils.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTUU.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\HTWSRC.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYBookmark.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYCgi.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYCharSets.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYCharUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYCharVals.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYClean.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYCookie.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYCurses.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYDownload.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYEdit.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\LYexit.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYExtern.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYGCurses.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYGetFile.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYGlobalDefs.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYHash.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYHistory.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYJump.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYJustify.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYKeymap.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\LYLeaks.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYList.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYLocal.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYMail.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYMainLoop.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYMap.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\LYMessages_en.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYNews.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYOptions.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYPrettySrc.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYPrint.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYrcFile.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYReadCFG.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYSearch.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYShowInfo.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYSignal.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYStrings.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYStructs.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYStyle.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYTraversal.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYUpload.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\LYVMSdef.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\SGML.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\structdump.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\TRSTable.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\UCAuto.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\UCAux.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\UCDefs.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\UCdomap.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\UCMap.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\userdefs.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\www_tcp.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\WWW\Library\Implementation\www_wait.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# End Group +# End Target +# End Project diff --git a/BUILD/VS6/lynx/lynx/lynx.dsw b/BUILD/VS6/lynx/lynx/lynx.dsw new file mode 100644 index 0000000..6b6364f --- /dev/null +++ b/BUILD/VS6/lynx/lynx/lynx.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "lynx"=".\lynx.dsp" - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/BUILD/VS6/lynx/makeuctb/makeuctb.dsp b/BUILD/VS6/lynx/makeuctb/makeuctb.dsp new file mode 100644 index 0000000..1964c31 --- /dev/null +++ b/BUILD/VS6/lynx/makeuctb/makeuctb.dsp @@ -0,0 +1,420 @@ +# Microsoft Developer Studio Project File - Name="makeuctb" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=makeuctb - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "makeuctb.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "makeuctb.mak" CFG="makeuctb - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "makeuctb - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "makeuctb - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "makeuctb - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /W3 /GX /O2 /I "..\..\..\..\src" /I "..\..\..\..\src\chrtrans" /I "..\..\..\..\WWW\Library\Implementation" /I "..\..\..\..\lib" /I "..\..\..\.." /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /D "__WIN32__" /D "NO_UNISTD_H" /D "_WINDOWS" /YX /FD /c +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# Begin Custom Build +InputPath=.\Release\makeuctb.exe +SOURCE="$(InputPath)" + +BuildCmds= \ + setlocal \ + set MYDST=..\..\..\..\src\chrtrans \ + copy $(InputPath) %MYDST% \ + cd %MYDST% \ + call makehdrs \ + endlocal \ + + +"..\..\..\..\src\chrtrans\makeuctb.exe" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\def7_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\cp1250_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\cp1251_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\cp1252_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\cp1253_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\cp1255_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\cp1256_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\cp1257_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\cp437_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\cp737_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\cp775_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\cp850_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\cp852_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\cp857_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\cp862_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\cp864_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\cp866_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\cp866u_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\cp869_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\dmcs_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\hp_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\iso01_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\iso02_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\iso03_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\iso04_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\iso05_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\iso06_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\iso07_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\iso08_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\iso09_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\iso10_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\iso13_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\iso14_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\iso15_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\koi8r_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\koi8u_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\mac_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\mnem2_suni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\mnem_suni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\next_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\pt154_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\rfc_suni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\utf8_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\viscii_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) +# End Custom Build + +!ELSEIF "$(CFG)" == "makeuctb - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /I "..\..\..\..\src" /I "..\..\..\..\src\chrtrans" /I "..\..\..\..\WWW\Library\Implementation" /I "..\..\..\..\lib" /I "..\..\..\.." /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /D "__WIN32__" /D "NO_UNISTD_H" /D "_WINDOWS" /YX /FD /GZ /c +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# Begin Custom Build +InputPath=.\Debug\makeuctb.exe +SOURCE="$(InputPath)" + +BuildCmds= \ + setlocal \ + set MYDST=..\..\..\..\src\chrtrans \ + copy $(InputPath) %MYDST% \ + cd %MYDST% \ + call makehdrs \ + endlocal \ + + +"..\..\..\..\src\chrtrans\makeuctb.exe" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\def7_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\cp1250_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\cp1251_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\cp1252_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\cp1253_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\cp1255_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\cp1256_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\cp1257_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\cp437_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\cp737_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\cp775_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\cp850_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\cp852_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\cp857_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\cp862_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\cp864_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\cp866_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\cp866u_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\cp869_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\dmcs_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\hp_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\iso01_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\iso02_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\iso03_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\iso04_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\iso05_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\iso06_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\iso07_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\iso08_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\iso09_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\iso10_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\iso13_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\iso14_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\iso15_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\koi8r_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\koi8u_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\mac_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\mnem2_suni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\mnem_suni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\next_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\pt154_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\rfc_suni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\utf8_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\..\src\chrtrans\viscii_uni.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) +# End Custom Build + +!ENDIF + +# Begin Target + +# Name "makeuctb - Win32 Release" +# Name "makeuctb - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=..\..\..\..\src\chrtrans\makeuctb.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=..\..\..\..\src\chrtrans\caselower.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\chrtrans\entities.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\chrtrans\jcuken_kb.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\chrtrans\rot13_kb.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\src\chrtrans\UCkd.h +# End Source File +# Begin Source File + +SOURCE=....\..\..\src\chrtrans\yawerty_kb.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# End Group +# End Target +# End Project diff --git a/BUILD/mingw-curses.bat b/BUILD/mingw-curses.bat new file mode 100644 index 0000000..cb15d82 --- /dev/null +++ b/BUILD/mingw-curses.bat @@ -0,0 +1,357 @@ +@echo off +@echo MAKELYNX.BAT +@echo Windows/Dos batch makefile for MingW32 and lynx.exe +@echo Remember to precede this by "command /E:8192" for Windows prior to +@echo W2000 and "cmd.exe /E:8192" for subsequent Window versions and to +@echo set the MingW32 C_INCLUDE_PATH and %C_INCLUDE_PATH%..\..\bin paths +@echo. +@echo Usage: makelynx [option] +@echo Default option: all +@echo Specifying "src" causes the libwww code to be skipped. +@echo Specifying "link" causes the batch file to skip to the final +@echo linking phase. +@echo. +@echo Note that you have to edit early versions of i386-mingw32\include\stdlib.h +@echo to put an "#ifndef WIN_EX" around the declaration for `sleep', or the +@echo compile won't work. There is also an "#ifndef PDCURSES" around +@echo the declaration for `beep' for the same reason. +@echo. +@echo To change the console library from libpdcurses to libslang, +@echo put a "rem" before the `SET LIBRARY' line below. +@echo. +@echo If you don't have libz.a, either compile it +@echo or put a "rem" in front of the following USE_ZLIB line. +@echo This will cause the gzip.exe version of lynx +@echo to be compiled. It doesn't work well at present: + +set USE_ZLIB= +set DEFINES= + +SET LIBRARY=PDCURSES +SET USE_ZLIB=YES + +rem Uncomment these lines if the slang/curses headers and libraries +rem are in the top-level lib directory: +rem set C_INCLUDE_PATH=..\lib;..\..\..\lib;%C_INCLUDE_PATH% +rem set LIBRARY_PATH=..\lib;..\..\..\lib;%LIBRARY_PATH% + +echo Your compiler may not support -march=pentiumpro. +echo In that case, replace -march=pentiumpro with -mpentium or -m486 or nothing: + +if "%OS%" == "Windows_NT" goto then0 +rem command.com doesn't handle the 'a=b' option +set CC=gcc -mpentium +goto else0 +:then0 +rem assumes a cmd.exe, rather than command.com, environment +set CC=gcc -march=pentiumpro -mthreads +:else0 + + +rem These definitions come from the Microsoft.msc makefile, with some +rem modification. Note that -Dx=y didn't work in older versions +rem of Windows batch files, only -Dx, so a lynx_cfg.h was needed as +rem a workaround. +echo /* Generated lynx_cfg.h file in the lynx directory: */ > lynx_cfg.h +echo. >> lynx_cfg.h +echo. >> lynx_cfg.h +echo #define ANSI_VARARGS 1 >> lynx_cfg.h +echo #define BOXHORI 0 >> lynx_cfg.h +echo #define BOXVERT 0 >> lynx_cfg.h +echo #define CAN_PIPE_TO_MAILER 0 >> lynx_cfg.h +echo #define HAVE_GETCWD 1 >> lynx_cfg.h +echo #define HAVE_STRERROR 1 >> lynx_cfg.h +echo #define LYNX_CFG_FILE "./lynx.cfg" >> lynx_cfg.h +echo #define LY_MAXPATH 1024 >> lynx_cfg.h +echo #define USE_BLAT_MAILER 1 >> lynx_cfg.h +echo #define USE_ALT_BLAT_MAILER 1 >> lynx_cfg.h +echo #define VC "2.14FM" >> lynx_cfg.h +echo #define _WIN_CC 1 >> lynx_cfg.h +rem echo #define USE_SCROLLBAR 1 >> lynx_cfg.h + +SET DEFINES=-DCJK_EX +SET DEFINES=%DEFINES% -DUSE_READPROGRESS +SET DEFINES=%DEFINES% -DEXP_NESTED_TABLES +SET DEFINES=%DEFINES% -DEXP_JUSTIFY_ELTS +SET DEFINES=%DEFINES% -DEXP_ALT_BINDINGS +SET DEFINES=%DEFINES% -DUSE_PERSISTENT_COOKIES +if not "%OS%" == "Windows_NT" goto next11 +SET DEFINES=%DEFINES% -DLY_MAXPATH=1024 +rem The following is unnecessary and causes the +rem compile to fail: +rem SET DEFINES=%DEFINES% -DUSE_WINSOCK2_H +:next11 +SET DEFINES=%DEFINES% -DNO_CONFIG_INFO +SET DEFINES=%DEFINES% -DSH_EX +SET DEFINES=%DEFINES% -DWIN_EX +SET DEFINES=%DEFINES% -D_WINDOWS +SET DEFINES=%DEFINES% -DUSE_EXTERNALS +SET DEFINES=%DEFINES% -DDIRED_SUPPORT +SET DEFINES=%DEFINES% -DDOSPATH +SET DEFINES=%DEFINES% -DHAVE_DIRENT_H +SET DEFINES=%DEFINES% -DHAVE_KEYPAD +SET DEFINES=%DEFINES% -DACCESS_AUTH +SET DEFINES=%DEFINES% -DNO_FILIO_H +SET DEFINES=%DEFINES% -DNO_UNISTD_H +SET DEFINES=%DEFINES% -DNO_UTMP +SET DEFINES=%DEFINES% -DNO_CUSERID +SET DEFINES=%DEFINES% -DNO_TTYTYPE +SET DEFINES=%DEFINES% -DNOSIGHUP +SET DEFINES=%DEFINES% -DNOUSERS +SET DEFINES=%DEFINES% -DLONG_LIST +SET DEFINES=%DEFINES% -DDISP_PARTIAL +SET DEFINES=%DEFINES% -DUSE_SOURCE_CACHE +SET DEFINES=%DEFINES% -DUSE_PRETTYSRC +SET DEFINES=%DEFINES% -DWIN32 +if not "%USE_ZLIB%" == "YES" goto next1 +echo *** Using ZLIB +SET DEFINES=%DEFINES% -DUSE_ZLIB +:next1 +if "%LIBRARY%" == "PDCURSES" goto else1 +echo *** Using SLANG +SET DEFINES=%DEFINES% -DUSE_SLANG +goto endif1 +:else1 +echo *** Using PDCURSES +SET DEFINES=%DEFINES% -DFANCY_CURSES +SET DEFINES=%DEFINES% -DCOLOR_CURSES +SET DEFINES=%DEFINES% -DPDCURSES +SET DEFINES=%DEFINES% -DUSE_MULTIBYTE_CURSES +:endif1 + +if not "%1" == "src" goto else + cd src + goto src +:else +if not "%1" == "link" goto endif + cd src + goto link +:endif + +SET INCLUDES=-I. -I..\..\.. -I..\..\..\src + +set CFLAGS=-g %INCLUDES% %DEFINES% +set COMPILE_CMD=%CC% -c %CFLAGS% + +cd WWW\Library\Implementation +erase *.o + +%COMPILE_CMD% HTAABrow.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTAAProt.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTAAUtil.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTAccess.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTAnchor.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTAssoc.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTAtom.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTBTree.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTChunk.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTDOS.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTFile.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTFinger.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTFormat.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTFTP.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTFWriter.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTGopher.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTGroup.c +if errorlevel 1 PAUSE + +%COMPILE_CMD% HTLex.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTList.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTMIME.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTMLDTD.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTMLGen.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTNews.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTParse.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTPlain.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTRules.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTString.c +if errorlevel 1 PAUSE + +%COMPILE_CMD% HTStyle.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTTCP.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTTelnet.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTTP.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTUU.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTWSRC.c +if errorlevel 1 PAUSE +%COMPILE_CMD% SGML.c +if errorlevel 1 PAUSE + +ar crv libwww.a *.o + +if errorlevel 1 PAUSE + +cd ..\..\..\src\chrtrans +erase *.o + +SET INCLUDES=-I. -I.. -I..\.. -I..\..\WWW\Library\Implementation +SET CFLAGS=-g %INCLUDES% %DEFINES% +SET COMPILE_CMD=%CC% -c %CFLAGS% + +%COMPILE_CMD% makeuctb.c +if errorlevel 1 PAUSE +%CC% -o makeuctb.exe makeuctb.o +if errorlevel 1 PAUSE + +call makew32.bat +if errorlevel 1 PAUSE +cd ..\ + +:src +SET INCLUDES=-I. -I.. -I.\chrtrans -I..\WWW\Library\Implementation +SET CFLAGS=-g %INCLUDES% %DEFINES% +SET COMPILE_CMD=%CC% -c %CFLAGS% +SET PATH=..\WWW\Library\Implementation;%PATH% +erase *.o + +%COMPILE_CMD% DefaultStyle.c +if errorlevel 1 PAUSE +%COMPILE_CMD% GridText.c +if errorlevel 1 PAUSE +if not exist TRSTable.c goto notrstable +%COMPILE_CMD% TRSTable.c +if errorlevel 1 PAUSE +:notrstable +if not exist Xsystem.c goto noxsystem +%COMPILE_CMD% Xsystem.c +if errorlevel 1 PAUSE +:noxsystem +%COMPILE_CMD% HTAlert.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTFWriter.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTInit.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTML.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYBookmark.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYCgi.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYCharSets.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYCharUtils.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYClean.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYCookie.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYCurses.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYDownload.c +if errorlevel 1 PAUSE + +%COMPILE_CMD% LYEdit.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYEditmap.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYexit.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYExtern.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYForms.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYGetFile.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYHash.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYHistory.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYJump.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYKeymap.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYLeaks.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYList.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYLocal.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYMail.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYMain.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYMainLoop.c +if errorlevel 1 PAUSE + +%COMPILE_CMD% LYMap.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYNews.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYOptions.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYPrettySrc.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYPrint.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYrcFile.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYReadCFG.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYSearch.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYShowInfo.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYStrings.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYStyle.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYTraversal.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYUpload.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYUtils.c +if errorlevel 1 PAUSE +%COMPILE_CMD% UCAuto.c +if errorlevel 1 PAUSE +%COMPILE_CMD% UCAux.c +if errorlevel 1 PAUSE +%COMPILE_CMD% UCdomap.c +if errorlevel 1 PAUSE + +:link +if not "%LIBRARY%" == "PDCURSES" goto else2 +SET LIBS=-L..\WWW\Library\Implementation -lwww -lpdcurses +goto endif2 +:else2 +SET LIBS=-L..\WWW\Library\Implementation -lwww -lslang +:endif2 + +SET LIBS=%LIBS% -lwsock32 -luser32 + +if not "%USE_ZLIB%" == "YES" goto else4 +SET LIBS=%LIBS% -lz +:else4 + +%CC% -g -o lynx *.o %LIBS% +if exist lynx.exe ECHO "Welcome to lynx!" diff --git a/BUILD/mingw-slang.bat b/BUILD/mingw-slang.bat new file mode 100644 index 0000000..d7cb537 --- /dev/null +++ b/BUILD/mingw-slang.bat @@ -0,0 +1,357 @@ +@echo off +@echo MAKELYNX.BAT +@echo Windows/Dos batch makefile for MingW32 and lynx.exe +@echo Remember to precede this by "command /E:8192" for Windows prior to +@echo W2000 and "cmd.exe /E:8192" for subsequent Window versions and to +@echo set the MingW32 C_INCLUDE_PATH and %C_INCLUDE_PATH%..\..\bin paths +@echo. +@echo Usage: makelynx [option] +@echo Default option: all +@echo Specifying "src" causes the libwww code to be skipped. +@echo Specifying "link" causes the batch file to skip to the final +@echo linking phase. +@echo. +@echo Note that you have to edit early versions of i386-mingw32\include\stdlib.h +@echo to put an "#ifndef WIN_EX" around the declaration for `sleep', or the +@echo compile won't work. There is also an "#ifndef PDCURSES" around +@echo the declaration for `beep' for the same reason. +@echo. +@echo To change the console library from libpdcurses to libslang, +@echo put a "rem" before the `SET LIBRARY' line below. +@echo. +@echo If you don't have libz.a, either compile it +@echo or put a "rem" in front of the following USE_ZLIB line. +@echo This will cause the gzip.exe version of lynx +@echo to be compiled. It doesn't work well at present: + +set USE_ZLIB= +set DEFINES= + +SET LIBRARY=SLANG +SET USE_ZLIB=YES + +rem Uncomment these lines if the slang/curses headers and libraries +rem are in the top-level lib directory: +rem set C_INCLUDE_PATH=..\lib;..\..\..\lib;%C_INCLUDE_PATH% +rem set LIBRARY_PATH=..\lib;..\..\..\lib;%LIBRARY_PATH% + +echo Your compiler may not support -march=pentiumpro. +echo In that case, replace -march=pentiumpro with -mpentium or -m486 or nothing: + +if "%OS%" == "Windows_NT" goto then0 +rem command.com doesn't handle the 'a=b' option +set CC=gcc -mpentium +goto else0 +:then0 +rem assumes a cmd.exe, rather than command.com, environment +set CC=gcc -march=pentiumpro -mthreads +:else0 + + +rem These definitions come from the Microsoft.msc makefile, with some +rem modification. Note that -Dx=y didn't work in older versions +rem of Windows batch files, only -Dx, so a lynx_cfg.h was needed as +rem a workaround. +echo /* Generated lynx_cfg.h file in the lynx directory: */ > lynx_cfg.h +echo. >> lynx_cfg.h +echo. >> lynx_cfg.h +echo #define ANSI_VARARGS 1 >> lynx_cfg.h +echo #define BOXHORI 0 >> lynx_cfg.h +echo #define BOXVERT 0 >> lynx_cfg.h +echo #define CAN_PIPE_TO_MAILER 0 >> lynx_cfg.h +echo #define HAVE_GETCWD 1 >> lynx_cfg.h +echo #define HAVE_STRERROR 1 >> lynx_cfg.h +echo #define LYNX_CFG_FILE "./lynx.cfg" >> lynx_cfg.h +echo #define LY_MAXPATH 1024 >> lynx_cfg.h +echo #define USE_BLAT_MAILER 1 >> lynx_cfg.h +echo #define USE_ALT_BLAT_MAILER 1 >> lynx_cfg.h +echo #define VC "2.14FM" >> lynx_cfg.h +echo #define _WIN_CC 1 >> lynx_cfg.h +rem echo #define USE_SCROLLBAR 1 >> lynx_cfg.h + +SET DEFINES=-DCJK_EX +SET DEFINES=%DEFINES% -DUSE_READPROGRESS +SET DEFINES=%DEFINES% -DEXP_NESTED_TABLES +SET DEFINES=%DEFINES% -DEXP_JUSTIFY_ELTS +SET DEFINES=%DEFINES% -DEXP_ALT_BINDINGS +SET DEFINES=%DEFINES% -DUSE_PERSISTENT_COOKIES +if not "%OS%" == "Windows_NT" goto next11 +SET DEFINES=%DEFINES% -DLY_MAXPATH=1024 +rem The following is unnecessary and causes the +rem compile to fail: +rem SET DEFINES=%DEFINES% -DUSE_WINSOCK2_H +:next11 +SET DEFINES=%DEFINES% -DNO_CONFIG_INFO +SET DEFINES=%DEFINES% -DSH_EX +SET DEFINES=%DEFINES% -DWIN_EX +SET DEFINES=%DEFINES% -D_WINDOWS +SET DEFINES=%DEFINES% -DUSE_EXTERNALS +SET DEFINES=%DEFINES% -DDIRED_SUPPORT +SET DEFINES=%DEFINES% -DDOSPATH +SET DEFINES=%DEFINES% -DHAVE_DIRENT_H +SET DEFINES=%DEFINES% -DHAVE_KEYPAD +SET DEFINES=%DEFINES% -DACCESS_AUTH +SET DEFINES=%DEFINES% -DNO_FILIO_H +SET DEFINES=%DEFINES% -DNO_UNISTD_H +SET DEFINES=%DEFINES% -DNO_UTMP +SET DEFINES=%DEFINES% -DNO_CUSERID +SET DEFINES=%DEFINES% -DNO_TTYTYPE +SET DEFINES=%DEFINES% -DNOSIGHUP +SET DEFINES=%DEFINES% -DNOUSERS +SET DEFINES=%DEFINES% -DLONG_LIST +SET DEFINES=%DEFINES% -DDISP_PARTIAL +SET DEFINES=%DEFINES% -DUSE_SOURCE_CACHE +SET DEFINES=%DEFINES% -DUSE_PRETTYSRC +SET DEFINES=%DEFINES% -DWIN32 +if not "%USE_ZLIB%" == "YES" goto next1 +echo *** Using ZLIB +SET DEFINES=%DEFINES% -DUSE_ZLIB +:next1 +if "%LIBRARY%" == "PDCURSES" goto else1 +echo *** Using SLANG +SET DEFINES=%DEFINES% -DUSE_SLANG +goto endif1 +:else1 +echo *** Using PDCURSES +SET DEFINES=%DEFINES% -DFANCY_CURSES +SET DEFINES=%DEFINES% -DCOLOR_CURSES +SET DEFINES=%DEFINES% -DPDCURSES +SET DEFINES=%DEFINES% -DUSE_MULTIBYTE_CURSES +:endif1 + +if not "%1" == "src" goto else + cd src + goto src +:else +if not "%1" == "link" goto endif + cd src + goto link +:endif + +SET INCLUDES=-I. -I..\..\.. -I..\..\..\src + +set CFLAGS=-g %INCLUDES% %DEFINES% +set COMPILE_CMD=%CC% -c %CFLAGS% + +cd WWW\Library\Implementation +erase *.o + +%COMPILE_CMD% HTAABrow.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTAAProt.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTAAUtil.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTAccess.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTAnchor.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTAssoc.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTAtom.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTBTree.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTChunk.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTDOS.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTFile.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTFinger.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTFormat.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTFTP.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTFWriter.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTGopher.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTGroup.c +if errorlevel 1 PAUSE + +%COMPILE_CMD% HTLex.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTList.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTMIME.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTMLDTD.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTMLGen.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTNews.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTParse.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTPlain.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTRules.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTString.c +if errorlevel 1 PAUSE + +%COMPILE_CMD% HTStyle.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTTCP.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTTelnet.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTTP.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTUU.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTWSRC.c +if errorlevel 1 PAUSE +%COMPILE_CMD% SGML.c +if errorlevel 1 PAUSE + +ar crv libwww.a *.o + +if errorlevel 1 PAUSE + +cd ..\..\..\src\chrtrans +erase *.o + +SET INCLUDES=-I. -I.. -I..\.. -I..\..\WWW\Library\Implementation +SET CFLAGS=-g %INCLUDES% %DEFINES% +SET COMPILE_CMD=%CC% -c %CFLAGS% + +%COMPILE_CMD% makeuctb.c +if errorlevel 1 PAUSE +%CC% -o makeuctb.exe makeuctb.o +if errorlevel 1 PAUSE + +call makew32.bat +if errorlevel 1 PAUSE +cd ..\ + +:src +SET INCLUDES=-I. -I.. -I.\chrtrans -I..\WWW\Library\Implementation +SET CFLAGS=-g %INCLUDES% %DEFINES% +SET COMPILE_CMD=%CC% -c %CFLAGS% +SET PATH=..\WWW\Library\Implementation;%PATH% +erase *.o + +%COMPILE_CMD% DefaultStyle.c +if errorlevel 1 PAUSE +%COMPILE_CMD% GridText.c +if errorlevel 1 PAUSE +if not exist TRSTable.c goto notrstable +%COMPILE_CMD% TRSTable.c +if errorlevel 1 PAUSE +:notrstable +if not exist Xsystem.c goto noxsystem +%COMPILE_CMD% Xsystem.c +if errorlevel 1 PAUSE +:noxsystem +%COMPILE_CMD% HTAlert.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTFWriter.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTInit.c +if errorlevel 1 PAUSE +%COMPILE_CMD% HTML.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYBookmark.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYCgi.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYCharSets.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYCharUtils.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYClean.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYCookie.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYCurses.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYDownload.c +if errorlevel 1 PAUSE + +%COMPILE_CMD% LYEdit.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYEditmap.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYexit.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYExtern.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYForms.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYGetFile.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYHash.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYHistory.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYJump.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYKeymap.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYLeaks.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYList.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYLocal.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYMail.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYMain.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYMainLoop.c +if errorlevel 1 PAUSE + +%COMPILE_CMD% LYMap.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYNews.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYOptions.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYPrettySrc.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYPrint.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYrcFile.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYReadCFG.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYSearch.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYShowInfo.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYStrings.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYStyle.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYTraversal.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYUpload.c +if errorlevel 1 PAUSE +%COMPILE_CMD% LYUtils.c +if errorlevel 1 PAUSE +%COMPILE_CMD% UCAuto.c +if errorlevel 1 PAUSE +%COMPILE_CMD% UCAux.c +if errorlevel 1 PAUSE +%COMPILE_CMD% UCdomap.c +if errorlevel 1 PAUSE + +:link +if not "%LIBRARY%" == "PDCURSES" goto else2 +SET LIBS=-L..\WWW\Library\Implementation -lwww -lpdcurses +goto endif2 +:else2 +SET LIBS=-L..\WWW\Library\Implementation -lwww -lslang +:endif2 + +SET LIBS=%LIBS% -lwsock32 -luser32 + +if not "%USE_ZLIB%" == "YES" goto else4 +SET LIBS=%LIBS% -lz +:else4 + +%CC% -g -o lynx *.o %LIBS% +if exist lynx.exe ECHO "Welcome to lynx!" diff --git a/CHANGES b/CHANGES new file mode 100644 index 0000000..0527d6c --- /dev/null +++ b/CHANGES @@ -0,0 +1,10199 @@ +-- $LynxId: CHANGES,v 1.1198 2024/05/31 22:09:16 tom Exp $ +=============================================================================== +Changes since Lynx 2.8 release +=============================================================================== + +2024-05-31 (2.9.2) +* modify makefile.in install-help to only remove files, leaving existing + directories and symbolic links -TD +* in POSIX environments, check paths for external programs to ensure that they + are executable, non-empty files -TD +* adapt special case of gzip decompression in 2.8.9dev.12 to brotli, to handle + downloads from websites which support "br" (report by Rajeev V Pillai) -TD +* updated configure macro CF_ANSI_CC_C, to reset flags on test-failure, to + keep options from accumulating. +* build-fix for NetBSD and Solaris system curses libraries, removing an ifdef + added in 2.9.0dev.11 (report by Nelson Beebe) -TD + +2024-04-15 (2.9.1) +* add samples/mhtml-tool, from + https://github.com/ThomasDickey/mhtml-tool + which can use Lynx to display an "mhtml" file, e.g., foo.mht by + mhtml-tool -e lynx foo.mht +* add --with-libintl-prefix configure option (from patch for tin by Urs + JanBen) -TD +* improve bookmark file-updates, allowing for users to add HTML markup + (report by TG) -TD +* remove obsolete option --with-included-gettext -TD +* override $BUILD_CPPFLAGS and $BUILD_LIBS when not cross-compiling, to help + build Lynx with PDCurses -TD +* re-order configure check for _XOPEN_SOURCE, etc., to reduce compiler warnings + with gcc 14 -TD +* add configure check for ssp library, needed in some MinGW configurations -TD +* fixes for saving a UTF-8 bookmark title -KH + + correct comparison of Windows codepage values when setting + DisplayCharsetMatchLocale, to handle UTF-8 + + reset the internal state of UCTransToUni() before using it for bookmark + title check. +* update/cleanup test-packages -TD +* correct logic in HTMLGen.c where XMP was not treated as a preformatted + style, causing NNTP headers to be formatted (report by Marco Moock) -TD +* fix special case of main status line on a page without links, broken by fix + for UBSAN/ASAN issue -TG + +2024-01-15 (2.9.0) +* change version of OpenSSL used in Windows installers to 3.x and 1.1.1x, + for new/old respectively -TD +* fixes for UBSAN/ASAN issues with clang on Windows -GV +* modify install-doc rule to use relative path for symbolic links -TD +* add support for gopher's hURL functionality (patch by Viatrix). +* allow ^G interrupt to end read from a stalled connection without exiting + Lynx (Debian #1033423) -TD +* allow the ^S keymap to be disabled in the configuration file, e.g., + KEYMAP:^S:UNMAPPED + (report by TG) -TD +* repair docs/OS-390.announce and docs/README.jp -TD +* make the test-files non-empty, to appease some packaging tools -TD +* check for getpwuid(), use in preference to deprecated cuserid() -TD +* modify curses initialization to permit ^S built-in keymap to work without + needing external stty changes -TD +* correct ifdef for LYmsec_delay() (report by Alexander Arkhipov) -TD +* add a NUL after "/" in the SGML parser when the next character is ">", to + make PRETTYSRC view display correctly (report by "Dima") -TD +* trim some obsolete style code for NeXT -TD +* improve responsiveness with respect to SIGWINCH (report by Mark Zaharov) -TD +* improve check for MAX_URI_SIZE -TD +* improve check for UTF-8 character encoding in XML Text Declaration (report by + Lennart Jablonka) -TD +* fix for decoding utf-8 in CDATA sections (patch by Hiltjo Posthuma) +* treat HTTP 308 permanently redirected the same as HTTP 301 permanently moved + (Debian #1041686). +* formatting fixes for manpage (Debian #1037353). +* change defaults in configure script to use compression -TD +* modify HTChunkPutb2() to avoid passing a zero-size or null pointer to + memcpy() -TD +* correct loop in fill_addrinfo() which adds sizes of struct addrinfo's found + in getaddrinfo() call; the 2.8.8dev.15 change did not update the pointer to + the struct addrinfo's (Redhat #2185402) -TD +* modify configure script to reduce implicit-function warnings -TD +* add viewport meta-tag to documentation/test files -TD +* update config.guess (2023-08-22), config.sub (2023-09-15) +* update ro.po from + http://translationproject.org/latest/lynx + +2023-01-08 (2.9.0dev.12) +* add a rewind() call before reading existing bookmark file opened for append + with read/write, to ensure that the file-position is at the beginning of the + file (report by Klatt Volkmar) -TD +* improve compiler-warnings in configure script checks -TD +* fix a few ifdef'ing problems (prompted by discussion with Klatt Volkmar) -TD +* fix spelling errors found with codespell (report by Jens Schleusener) -TD +* update eo.po from + http://translationproject.org/latest/lynx + +2022-12-28 (2.9.0dev.11) +* update ncurses/lynx homepage URLs to deprecate ftp -TD +* modify configure script to reduce implicit-function warnings (report by + Florian Weimer) -TD +* update configure script to work around regression in grep 3.8 -TD +* add some consistency-checks to dtd_util to make it easier to use -TD +* improve formatting of bookmark file, e.g., to fix warnings by tidy -TD +* correct workaround for asan2 report of overlapping strcpy (report by KH) -TD +* amend fix for Debian #738121; URL-encoded "?" in HTFile.c corresponds to an + actual "?" in a file path (report by TG) -TD +* before calling delscreen, delete the private working windows in case delwin + invalidates those (Debian #1015756) -TD +* add presentation type for xhtml, related state information to better handle + things such as ", prevented a match + on the -TD +* do not free adult_table[] atexit - it should be perfectly empty after + free'ing all HText's. (There is an error if it is not empty at exit) -LP +* unnamed child anchors (`children_notag' list) now use HText memory pool. + Links properly deleted when reparsing the document -LP +* Use less memory for documents with many anchors: most anchors are never + visited, just stored for the reference. So fill in adult_table[] with + HTParentAnchor0 (36 bytes size) instead of full HTParentAnchor (~200 bytes). + HTParentAnchor now allocated on demand, nearly 1:1 to HText. [more comments + in HTAnchor.h, changes located in HTAnchor.c] -LP +* HTParentAnchor0 stores its hash value, to avoid calling HASH_FUNCTION twice + on the same anchor (Re: HTAnchor_delete()) -LP +* fix a potential out-of-bounds bug in HTBEquivalent() -LP +* change strrchr() calls to strchr() in a few src/*.c file when parsing + "#fragment" left-to-right -LP +* modify HTFWriter_abort() to remove file on error -IZ +* added hot.paste style which puts a right-arrow at the UR corner (which is + currently unused). Clicking on it initiates a GOTO to the current selection + (same as PASTE_URL action). The hot.paste style is disabled unless STYLES + and CUT_AND_PASTE are both enabled. Disabled and the user defines hot.paste + in the ".lss" file -IZ +* modify LYK_PASTE_URL case in LYMainLoop.c to allow pasting URLs of the form +
and "" to lynx. This is useful when it is not easy to + choose address without the surrounding "<>" or "" -IZ +* make a minor memory saving (circa 15%) for table processing, improve yet + another case of "ladder" (as in the top of google results), and fixes one + case of "wrong indentation" (elements of a table which contained
+ were made too wide) -IZ +* modify Stbl_trimFakeRows() to compensate for 2.8.5dev.15 changes to + Stbl_addRowToTable() from 2.8.5dev.15 which caches shrinking cell arrays in a + pool. It did not take into account reallocation of the same data, e.g., in + Stbl_reserveCellsInRow(). For example: + http://camden-sbc.rutgers.edu/FacultyStaff/Directory/default.htm + (report by Patrick Ash) -IZ +* add FORCE_COOKIE_PROMPT setting to lynx.cfg, allowing for manipulation in the + options menu and (if LYNXRC_ENABLE is set) via the .lynxrc file. This lets + the user decide whether to ignore prompting for cookies with invalid syntax. + If the prompts are ignored, a corresponding message is displayed -TD +* add FORCE_SSL_PROMPT setting to lynx.cfg, allowing for manipulation in the + options menu and (if LYNXRC_ENABLE is set) via the .lynxrc file. This lets + the user decide whether to ignore prompting for questionable aspects of + an SSL connection. If the prompts are ignored, a corresponding message is + displayed -TD +* change select() calls to use the expected 1+descriptor value documented for + that function rather than FD_SETSIZE. It is possible that some very old + or unique platform would not work, but this is more efficient (discussion on + lynx-dev) -TD +* modify logic for S_litteral case in SGML_character() to recover from spaces + between the '<', '>' and the corresponding end of the tag, e.g., + + < /style > + < /style > + match "" (report by TH) -TD +* correct a check in HText_trimHightext() for the last line of the display, + which left unhighlighted the portion of a multi-line anchor which fell in + that place (report by TH) -TD +* correct an off-by-one in redraw_lines_of_link() which left the link on the + last line on a page highlighted when moving the cursor up (reported by Morten + Bo Johansen) -TD + +2003-04-27 (2.8.5dev.15) +* change definition of docdir1, helpdir1 to avoid using ksh-semantics (see + 2.8.5dev.2) -TD +* update ja.po, uk.po, zh_TW.po from + http://www.iro.umontreal.ca/contrib/po/maint/lynx/ +* improve layout of TRSTable.c, reducing "laddering" effect, where cells in + different columns do not overlap by lines -IZ +* modify SGML_new() to allow display charset-switching when reloading a + document -IZ +* fix a buffer-size in LYK_PASTE_URL case in LYMainLoop.c -IZ +* various improvements to load-time for TRSTable.c -IZ + As a test: + . + This is a simple table with 2 columns, one with bold contents, another with + italic one. The total number of rows is 500K. With the patch and an + acceptable malloc(), lynx should use the working set of about 110M to show + the table. On my system with 128M memory, this leads to only 4M of the + process space swapped. +* add case LYK_TO_CLIPBOARD to HTCheckForInterrupt (not a good place), to allow + COPY command to work during download (copying the location to clipboard). + The best thing would be to get the location *after* redirects, but this will + require some additional work -IZ +* add popen-based support for cut/paste. This is a slightly reworked patch to + GNU readline. If RL_PASTE_CMD and RL_CLCOPY_CMD are defined in the + environment, lynx will use them as commands to do cut&paste. The simplest + such commands could just store/retrieve things from + /tmp/.clipboard_user-name; more advanced ones could use X clipboard -IZ +* modify yawerty_kb.h to map U+0411 and U+0431 to 'B' and 'W' positions + respectively. The map contained U+0412 and U+0432 at those positions, + which are duplicated at other positions -IZ +* fix HTLoadFinger() which was miscasting const data -TD +* update Subir Grewal's Lynx links URL to the newest location + http://www.subir.com/lynx.html -TD +* add configure option --with-gnutls, to allow lynx to be built with gnutls. + Used gnutls 0.8.6 on Redhat 8.0 to login at yahoo (gnutls is not very + portable, so this is an experimental option) -TD +* modify loop in HTInitProgramPaths() to convert enum ProgramPaths to an + integer, to accommodate HPUX 11.22 compiler (report by JES) -TD +* amend change in dev.13 to HTParse() to escape spaces, to exclude lynx's + internal URL types such as lynxprog (report by P.J.Walsh) -TD +* modified ifdef's to enable -connect_timeout option for DJGPP -GV + In particular, in LYUtils.c, undef "select" in case Lynx is compiled with + curses (and not S-Lang). Watt-32's select_s cannot be used on a + stdin handle, so one must undef it and use DJGPP's select(). +* modify ifdef in HTCheckForInterrupt() to work with MingW and PDcurses -GV +* add version information for the macros in aclocal.m4 (request by Lars + Hecking) -TD +* modify file-upload to use actual binary-data rather than base64-format -TD +* generate unique boundary for multipart data in HText_SubmitForm() -TD +* reorganize HText_SubmitForm(), maintaining post data using bstring's -TD +* modify HText_SubmitForm() to add field name for the fake coordinate pair + when formatting a multipart submit (report by Peter Pilsl + ) -TD +* change post_data to a bstring; implement functions and macros for + manipulating bstring data. This allows post_data to maintain embedded nulls, + e.g., for file-upload -TD +* fix ifdef's for -TD +* fixes for file upload -IZ + + modify logic for headers use write them even if MultipartContentType was + not set. + + change logic for base64 to be used ONLY if \0 was found. + If a "strange" char is found, only change "text/plain" to + "application/octet-stream". +* change a couple of _user_message() calls to HTUserMsg2() calls so their + content is saved in the "Messages" buffer -IZ, TD +* undo 2002-11-11 SGMLFindTag optimization (problem with color styles, + reported by IZ). Optimize the function by storing the previously found tags. + Also use my_casecomp() to decrease AS_casecomp() calls by testing the + first character manually -LP +* optimize HTStyle comparison: just compare numbers from enum. + It was previously implemented as a strcmp comparison with a fixed string. + Used in a very inner loop, in HTML_put_character() -LP +* LYEnsureAbsoluteURL() now absorbs LYFillLocalFileURL() call -LP +* optimize LYLegitimizeHREF() -LP +* in HTML.c, revise href resolving logic. HTAnchor_findChildAndLink now + resolves href with respect to BASE internally; HTParse incorporates + LYFillLocalFileURL call (after the parsing, and only when the related string + is not empty and parse includes access, host, path and punctuation). This + removes all LYFillLocalFileURL and most HTParse calls from HTML.c and makes + code more consistent. (Previously, functions were called in a different order + for document with/without BASE, which had the side effect in some cases, + e.g., href="c:" on a DOS machine was resolved properly with _any_ base, and + badly broken without:) -LP +* add/use HTParseALL macro to simplify coding -LP +* revise "internal links" logic (read KW 1997-11-03 notes, before v2.8). + In HTML.c and HTAnchor.c, internal links code affects only parent lookup + in the adults table (more correct in case of post data), now a mainline: + we omit "#ifndef DONT_TRACK_INTERNAL_LINKS" condition in the two files. + In HTML.c, avoid using internal links for unrelated `src=' attributes + (BGSOUND_SRC, FRAME_SRC, IFRAME_SRC, OVERLAY_SRC, EMBED_SRC links:) -LP +* refine HTAnchor_delete() vs deleteLinks() mutual recursion logic - LP +* change ALIGN_SIZE in GridText.c to sizeof(double), which is probably more + portable than "8" -LP +* modify a syslog() call to guard against possible '%' in its parameter -TD +* remove extra quotes from calling HTMake822Word() for form boundary names + (addresses bug report for fastmail.fm by P.J.Walsh) -TD + +2003-02-04 (2.8.5dev.14) +* correct a missing ">" at the beginning of page sent as response to mailto -TD +* simplify (clarify) anchor structure: links now moved from HTAnchor to + HTChildAnchor (the only place they were used). By this we avoid unneeded + casting in calls to HTAnchor_followMainLink, HTAnchor_followTypedLink, + deleteLinks. [GridText.c, HTML.c, LYList.c, HTAnchor.c] -LP +* as of 1998-11-21 "workaround for multiple anchors in the same (invalid) HTML + document with the same NAME and different destinations (HTAnchor.c) - KW", + along with skipping HTAnchor_link() call in this case now, we realize that + HTChildAnchor may have only a single link. (Previously implemented by + mainLink and links list). This simplifies HTAnchor.c -LP +* simplify HTChunk.c -LP +* optimize LYRemoveNewlines() and LYRemoveBlanks() -LP +* check for no common name (CN) in certificate when connecting via SSL, fixes + a SIGSEGV with + https://web-shokai.tokyo-denwa.net/ + (patch by Hataguchi Takeshi) +* add uk.po (Ukrainian) from + http://www.iro.umontreal.ca/contrib/po/maint/lynx/ +* modify HTList_linkObject to avoid an infinite loop in HTList_unlinkObject due + to relinking some node several times, corrupting the previous list chain -LP +* increase ATEXITSIZE to 50, 40 was not enough -TD +* ifdef-out call to Cygwin_Shell() in LYMainLoop.c, which does not work + properly for some environments (report by Corinna Vinschen + , forwarded by Frederic L W Meunier) -TD +* correct a bug in HTAnchor_findChildAndLink() introduced in dev.13 handling + USEMAP, e.g., + http://www.sendas-delivery.com.br/topo_sendas.asp + (reported by Frederic L W Meunier) -LP +* minor fixes for K&R compiler on SunOS: prototype of HTDOS_slashes(), + definition of LYLeakSequence -TD + +2003-01-22 (2.8.5dev.13) +* change new memory-allocation in HTString.c and GridText.c to provide pointers + to data aligned to the host's pointer-size, to work on Tru64 where this + happens to be 8 -TD +* resync ".po" files using msgmerge -TD +* remove quadratic complexity from insert_blanks_in_line() usage with large + tables (Stbl). It occasionally cleans up split_line() a bit. CPU load + anomaly reported by BL -LP +* ALLOC_IN_POOL, POOL_NEW, POOL_FREE macros now became functions, suggested by + BL -LP +* define HAVE_ALLOCA for djgpp fixed-configuration -LP +* add command-line option (--nested-tables) to help in testing this feature -TD +* add command-line option (--find-leaks) to disable the memory leak checking + code, allowing one to build an executable which is useful for both normal + and leak-checking (request by Frederic L W Meunier) -TD +* improve performance of HTParse() for very long strings -LP +* fix memory leak in HTFileSaveStream() -LP +* further optimization in HTAnchor.c - save 3 mallocs per HTChildAnchor by + using new HTList_ functions: HTList_linkObject(), HTList_unlinkObject(), + HTList_unlinkLastObject() which utilize external memory, no malloc/free -LP +* modify "make install-help" rule to avoid warning message about keystrokes + subdirectory (report by Martin Mokrejs) -TD +* optimize !HText_TrueLineSize() expressions as HText_TrueEmptyLine() -LP +* optimize is_url(), rewriting it as case-statements to avoid unnecessary + comparisons, make similar optimization in HTParse() -LP, TD +* corrected logic in is_url() where the "://" was not necessarily checked in + the proper position - TD +* for color-style configuration, add a link to lynx.lss from LYNXCFG: -TD +* simplify setup of internal pages with new function InternalPageFP() -TD +* modify parsing of refresh-URL to strip single quotes, to handle + http://tovar.yandex.ru/ + (reported by LP) -TD +* investigated conflict between NSL_FORK and _WINDOWS_NSL ifdef's for Cygwin + configuration in HTTCP.c; left them as-is since #undef'ing _WINDOWS_NSL in + that case causes problems connecting (feedback by Frederic L W Meunier) -TD +* corrected an off-by-one error in computing the location of the bottom line + for mouse input in PDCurses configuration which made that area ignore mouse + clicks. Merged almost-identical cases for mouse-input for NT/Windows95 -TD +* ifdef'd out (USE_CURSES_PAIR_0) the ASSUMED_COLORS logic for the PDCurses + configuration (reports by DK) -TD +* LYSetHiText(), LYAddHiText(), and LYClearHiText() use HText memory pool -TD +* add atexit-cleanup for history stack, removed incomplete code for this from + cleanup(), since that gave misleading results in leak-checking. Fix a few + small leaks as well (reported by LP) -TD +* modify cleanup() to leave the trace file open if checking for leaks -TD +* add some simple statistics to summary in Lynx.leaks report -TD +* add malloc-sequence number to Lynx.leaks report, to help with debugging -TD +* fix memory leaks in nested-tables logic, which did not free subtable data + if there was an enclosing table (reports by Frederic L W Meunier) -TD +* adapted change by LP to allocate HTLine's from memory pool -TD +* move fallback definition of MAXHOSTNAMELEN from HTFTP.c to www_tcp.h so it + can be used in HTTCP.c (Debian #140682) -TD +* improved configure script checks for ncurses -TD +* correct description of XLOADIMAGE_COMMAND in lynx.cfg (report by Mats + Peterson ) -TD +* fix configure script so it does not compute basename of system mailer when + none was found. Add check in LYMail.c, LYPrint.c to avoid using system + mailer when it is not configured (report by Frederic L W Meunier) -TD +* update several po files (da.po, de.po, et.po, hu.po, sv.po, tr.po) from + http://www.iro.umontreal.ca/contrib/po/maint/lynx/ +* correct logic in recent HTAnchor_findChildAndLink() changes around internal + links and fragments; avoid unneeded reallocations by using HTParseAnchor() + instead of HTParse() -LP +* trim some fat from HTML_start_element(), case HTML_A -LP +* add optimized string functions StrAllocCopy_extra() [and paired FREE_extra()] + which store string size and never shrink; for heavily reallocated strings in + temp objects. Used in SGML.c for value[] fields currently -LP +* in HTUtils.h, FREE macro was unsafe if happen before 'else' -LP +* modify HTParse() to escape any spaces which remain from LYLegitimizeHREF() or + other sources (report by Peter Rasmussen ) -TD + +2002-12-18 (2.8.5dev.12) +* remove a check in LYMain.c for Cygwin's console, which does not work with + screen (report by Frederic L W Meunier) -TD +* undo line/pool logic, fixing a memory leak -LP +* changes proposed by Bela Lubkin, to optimize ALLOC_IN_POOL macro + substitution, pack bitfields in HTStyleChanges to make them more compact on + some systems -LP +* correct logic of do_check_recall(), broken in dev.9 cleanup of pathname + constants with LYIsDosDrive() (report by Frederic L W Meunier) -TD +* update makelynx.bat, built with slang configuration -TD +* turn on file-upload in makefile.msc -TD + +2002-12-01 (2.8.5dev.11) +* fix a typo in changelog date -TD +* add project version & date to lynx.cfg -TD +* document xxx_PATH variables in lynx.cfg -TD + +2002-11-11 (2.8.5dev.10) +* modify file-upload to provide content-type based on file-suffix. This is + needed to validate local html files with current the W3C validation service + webpage -TD +* modify file-upload to warn but permit the filename or file contents to be + missing (report by Clemens Fisher) -TD +* workaround for compiler bug in fix_httplike_urls() -BL +* change enumShowColor so SHOW_COLOR_NEVER is zero as in 2.8.3, which makes the + result from LYChoosePopup() match the enum values. This fixes a bug which + would make the slang configuration toggle back to color when accepting an + options screen (report by Sean McGuire and Carlton + Anderson) -TD +* add a null-pointer check in content_is_compressed() -TD +* in partial mode, load document with #fragment on the fly. Long awaited fix. + LYMainLoop_pageDisplay() now returns BOOL -LP +* calculate WWW_SOURCE once, it is now a constant, not a define -LP +* use malloc instead of calloc in several places, particularly in HTList + operations (each field initialized explicitly) -LP +* fix a few typos in samples/mailcap (Carlton Anderson + ). +* ifdef'd default_fg and default_bg for PDCURSES to be 15, since that agrees + with lynx's use of color names, and works around a bug exposed by the + ASSUMED_COLORS change from 2.8.5dev.9 (report by DK) -TD +* simplify pretty-source code in SGML.c using PUTS(), put_pretty_entity() and + put_pretty_number() -TD +* refine S_attr test in SGML.c to make pretty-source code handle the case where + a blank precedes the '='. The misplaced markers made lynx omit newlines from + the pretty-source view (report by LP) -TD +* rewrote HTStat() to ensure that it does stat() for files on Windows -TD +* HTTCP.c patch to make DJGPP/Watt-32 non-blocking connect in HTDoConnect(). + This allows pressing 'z' to abort connections. Removed extra _HTProgress() + for INET6; it overwrote previous progress message -GV, DK +* in SGML.c, element stack now use a pool of 10 elements to avoid most of + malloc/free calls -LP +* in HTParse(), use single alloca instead of three malloc/free pairs -LP +* in HTParse.c, avoid most strcasecomp calls in scan() - LP +* modify GridText.c to store lines, anchors, and forms in the same HText memory + pool as styles. This will optimize memory allocation/deallocation by 8Kb + units. The down side: lines in TRST mode will be stored twice. Some + structs are made a bit more compact -LP +* add DJGPP to SINGLE_USER_UNIX special cases -DK +* modify configure script to not strip the -g option from $CFLAGS if it was + present in the user's environment rather than autoconf adding it (report by + DK) -TD +* add --with-curses-dir configure script option -TD +* in SGMLFindTag, we translate string uppercase in-place and launch case + insensitive search, add SGMLFindUprTag() to cover the cases where the string + is readonly - LP, TD +* DJGPP build restored. MV_PATH was undefined long ago by mistake. Fix recent + DJGPP changes in HTTCP.c: move _resolve_hook few lines below, it will not + compile otherwise. Remove minor warnings -LP +* optimize parsing html with many relative links, href="#fragment" - + HTAnchor_findChildAndLink() and HTML_start_element(), case HTML_A: now avoid + significant overhead when link == HTInternalLink (e.g., resolving against + base, lots of reallocations, parent lookup, etc., all are useless). Two + functions affected. [HTAnchor.c, HTML.c]. The code works both with and + without DONT_TRACK_INTERNAL_LINKS symbol -LP +* optimize parsing of large html files - with thousands of anchors - LP + + remove quadratic complexity from split_line() usage [GridText.c]. + Because of some work with anchors on the last(=split) line, + the anchors list was traversed from the beginning for each output line. + Now we store last_anchor_before_split explicitly. + [According to gprof, split_line() weight decreased from 33% down to 4%, + with the following test file: 13,000 output lines, 3,100 anchors, ~800Kb] + + remove quadratic complexity from HTAnchor_findChild() usage [HTAnchor.c]. + HTParentAnchor::children list was traversed zillion times, now we + split it into a tree (named anchors , fast search required) + and a list (just a storage for the rest anchors, no search required). + [The same file, gprof shows HTAnchor_findChild() weight decreased + from 11% down to 0.1%] + + remove quadratic complexity when generating a 'l'ist page, + now traverse anchors list only once [LYList.c, GridText.c]. +* add a search method to HTBTree implementation - LP +* amend check for refresh-URL to avoid adding a link if the retrieved page is + compressed, since the link would be added to the compressed file, corrupting + it (reported by Michel SUCH and Karl-Heinz Weirich ) -TD +* strip parameters from refresh-URL -TD +* correct misspelled $LYNX_LOCALEDIR variable in LYMain.c (reported by Michel + Such) -TD +* change the install procedure for lynx.cfg to attempt to update the file with + the user's customizations if any, and to save the old configuration + information in a series, e.g., lynx.cfg-1, lynx.cfg-2, etc. -TD +* re-fix the problem with config.cache not being removed at the beginning of + the configure script - see 2.8.5dev.3 (report by Clemens Fisher) -TD + +2002-10-06 (2.8.5dev.9) +* improve ETA data shown in experimental read-progress (adapted from Debian + #117476) -TD +* modify -dump, -source and related options which set dump mode to also set + -nopause (report by Benjamin Pflugmann ) -TD +* correct indexing in LYStyle.c our_pairs[] array, which did not allow for use + of default colors -TD +* make ASSUMED_COLORS in lynx.cfg apply to normal curses implementations which + do not implement assume_default_colors() (prompted by discussion with + Bela Lubkin) -TD +* make permanent an ifdef from SH which provides for truncating a too-long + title with an ellipsis -HN +* gettext'ify a few overlooked strings in LYOptions.c -HN +* modify LYGetHostByName() for MSDOS/DJGPP/Watt-32 to enable terminating it by + pressing 'z' (patch by Gisle Vanem) +* update configure script macros for NLS to gettext 0.10.40 -TD +* add PRCS version/date to lynx.cfg -TD +* setup ifdef's for Unix-specific permissions checks to exclude single-user + systems such as OS/2 EMX, Cygwin and BeOS, which otherwise act like Unix + since we can run the configure script on those platforms -TD +* re-order some tests in the configure script to allow pdcurses' X11 port to + be recognized as supporting color and line-drawing characters -TD +* work around a Cygwin bug which causes subprocesses of a full-screen program + to dump core (perhaps reported by Frederic L W Meunier, but observed in + running lynx in a bash shell spawned from my directory editor) -TD +* fix an inequality in HTDirTitles() which made the "Up to" link omitted for + the first level of an ftp listing, making it awkward to visit the parent + directory if one first visited a subdirectory -TD +* change HTURLPath_toFile() to keep local URLs distinct from remote ones, so + win32 version will not display misleading drive letter on ftp listings -TD +* rewrote Home_Dir(), adding checks for "My Documents" on Windows 2000, and + ensuring that the resulting directory actually exists -TD +* change ifdef's in LYwaddnstr() to use waddstr() consistently, since + wide-character curses implementation treat the string in the given locale + anyway, just like the waddnwstr() call -TD +* reduce clutter with new macro LYIsDosDrive() -TD +* add ifdef's for OS/2 EMX to existing DOSPATH code which checks for drive + letter (Michel Such) +* modify definitions of IsOurFile() and OpenHiddenFile() to allow trace file + to be written when one already exists -TD +* use new macros LYSameFilename(), LYSameHostname(), LYIsNullDevice() to hide + platform-specific filename comparisons -TD +* change sed delimiter in configure script to use '%' consistently, to avoid + using '@', which may appear in AFS pathnames (report by Martin Mokrejs) -TD +* narrowed accommodation for paths with embedded blanks in LYLegitimizeHREF() + to exclude those containing newlines or tabs (report by Leslie Fairall for + http://www.realtor.com) -TD +* modified VMS build scripts to allow linking with OpenSSL, tested with + OpenSSL-0.9.6g using UCX network libraries. It is reported (by + ) that one can modify the scripts to also build + with Multinet's UCX emulation, but the machine I used for testing has only + UCX (comp.os.vms discussion with Christoph Gartmann + ) -TD +* modify UCSetBoxChars() to assume wide-character curses implementations can + draw boxes -TD +* reduce the number of strncasecomp() calls with associated constants by making + macros for the lynx internal URL types, e.g., isLYNXCGI() -TD +* modify logic that handles goto-fragment (e.g., G #foo) to update the URL + shown in the info page (Debian #113734) -TD +* reduce clutter using new macros findPoundSelector(), + restorePoundSelector() and function trimPoundSelector() -TD +* reduce clutter using functions for updating the strings in DocInfo, mainly in + LYMainLoop.c -TD +* renamed 'document' to 'DocInfo' -TD +* reduce clutter by using NonNull() macro consistently -TD +* define HistInfo struct in terms of document, to make it clearer -TD +* add verification of SSL server certificates. It requires a "cert.pem" file + or cert files in the "certs" subdirectory in your OpenSSL directory for CA + verification. The mod_ssl distribution includes a "ca-bundle.crt" that has a + good set of root certifying authority certs and works well for "cert.pem". + Adding custom CA root certs can be done by either putting them in the server + "cert.pem", or (for a normal user) copying "cert.pem", adding the cert, and + setting the SSL_CERT_FILE environment variable before running Lynx (Chris + Adams ). +* add REPLAYSECS config value to allow slowing command scripts down, for + testing -TD +* implement a "set" command for command-scripts, allowing the script writer + to manipulate the sleep-times for messages (prompted by discussion with + Ville Herva) -TD +* implement an "exit" command for command-scripts (Ville Herva) +* modify logic for -cmd_script to stop reading from the command script when + an end-of-file is detected (patch by Ville Herva ). +* fill in a few descriptions of restrictions for the help message, as well + as showing the on/off state of the "goto_xxx" restrictions -TD +* correct inverted logic of restrictions table which made "-restrict=default" + provide incorrect values for several items. This was broken in 2.8.4dev.19 + (reported by Jeff Long and RobertM ) -TD +* add environment variable LYNX_TRACE_FILE which, if given, overrides the + compiled-in value of Lynx.trace (or LY-TRACE.LOG). This specifies the + name of the trace file relative to the home directory -TD +* treat empty string for most environment variables, e.g., those specifying + a pathname, as null -TD +* add environment variable LYNX_LOCALEDIR to simplify configuration on OS/2 + EMX (from discussion with Michel Such) -TD +* add alias for charsets "ISO-8859-8-I" and "ISO-8859-8-E" to "iso-8859-8" + (Debian #152441, request by Atsuhito Kohda) -TD +* modify handling of HTML_SUP to always append '^'. It was checking if the + preceding character was a valid hexadecimal code (reported by HN and Steve + White ) -TD +* correct check for calling endwin() to allow for curses implementations + without newterm (report/patch by Brett Lymn). +* add koi8-r.html as a test for non-ANSI 8-bit displays -TD +* construct "Accept-Encoding" gzip/compress parameters based on whether lynx is + built with zlib and/or gzip/compress paths are defined. The latter is + assumed on Unix (by the configure script), though non-Unix environments may + lack those utilities (report by Roy Langford , + analysis by Frederic L W Meunier) -TD +* modify mouse support in slang configuration (Eduardo Chappa): + + Middle button takes you to the bookmarks file. + + Clicking on empty parts of the screen makes the screen scroll. This is + not 100% true, here are the caveats related to this: + + When you click in the first line of the screen pine goes back one screen, + the same happens when you click in the last line (this is normal Lynx + behavior, I did nothing in this respect). With this patch, intermediate + scrolling is enabled, which means that left clicking in different (empty + parts) of the screen may move you half a screen or two lines. The idea + is that close to the top you scroll more, close to the center you scroll + less and you scroll in the direction up or down according to which half + of the screen you click on. + + If you click on an empty part of the screen, Lynx changed its behavior + from doing nothing to moving the active link to the closest link near the + click. This is not disabled by this patch, instead, if a closest link is + not found, the screen will scroll, according to the position of the link. +* improve check in LYgetEnum() for ambiguous/abbreviated names in the lynx.cfg + file, e.g., to match the string "visited_links=first" without confusing it + with "visited_links=first_reversed" (report by vortex5 , + analysis by TH) -TD +* use StrAllocCopy() rather than strdup() in parse_style() (LYStyle.c) to + avoid false report from leak-checking (from report by Martin Mokrejs) -TD +* share SSL handle between HTTP.c and HTAAUtil.c so that call of + HTAA_shouldRetryWithAuth() from HTLoadHTTP() updates the handle used in that + function. This makes lynx able to get the user/password prompt for + https://enter.nifty.com/iw/ -TH +* fix a highlighting problem in view-source mode, which left the final + character of the target unhighlighted -TH +* modify LYStringToKeycode(), which is used by -cmd_script option to decode + characters, to handle hexadecimal codes written with -cmd_log option from + dev.8 changes (reported by Gleb V Kotelnitskyy ) -TD +* modify ifdef's in LYCurses.h to implement underline-links for slang + configuration (report by TH) -TD +* modify checks with WEXITSTATUS() and similar "result" macros to ensure that + they consistently use corresponding "test" macros such as WIFEXITED() -TD +* add a null-pointer check in GridText.c to cover a problem with + http://209.1.58.86/store/ + (reported by Walter Ian Kaye) -TD +* add some more CTRACE's to LYCookie.c to help diagnose LV problem report -TD +* updated nl.po (patch by Pieter-Paul Spiertz ) -JES +* new (cs.po, hu.po, tr.po) and updated (da.po, et.po, it.po, ru.po, sv.po) po + files from http://www.iro.umontreal.ca/contrib/po/maint/lynx/, used msgmerge + to align with 2.8.4's lynx.pot and corrected some minor issues highlighted by + check_po. Checking for a newer version of check_po (to handle patterns + such as "%1$s") found none, but noticed a comment in gettext mailing list + stating that msgfmt does checking. Comparing with "msgfmt -c -v", found + that it does useful checks, but misses about 1/4 of what check_po finds. + Will use both -TD +* escape blanks and other non-7bit graphic characters in startfile and similar + addresses to guard against interpreting the address as multiple lines + during a GET, etc (report by Ulf Harnhammar ) -TD + +2002-05-28 (2.8.5dev.8) +* updated makelynx.bat (Victor Schneider). +* recognize charset value in meta description even if content-type is not + given, in LYHandleMETA() -VH +* remove ifdef that disabled home/end keys with Cygwin configuration -DK +* fix a problem when whereis target string, which includes Japanese and is top + of the second line in the link string, is in the current link (patch by + Hataguchi Takeshi). +* fix a problem with highlighting Japanese string (patch by Hataguchi Takeshi). +* modify LYDownload() to ensure that local addresses under DJGPP using the + special form of path beginning "/dev/" are passed to external programs + without stripping the initial slash from the path. This special form of path + will be understood only by other DJGPP programs. Addresses of the form + "/dev/x/" are equivalent to the DOS path "x:\". Addresses of the form + "/dev/env/VARI" are equivalent to the environment variable "VARI" -DK +* modify remove_bookmark_link() to assume that OS/2 EMX does not allow rename + of a file overwriting an existing one -IZ +* recognize local .php files as HTML files (patch by Karl Eichwalder + ). +* change LYCurses.c to not redefine gettext(), and use ScreenClear() instead + of clrscr() for DJGPP -DK +* the DJGPP port of Lynx once used to compile with DJ Delorie's tcp/ip library. + It is no longer the case; Watt-32 is required. Changed to assume WATT32 is + defined when DJGPP (or __DJGPP__) is defined (patch by Gisle Vanem). +* change LYKeycodeToString() to provide a default translation for characters + which are not key-symbols, etc., so they may be used in command scripts + with the -cmd_script option (reported by Christoph Fabianek) -TD +* new po files (ca.po, et.po, it.po, zh_TW.po) and updated de.po from + http://www.iro.umontreal.ca/contrib/po/maint/lynx/, used msgmerge to align + with 2.8.4's lynx.pot and corrected some minor issues highlighted by + check_po script by Stefan Hundhammer -TD +* some cleanup/restructuring of HText_SubmitForm(), incomplete - toward + implementing correct MIME boundary -TD +* revalidate user's guide and related files using W3C validator via file-upload + facility -TD +* simplify some loops in GridText.c using new function next_anchor() -TD +* quote field-names used when submitting form-data as suggested in RFC 2068 + (report by Lieven Tomme ) -TD +* remove configure-check for mkstemp(), which is redundant given that lynx + writes temporary files in a directory which is not readable by other users. + On more than one system (e.g., Solaris), mkstemp() is not usable in the + manner we attempted, since it does not necessarily choose a distinct name if + the previously-chosen filename no longer exists (report/analysis by PG) -TD +* correct a typo in configure macro CF_HEADER_PATH which told the script to + look for header files in the user's $HOME/lib rather than $HOME/include + directory -TD +* update config.guess, config.sub -TD +* add check for HTTP headers using Netscape extension "Refresh", and if found, + add a corresponding refresh-URL at the beginning of the document. Fixes + Debian #126723 -TD +* strip username from URLs used in an HTTP GET, and warn about this condition. + The example given was + "http://www.microsoft.com&item%3dq209354@212.254.206.213/1338825GHU_98.asp" + the text of which could mislead a user into believe it was an official site + (reported by Frederic L W Meunier) -TD +* add limit checks in HText_trimHightext() to fix an infinite loop visiting + this site (which contains a form with only hidden input fields): + 1- http://www.ibazar.com.br/ + 2- Click "Cadastro" and accept all cookies + 3- Click "[accepte.gif]" + (reported by Frederic L W Meunier) -TD + +2002-01-06 (2.8.5dev.7) +* ifdef'd new directory-sorting code to compile when configure --disable-dired + is specified -TD +* add (commented-out) definitions for building with OpenSSL in makefile.msc, + tested with OpenSSL 0.9.6c and Visual C++ 5.0 -TD +* correct call to HTGetLinkInfo() in follow_link_number() from 2.8.5dev.6 + changes to fix uninitialized pointer (report by PW) -TD + +2002-01-01 (2.8.5dev.6) +* add configure options to link with dbmalloc and dmalloc debugging libraries + which offer different features than --enable-find-leaks -TD +* restructured LYhighlight() and logic related to highlighted text using new + functions LYSetHilite(), LYAddHilite(), LYGetHiliteStr() and LYGetHilitePos() + to allow more than two lines to be highlighted in links (Debian #114062) -TD +* simplified some of LYCookie.c with new functions find_domain_entry(), + alloc_attr_value() and parse_attribute() -TD +* modify expansion of %s for WIN_EX EXTERN commands so that short names (used + when the command begins with an uppercase character) are not quoted, and use + backslashes. The normal %s expansion uses forward slashes and may quote the + name if it contains a blank -TD +* modify local directory sort by type to ignore leading '.' characters when + looking for filetype -TD +* modify logic of HTDirEntry() to avoid storing a trailing backslash (DOS-style + path separator) in the anchor URL for local directory entries (from report + by Hataguchi Takeshi) -TD +* amend change in 2.8.5dev.2 to HTLoadHTTP() to omit "Accept-Encoding: gzip" + ensuring that also -source or -dump option is used. Retesting excite.com + shows that it no longer matters, since the page was replaced by a short + javascript which is not sent compressed (request by Hataguchi Takeshi) -TD +* correct a missing definition for COMPRESS_PROG in the configure script + introduced by 2.8.5dev.5 changes (also noted by Stepan Kasal) -TD +* several fixes from Stepan Kasal : + + remove code in LYCurses.c which checks ttytype variable for "dec-vt" + prefix. EWAN, a decent telnet program for M$ Windows, sets $TERM to + dec-vt100. This is similar to vt102 but it is not appropriate to use vt100 + settings for it. ncurses has in its terminfo database an entry for + "dec-vt100|EWAN telnet's terminal" + The terminfo file is successfully opened when ncurses is initialised and + ttytype is set to the name mentioned above. After stripping "dec-" from + it, lynx ends up searching for + "/usr/share/terminfo/v/vt100|EWAN telnet's terminal" + which cannot cannot be found and lynx crashes. + + compress installed html files with the -9 option of gzip. + + improved install-help makefile rule +* fix some longstanding problems with the DOS port -DK + + fix inability to break out of a hung nameserver lookup or hung connection + attempt without aborting lynx entirely. Using the WATT-32 signal handler + for this seems to work well. Change the default compile option for DJGPP + to -DIGNORE_CTRL_C, causing lynx to ignore SIGINT, so CTRL-BREAK is + completely disabled. With this patch CTRL-C stops current actions without + quitting lynx. To have a way to abort lynx when necessary, bind ALT-X to + SIGQUIT. (The unix default of CTRL-\ is not bound to the same scan code on + different international keyboards, while ALT-X is familiar to DOS users for + existing programs). Because the WATT-32 signal handler was not available + to external programs, a patch to WATT-32 is necessary for the new lynx code + to work. The patch for WATT-32 is shown in the INSTALLATION documentation. + + added some fixes for "/" vs "\" handling in pathnames so that non-DJGPP + programs will see standard DOS pathnames when called. The mailer code was + ignoring the environment variable SHELL when calling the mailer, leading to + a lack of environment space for the mailer to work -DK + + add calls to _eth_release() and _eth_init(), which Gisle Vanem says should + not be necessary in this part of lynx. But this seems to fix get + intermittent hung nameserver lookup sessions after using CTRL-C. +* simplified a loop in HTConfirmCookie() and added CTRACE's to demonstrate that + this is working properly (addresses Debian #119751) -TD +* add some CTRACE's to curses screen initialization and resizing to provide + better diagnosis of problem reported by David Balazic + on HP-UX 11.00) -PG +* modify configure.in and aclocal.m4 to work with autoconf 2.52 patched with + ftp://ftp.invisible-island.net/autoconf/autoconf-2.52-20011227.patch.gz + (TD). +* modify configure macros CF_CURSES_CPPFLAGS and CF_NCURSES_CPPFLAGS to ensure + that the (n)curses.h header file is actually found, in case someone tries to + build lynx without having installed the development files (based on anonymous + posting on comp.os.linux.networking newsgroup) -TD +* update config.guess, config.sub from + http://subversions.gnu.org/cgi-bin/viewcvs/config/config/ + +2001-11-18 (2.8.5dev.5) +* modify prompt for file- or directory-name in rename/move operation to provide + a default based on the selected file/directory -TD +* add -DOK_OVERRIDE to makefile.msc, to allow rename/move of files in the local + directory editor -TD +* correct delay time for win32's HTAlert, etc., which was in milliseconds + rather than seconds -TD +* modify check for mbstate_t in CF_WIDEC_CURSES to define HAVE_MBSTATE_T if + that type is found, use this ifdef in LYwaddnstr() to make that compile + properly on Solaris 2.6 (report by PG) -TD +* disable SUPPORT_MULTIBYTE_EDIT ifdef for EBCDIC (i.e., NOT_ASCII) -PG +* collect names of lynx.cfg and .lynxrc variables into LYrcFile.h, to simplify + checks for matching spelling, e.g., against the values used in + LYOptions.c -TD +* modify dired support to allow sorting by a variety of things in addition to + the existing sort into files versus directories. This is saved in ~/lynxrc + as dir_sort_order -TD +* if --disable-full-paths is specified, do not use full path for SYSTEM_MAIL + (report by IZ) -TD +* modify configure script to reduce the number of xxx_PATH definitions compiled + into the code if --disable-dired was specified. Change configure script so + that dired support for chmod, copy, mkdir, and touch to use built-in + functions rather than external programs -TD +* modify configure script tests for program pathnames so that if full pathnames + are specified and the program is not found, no corresponding xxx_PATH symbol + is defined -TD +* eliminate duplicate LYGetEnum() in LYrcFile.c, using the variant from + LYReadCFG.c which allows abbreviations -TD +* change handling of tagsoup option in LYrcFile.c to invoke the corresponding + HTSwitchDTD() function, so saved tagsoup initializes properly from ~/.lynxrc + (report by Michel Such) -TD +* modify some tag-names in LYOptions.c to correspond to the names used in + lynx.cfg to make the corresponding names work properly when used in ~/.lynxrc + via ENABLE_LYNXRC settings (report by Michel Such) -TD + old new + ---------------------------- + assume_char_set -> assume_charset + show_scrollbar -> scrollbar + DTD_recovery -> tagsoup + show_rate -> show_kb_rate + user_agent -> useragent + ---------------------------- +* correct length passed by LYpaddstr() to LYwaddnstr(), which could be larger + than allowed -TD + +2001-11-08 (2.8.5dev.4) +* if file-upload code is configured, suppress message that indicates it is not + implemented, i.e., "[FILE Input] (not implemented)" -TD +* modify file-upload submission to send plain text if the file is entirely + printable text. Mime encoding is needed if the file contains nulls, etc., + but reportedly may confuse some hosts -TD +* suppress "charset=" clause on form submission if it is iso-8859-1 -TD +* move case for F_FILE_TYPE in HText_SubmitForm() to obtain original behavior + of fallthrough for F_SUBMIT_TYPE, F_TEXT_SUBMIT_TYPE, F_IMAGE_SUBMIT_TYPE to + translate their character set, etc. (report by KW) -TD +* revert 2.8.4dev.21 change to avoid truncating cookie path in LYSetCookie(). + The server that wouldn't work with the current lynx behavior is + identified as "Oracle_Web_Listener/4.0.8.2.3EnterpriseEdition" -DK +* modify LYLegitimizeHREF() change from 2.8.4dev.21 to eliminate newlines from + the HREF rather than converting them to spaces. This fixes a problem with + www.ebay.com which splits up HREFs with newlines. Changing the newlines to + spaces made the HREF no longer match, e.g., when it was built up from + a CGI script (report by Morten Bo Johansen) -TD +* add two test files for testing UTF-8, based on Markus Kuhn's demos + (quickbrown.html and utf-8-demo.html). These work with ncurses 20011103 + patch, for wide-characters except for combining characters (more work is + needed in ncurses). Tested with XFree86 xterm (patch #163) -TD +* modify select_multi_bookmarks() check for interrupt character to limit it to + "hard" interrupt characters such as ^G. This fixes a case where "z" would + have been treated as an interrupt character in advanced multibookmark mode + (reports by Michael Warner, HN, as well as Debian #111463) -TD +* modify SUPPORT_MULTIBYTE_EDIT logic in LYUpperCase() and LYLowerCase() to + check for a null character following an upper-128 code. This is more likely + to occur in EBCDIC, though the multibyte strings should not have a null at + this position in any case (report by PG) -TD +* for wide-character curses configuration, do not force repainting at the end + of display_page() -TD +* modify configure test for mkstemp() to check if that function returns + distinct values (report by Fr3dY indicates that AmigaOS has a broken version + of mkstemp() which always returns the same value) -TD +* modify LYwaddstr() to use wide-character curses functions to make UTF-8 + output work without relying upon side-effects of narrow-character functions. + Note that this relies on the user having set a UTF-8 locale, e.g., + en_US.UTF-8 -TD +* modify HText_appendCharacter() to not use utfxtra_on_this_line when compiling + with WIDEC_CURSES, since the curses library already does this adjustment -TD +* correct the following names in LYrcFile.c which were added to allow + ENABLE_LYNXRC lines in lynx.cfg to enable them to be saved in ~/.lynxrc + old new where-used + ------------------------------------- + DTD_recovery tagsoup (command-line option and lynx.cfg) + show_rate show_kb_rate (lynx.cfg) + user_agent useragent (command-line option) + ------------------------------------- + (report by Michel Such ) -TD +* modify LYRefreshEdit() to clear field before repainting (patch by Hataguchi + Takeshi) +* for CJK configuration, force clearing/repainting in HTUserMsg() (patch by + Hataguchi Takeshi) +* make HTInfoMsg() sleep condition consistent with other messages by using + LYSleepInfo() -TD +* reduce clutter with new function utf8_length() -TD +* replace !isascii(ch) with new macro is8bits(ch), to reduce clutter, fix some + sign-extensions and make it more portable -TD +* change some of the "#if" statements to "#ifdef", to work around broken + versions (2.96, 3.0.1) of gcc distributed with Mandrake 8.1 (though + reportedly this is due to Redhat): the -C option passes through comments as + usual, but some comments expand on preprocessor lines, which causes the + preprocessor to report an expression error. This prevented "make + install-help" from running, though the -C option is not needed for that. + However, lacking a working -C option makes the C preprocessor useless for + analyzing bugs -TD +* modify configure script to accept --with-screen=ncursesw, to build with the + wide-character version of ncurses -TD +* modify configure script to look for mkdtemp(), to quiet another bogus linker + message -TD + +2001-10-06 (2.8.5dev.3) +* add CF_MKSTEMP configure macro, from vile, to check for a working mkstemp(). + This will quiet some bogus warning messages in recent runtime support, but + (see 2.8.3) does not affect the security of temporary files in lynx -TD +* updated CF_PATH_SYNTAX configure macro, from vile, to handle leading "\\" in + a win32 pathname -TD +* for configurations that provide scrollbar, add a checkbox to the Options menu + to enable or disable it -TD +* modify LYGetHostByName() in HTTCP.c to use the threaded _WINDOWS_NSL code for + all Cygwin machines. The ability to interrupt nameserver lookup has not + worked in Win98 using the Cygwin port, despite defining _WINDOWS_NSL. It + looks like the threaded code was only for WinNT. At least under Cygwin, that + code also seems to work fine under Win98 -DK +* add FIELDS_ARE_NUMBERED as a possible value for DEFAULT_KEYPAD_MODE in + lynx.cfg as well as keypad_mode in .lynxrc -TD +* add NUMBER_FIELDS_ON_LEFT and NUMBER_LINKS_ON_LEFT to lynx.cfg, use these to + control where field- and link-numbering is placed. Caveat: there are some + cases where fields that do popup's are truncated, e.g., in the Options menu, + when right-alignment is used -TD +* support for DJGPP's two forms of file addressing, [a-zA-Z]:[/\\] and + /dev/[a-zA-z]/ -DK +* add samples/lynxdump script, to illustrate how to use lynx -dump with no + link references (prompted by discussion with LV) -TD +* add samples/keepviewer script, to illustrate how to retain a temporary file + for use in an external viewer -TD +* add ifdef's in is_url() to avoid recognizing URLs if they are disabled in the + given configuration, i.e., bibp, finger, ftp, gopher, news (report by + Frederic L W Meunier) -TD +* modify LYrefresh() to take into account whether a popup window exists, so + that a search prompt will not overwrite a popup. This bug was introduced by + the curses pads (reported by Felicia Neff and Fr3dY + ) -TD +* add note in keystroke_help.html about CTRL-V as literal-next (lnext) for + users who are unfamiliar with stty -TD +* add NcFTP-style ftp-URLs which are supported by Netscape and wget (request + by Martin Mokrejs) -TD +* add traces in LYReadCFG.c and LYrcFile.c to report lines which are not found + in the symbol table, to help diagnose when a user adds lynx.cfg information + to .lynxrc -TD +* define additional -trace-mask option, 8=config -TD +* modify LYtouchline() to avoid using wredrawln() for ncurses, since the + LYwin variable may be a pad much wider than the screen, which is not handled + properly (report by Karl Eichwalder ) -TD +* correct beginning of configure script, which was supposed to remove + config.cache, but did not, due to a misplaced line when it was added + 1998-06-04 (prompted by a report by Fr3dY that the + checks for srand/rand did not work) -TD + +2001-08-15 (2.8.5dev.2) +* several small fixes to HTFile.c to make directory listings work properly on + win32, e.g., stat'ing a directory with a trailing slash fails (reported by + Hataguchi Takeshi) -TD +* adjust definitions in LYCurses.h to get rid of slang-ifdef's for getyx() and + wmove() -TD +* change order of srandom/random versus srand48/lrand48 -DK +* patch to get the DJGPP port to use the configure script -DK + It seems to work well in the variations I have tried, including both PDCurses + and SLang. revised INSTALLATION for DOS, giving a URL for my DOS patch to + openssl. I dropped the reference to goto URL of the form + file:///dev/c/path/filename, since this only works in certain parts of lynx + (such as lynx.cfg). I'll try to get this working in the future. In fixing + makefile.in, I patched the sed script for converting the path to docdir. As + far as I can tell, however, from my examination of lynx.cfg, this isn't used + for any platform. Does this part of the sed script do anything? + Things still needing fixing for DOS: + + support for both forms of file addressing, [a-zA-Z]:[/\\] and + /dev/[a-zA-z]/. + + support for gzipped help files. This works with long file names in a DOS + box under Windows, but not in plain DOS, which doesn't allow double + extensions. + + better handling of local files in root directory. "file:///c:/" takes a + long time to work, but "file:///c:/." works fine. I haven't really looked + to see why. + + no ability to break out of hung nameserver lookups or http requests without + closing lynx with SIGINT. This is the biggest complaint I get by email. +* modify ifdef for myGetChar() in LYStrings.c to build with PDCurses 2.3 e.g., + to use a version which is modified for Japanese input (patch by Hataguchi + Takeshi) +* review LYSafeGets() calls, stripping newlines from a few places where they + were overlooked, and simplifying some places where LYSafeGets() would + normally return a buffer ended with a newline (prompted by a report by Brian + S Queen for LYTraversal.c) -TD +* correct reallocation-size in ProcessMailcapEntry() -TD +* modify HTLoadHTTP() to omit "Accept-Encoding: gzip" if command-line "-base" + option is given. This makes + lynx -base -source excite.com + work as expected. Otherwise, excite.com will transmit the document gzip'd, + and the ensuing logic in HTSaveToFile() would see the mime-type as gzip + rather than text/html, and not prepend the base URL (report by Kai Shih + ) -TD +* work around defect in move_anchors_in_region() and related logic by changing + default for nested-tables to FALSE when Lynx is not configured for + color-style. The problem is that when an anchor is shifted right by + nested-table logic, if it has a
near the beginning of a table cell and + it happens to be split across a line, its size will not be adjusted properly + (report by Hataguchi Takeshi) -TD +* correct logic used for trimming TEXTAREA introduced in 2.8.4pre.3, which did + not trim carriage-return characters if TRIM_INPUT_FIELDS was false. + (report by Hataguchi Takeshi) -TD +* correct a bug in search logic which happens with pages shorter than the + screen, due to improper starting-line value sent to search function. Fixed + by adding checks in www_search_backward() and www_search_foreward(), (report + by -Frederic L W Meunier) -TD + +2001-07-24 (2.8.5dev.1) +* modify GetChar() definition for PDCurses to ignore key-modifiers which are + passed back from getch() as if they were key codes. Those interfere with + shifted commands such as 'Q' -TD +* modify parse_style() function to operate on a copy of its parameter, to avoid + changing it. Otherwise, when parse_style() is executed as a side effect of + start_curses(), its data is modified and not valid on successive calls. + This bug existed prior to 2.8.4dev.17 -TD +* set return value of edit_current_file() to true if the file is edited. This + forces a reload for example if one edits the current html file, and is needed + to make PDCurses repaint the screen as well (report by Victor Schneider, + bug introduced in 2.8.4dev.21) -TD +* add ifdef for wresize() to accommodate FreeBSD 3.x which has resizeterm() but + not wresize(). Also, use a 'long' rather than 'attr_t'. These changes are + needed to build with the 1.8.6ache patches to ncurses (report by Matt + ) -TD + +2001-07-17 (2.8.4rel.1) +* remove comment in README.ssl directing people to + http://www.moxienet.com/lynx/, since that page is moot with 2.8.4 -DK +* add an ifdef in CF_CURSES_FUNCS configure macro to avoid confusing ncurses' + term.h with other versions -TD +* update URL for zlib -Frederic L W Meunier + +2001-07-14 (2.8.4pre.5) +* document CHARSETS_DIRECTORY and CHARSET_SWITCH_RULES in lynx.cfg -IZ +* add a fallback in _Switch_Display_Charset() if no CHARSETS_DIRECTORY was + specified -IZ +* ensure that config variable names in LYReadCFG.c are in alphabetic order, + though only the first character matters (report by IZ) -TD +* updated notes on DOS in INSTALLATION -DK +* modify ifdef in HTTP.c to build with configure --with-ssl --disable-news + (report by Frederic L W Meunier) -TD + +2001-07-10 (2.8.4pre.4) +* correct red/blue color swapping for PDCurses when built on Unix, which uses + X11 -TD +* correct order of checks for wrapping in www_search_forward() and + www_search_backward(), which would allow an infinite loop if there were no + anchors on the current page (report by Frederic L W Meunier) -TD +* add a missing chunk to reverted change of SGML_character() -NSH + + +2001-07-07 (2.8.4pre.3) +* review/add descriptions of new command-line options in lynx.man, lynx.hlp and + Lynx_users_guide.html -TD +* update da.po, ja.po, ru.po, sv.po from + http://www.iro.umontreal.ca/contrib/po/maint/lynx/ + (report by JES) -TD +* add command-line option -curses-pads which can be used to disable the + left/right scrolling logic. This is used for testing, e.g., the repaint + bug reported below -TD +* remove logic in lynx_force_repaint() which reset the window background, since + it does not work with the logic used to implement left/right scrolling. + Retested older versions of ncurses and did not find a case where this was + needed after all (report by IZ) -TD +* revert dev.21 change to SGML_character() S_equals case, which has undesirable + side effects regarding spacing around '=' (report by + ) -TD +* define additional -trace-mask option, 1=SGML -TD +* add -trim_input_fields command-line option and corresponding + TRIM_INPUT_FIELDS to lynx.cfg to suppress trimming of TEXT and TEXTAREA + fields in forms. This does not retain trailing blank lines in a TEXTAREA; + more work would be needed to do that (reported by VH, most browsers appear to + retain trailing blanks) -TD +* modify parsing of "" to allow "" comments + in SGML_character(). Though the HTML 4.0 spec is fairly clear, other + browsers (and some webpages) assume that " and comment -TD +* improve logic for nested-tables to handle cases such as www.tin.org -IZ + TRST ignores the horizontal alignment *inside* a multi-line cell of a table. + This limitation, in conjunction with the nested-tables modifications does not + work well when text with a horizontal alignment (e.g.,
will not be taken into + account and new links will be added below rather than above those end + tags. Instead, add a link to the Mosaic file in your Lynx file, and + to the Lynx file in your Mosaic file, so that you can access both files + with both clients. + +--------- + + SOCKSification and the -socks switch have not yet been integrated with + the slang library support. + +--------- + + There is an apparently broken version of select() in libcurses.a + of HP/UX 10.10. It also breaks tn3270, ncftp, emacs, and xemacs. + Using: + LIBS="-lc -lcurses -ltermcap \ + ^^^ + (i.e, adding -lc *before* the -lcurses) in the snake3 and snake3-slang + targets of the top level Makefile yields a usable image, but with + inappropriate video attributes on the Lynx displays (reverse video and + underscores on everything). Using "-lc -lHcurses" instead fixes the + ^^^^^^^^^ + video attributes but then the arrow keys are messed up. - Donald S. + Teiser (dsteis01@homer.louisville.edu) + NOTE: If HP fixes the problem or you come up with a better workaround, + notify the lynx-dev@nongnu.org list. + Updated NOTE (1996-09-02): A patch reportedly is available from HP to + fix the select() problem, so that "-lc" is no longer needed, but + the curses glitch is not yet fixed, and you should still include + "-lHcurses". + Updated NOTE (1997-02-03): The problems reportedly are fixed with + patches PHCO_8086 and PHCO_8947 from HP. + Updated NOTE (1997-12-15): PHCO_8086 & PHCO_8947 are very old and are + no longer available. The current patch to install if running + under HP-UX 10.20 is PHCO_11342. + +--------- + + Lynx juggles variable abilities of curses packages or emulations to + display bolding and underlining simultaneously. This may fail if + Lynx thinks that your terminal, in connection with the curses package, + supports a capability which the terminal hardware or emulation does not + in fact support. Setting the right TERM environment variable, tweaking + terminfo or termcap files, or compiling with a newer version of ncurses + or slang may solve problems with missing highlighting or strange + characters appearing on the screen. Also, for a mono terminal, + make sure "show color" is not set to ON in the Options Menu. + + The Wyse 50 and older TeleVideo terminals, among others, are + "magic cookie" terminals. This means that display attributes like + reverse, blink, underline, etc. work in a bizarre way that makes them + difficult to program. You may see extra spaces scattered around your + screen (separating different sorts of highlight); or sections of the + screen may be unexpectedly highlighted. + There is a workaround which works by restricting the terminal to a + single standout attribute (e.g., normal and reverse, but no others). + Implementing the workaround is specific to your curses implementation. + Most versions of curses use one of two terminal databases, called + "termcap" and "terminfo". Updating these databases is system-specific. + New databases should be available from the vendor or other sources. + For the Wyse 50, try + ; + extract the "wy50" (NOT "wy50-mc") entry and use that in place of the + existing one. See `terminfo', `infocmp', `tic' etc. man pages if + necessary. + Alternatively, compiling Lynx with the slang library may avoid problems + with these terminals. + + The Sun console driver (aka wscons(7)) implements "reverse" and "bold" + as "reverse", causing confusion where Lynx uses the distinction between + the two to convey information. Lynx tries to detect this automatically, + but if it fails (for instance, you are running under "screen"), try + setting the -noreverse commandline option. + +--------- + + On VMS, Lynx, and other TCP-IP software, have been experiencing chronic + problems of incompatibilities between DECC and MultiNet headers whenever + new versions of either DECC or MultiNet are released. The Lynx build + procedure for VMS and a maze of spaghetti #ifdef-ing in tcp.h of the + libwww-FM had previously been successful in dealing with this problem + across all versions of MultiNet and of DECC, VAXC, and Pat Rankin's + VMS port of GNUC, but are now not 100% successful. If you get compiler + messages about "struct timeval timeout" having no linkage, add that + declaration immediately below the inclusion of ioctl.h for MultiNet in + tcp.h (by deleting the "#ifdef NOT_DEFINED" and "#endif /* NOT_DEFINED */" + lines): + [...] + #include "multinet_root:[multinet.include.sys]ioctl.h" + struct timeval { + long tv_sec; /* seconds since Jan. 1, 1970 */ + long tv_usec; /* microseconds */ + }; + [...] + If you get compiler warnings about incompatible multinet_foo() + declarations, delete those where indicated in tcp.h. For the most + current versions of MultiNet, you can modify tcp.h to use the DECC + socket and related headers. + + On VMS, the ftp function does not work with SOCKETSHR 0.9D and NETLIB + 2 (NETLIB 1 may work). This is because the functions getsockname() + and getpeername() within SOCKETSHR make incorrect calls to the NETLIB + functions. This results in zeroes being returned for part of the local + IP address. Since ftp sends this IP address to the remote end, the + remote server ends up sending a file back to a non-existent address. + Andy Harper (A.HARPER@kcl.ac.uk) has fixed these problems in the + SOCKETSHR 0.9D sources and offers the fixes as: + http://alder.cc.kcl.ac.uk/fileserv/zip/socketshr_src_09d-2.zip + ftp://ftp2.kcl.ac.uk/zip/socketshr_src_09d-2.zip + +--------- + + On VMS, to build an SSL-capable version, lynx and the ssl library + e.g., OpenSSL, must be built using the same network library. If you + build OpenSSL without specifying the network library (the 5th parameter + of the makevms.com script), it will guess, possibly not the one you + intended. We have tested only the UCX configuration -TD (2002/9/15). diff --git a/README b/README new file mode 100644 index 0000000..79406ff --- /dev/null +++ b/README @@ -0,0 +1,150 @@ + Lynx README file + +Lynx Version 2.9.2 is the latest release (May 2024). +See the CHANGES file for a complete record of all changes and bug fixes. +New releases are announced on the lynx-dev mailing list (see below). + +FOR REAL NOVICES + +To use this package, you need a compiler & a bit of experience +at very simple programming. If you just want something which will work +`out-of-the-box', you can get pre-compiled versions of Lynx +by following the links from + ; + +WHAT IS LYNX? + + Lynx is a fully-featured World Wide Web (WWW) client for users running + cursor-addressable, character-cell display devices such as vt100 terminals, + vt100 emulators or any other character-cell display. + + Lynx will display Hypertext Markup Language (HTML) documents containing + links to files on the local system, as well as files on remote systems + running http, gopher, ftp, wais, nntp, finger, or cso/ph/qi servers, and + services accessible via logins to telnet, tn3270 or rlogin accounts. + + Lynx runs on any POSIX platform as well as Windows. + + Older releases also work with VMS, Windows95 (and up), 386DOS and OS/2 EMX. + + Lynx can be used to access information on the WWW, or to establish + information systems intended primarily for local access. Lynx has been + used to build several Campus Wide Information Systems (CWIS). Lynx can + also be used to build systems isolated within a single LAN. + +HOW TO GET LYNX + + For the latest release of Lynx go to: + ; + ; + + The latest development version is at: + . + + The Lynx homepage is . + The on-line help page (enter `h') has links to many useful things. + +LICENSE + + Lynx is distributed under the GNU General Public License, version 2 (GPLv2) + without restrictions on usage or redistribution. The Lynx copyright + statement, "COPYHEADER", and GNU GPL, "COPYING", are included in the + top-level directory of the distribution. Lynx is supported by the Lynx + user community, an entirely volunteer (and unofficial) organization. + + Certain portions of the Lynx source distribution were originally + created by CERN and have been modified during the development of + Lynx. See WWW/FreeofCharge.html for copyright info regarding CERN + products used in Lynx. + + Note that Lynx is not self-contained; typically it is built with a + variety of add-on libraries, including those for compression, IPv6, + SOCKS and SSL support. + +YEAR 2000 COMPLIANCE + + We believe Lynx works properly for the Year 2000 issues, since it does + not store dates in 2-digit form. Since it must communicate with a wide + range of web servers, it interprets dates in a variety of formats. In + particular, if Lynx receives a date with a 2-digit year, it assumes that + values less than 70 are in the range 2000-2069. + +INSTALLING LYNX + + To install Lynx, follow the steps in the INSTALLATION file, which is + located in the top directory of the source distribution. + +DOCUMENTATION + + A users guide is included in this distribution along with a man page + for Unix systems and a help file for VMS systems. All documentation is + contained in the top directory and the docs, samples and lynx_help + subdirectories. + + While running Lynx, type 'h', 'H', or '?' to invoke the help menu + system. From the help menu you may access several useful documents + pertaining to Lynx and the World Wide Web. The most important of + these is the Lynx Users Guide. By default, Lynx will use the Lynx + Enhanced Pages, which includes http links for help and FAQs concerning + Lynx. It is recommended that you install your own help menu system at + your site in order to lessen the load on http servers. This also will + allow you to customize the help menu system for your site and greatly + speed up access for those using Lynx over a slow connection. + +INSTALLING THE DOCUMENTATION + + For Unix and related systems which support the autoconf configure script, + the help menu system is installed by the "make install-help" command. + + For other systems (such as VMS), copy COPYHEADER and COPYING into the + lynx_help/ subdirectory. Then copy the lynx_help subdirectory to a public + place on your system, or into your $HOME directory if you are a single + user. Finally, edit the lynx.cfg file so that the HELPFILE line is + defined as follows: + HELPFILE:file://localhost/[public_path]/lynx_help/lynx_help_main.html + + where [public_path] is the absolute path to the lynx_help directory. + Customizing the help menu system is just a matter of editing a set of + HTML files. + +INSTALLING LYNX + + To install Lynx, follow the steps in the INSTALLATION file, which is + located in the top directory of the source distribution. + +PROBLEMS + + If you experience problems configuring, compiling or installing Lynx, + please read Section VI. "General installation instructions" in the + INSTALLATION file. Instructions are given there for reporting your + problem to the "lynx-dev" mailing list, which is frequented by experienced + Lynx users. + +LYNX-DEV MAILING LIST + + To subscribe to lynx-dev, send email to + + with "subscribe" for a subject line. + + If you wish to unsubscribe from lynx-dev, send email to + + with "unsubscribe" for a subject line. + + Any messages you wish to post should be sent to + . + + PLEASE use the lynx-dev list, NOT private email to the developers, + for questions or discussion about Lynx, or contributions of patches. + Patches should use the unified diff format (diff -u). + + You need not be subscribed to the lynx-dev list in order to post. If + you post without subscribing, though, you should read replies to your + questions or comments in the archive since more often than not nobody + will send a carbon copy to you. View the archives at: + "lynx-dev Mailing list archives" + + +------------------------------------------------------------------------------ +-- vile:txtmode fc=72 noti +-- $LynxId: README,v 1.39 2024/05/29 22:33:17 tom Exp $ +------------------------------------------------------------------------------ diff --git a/VMSPrint.com b/VMSPrint.com new file mode 100644 index 0000000..d1e2c93 --- /dev/null +++ b/VMSPrint.com @@ -0,0 +1,15 @@ +$! +$! Lynx_Dir:VMSPrint.com - Alan J. Hirsh (hirsh@atuk.aspentec.com) +$! --------------------- +$! Lynx deletes temporary files on exit. If your printer queue +$! is very busy such that Lynx is deleting the files before they +$! have been queued for printing, use PRINTER commands in lynx.cfg +$! which invoke this script. +$! +$! PRINTER:description for menu:@Lynx_Dir\:VMSPrint queue_name %s:FALSE:58 +$! +$! P1 = queue_name (e.g., sys$print) P2 = temporary Lynx file (%s) +$! --------------------------------- ----------------------------- +$ copy 'P2' 'P2'_temp_print +$ print/queue='P1'/delete 'P2'_temp_print +$ exit diff --git a/WWW/FreeofCharge.html b/WWW/FreeofCharge.html new file mode 100644 index 0000000..2725dee --- /dev/null +++ b/WWW/FreeofCharge.html @@ -0,0 +1,26 @@ + + + + + + +CERN WWW software freely available

+Software freely available

The following CERN software is hereby put into +the public domain. +
    +
  • WWW basic ("line-mode") client
  • WWW basic server
  • WWW Library of +common code.
CERN relinquishes all intellectual property rights to this +code, both source and binary form and permission is granted for anyone to use, +duplicate, +modify and redistribute it.

CERN provides absolutely NO WARRANTY OF ANY KIND +with respect to this software. +The entire risk as to the quality and performance of this software is with the +user. +IN NO EVENT WILL CERN BE LIABLE TO ANYONE FOR ANY DAMAGES ARISING OUT THE USE +OF THIS SOFTWARE, INCLUDING, WITHOUT LIMITATION, +DAMAGES RESULTING FROM LOST DATA OR LOST PROFITS, OR FOR ANY SPECIAL, +INCIDENTAL OR CONSEQUENTIAL DAMAGES.

This is part of the +CERN WWW distribution conditions.

Declaration to this effect signed by +the CERN directors of Administration (H. Weber) and Research (W. +Hoogland), May 1993. diff --git a/WWW/Library/Implementation/HTAABrow.c b/WWW/Library/Implementation/HTAABrow.c new file mode 100644 index 0000000..c963acd --- /dev/null +++ b/WWW/Library/Implementation/HTAABrow.c @@ -0,0 +1,1354 @@ +/* + * $LynxId: HTAABrow.c,v 1.43 2018/05/11 22:54:19 tom Exp $ + * + * MODULE HTAABrow.c + * BROWSER SIDE ACCESS AUTHORIZATION MODULE + * + * Contains the code for keeping track on server hostnames, + * port numbers, scheme names, usernames, passwords + * (and servers' public keys). + * + * IMPORTANT: + * Routines in this module use dynamic allocation, but free + * automatically all the memory reserved by them. + * + * Therefore the caller never has to (and never should) + * free() any object returned by these functions. + * + * Therefore also all the strings returned by this package + * are only valid until the next call to the same function + * is made. This approach is selected, because of the nature + * of access authorization: no string returned by the package + * needs to be valid longer than until the next call. + * + * This also makes it easy to plug the AA package in: + * you don't have to ponder whether to free() something + * here or is it done somewhere else (because it is always + * done somewhere else). + * + * The strings that the package needs to store are copied + * so the original strings given as parameters to AA + * functions may be freed or modified with no side effects. + * + * The AA package does not free() anything else than what + * it has itself allocated. + * + * AUTHORS: + * AL Ari Luotonen luotonen@dxcern.cern.ch + * + * HISTORY: + * Oct 17 AL Made corrections suggested by marca: + * Added if (!realm->username) return NULL; + * Changed some ""s to NULLs. + * Now doing calloc() to init uuencode source; + * otherwise HTUU_encode() reads uninitialized memory + * every now and then (not a real bug but not pretty). + * Corrected the formula for uuencode destination size. + * + * 28 Apr 1997 AJL Do Proxy Authorisation. + * + * BUGS: + * + * + */ + +#include +#include +#include /* URL parsing function */ +#include /* HTList object */ +#include /* HTConfirm(), HTPrompt() */ +#include /* AA common to both sides */ +#include /* Assoc list */ +#include /* Are we using an HTTP gateway? */ +#include /* Implemented here */ +#include /* Uuencoding and uudecoding */ + +#include + +/* + * Local datatype definitions + * + * HTAAServer contains all the information about one server. + */ +typedef struct { + + char *hostname; /* Host's name */ + int portnumber; /* Port number */ + BOOL IsProxy; /* Is it a proxy? */ + HTList *setups; /* List of protection setups + on this server; i.e., valid + authentication schemes and + templates when to use them. + This is actually a list of + HTAASetup objects. */ + HTList *realms; /* Information about passwords */ +} HTAAServer; + +/* + * HTAASetup contains information about one server's one + * protected tree of documents. + */ +typedef struct { + HTAAServer *server; /* Which server serves this tree */ + char *ctemplate; /* Template for this tree */ + HTList *valid_schemes; /* Valid authentic.schemes */ + HTAssocList **scheme_specifics; /* Scheme specific params */ + BOOL retry; /* Failed last time -- reprompt (or whatever) */ +} HTAASetup; + +/* + * Information about usernames and passwords in + * Basic and Pubkey authentication schemes; + */ +typedef struct { + char *realmname; /* Password domain name */ + char *username; /* Username in that domain */ + char *password; /* Corresponding password */ +} HTAARealm; + +/* + * To free off all globals. - FM + */ +static void free_HTAAGlobals(void); +static BOOL free_HTAAGlobalsSet = FALSE; +static char *HTAA_composeAuthResult = NULL; +static char *compose_auth_stringResult = NULL; /* Uuencoded presentation */ + +/* + * Module-wide global variables + */ +static HTList *server_table = NULL; /* Browser's info about servers */ +static char *secret_key = NULL; /* Browser's latest secret key */ +static HTAASetup *current_setup = NULL; /* The server setup we are currently */ + + /* talking to */ +static char *current_hostname = NULL; /* The server's name and portnumber */ +static int current_portnumber = 80; /* where we are currently trying to */ + + /* connect. */ +static char *current_docname = NULL; /* The document's name we are */ + + /* trying to access. */ +static char *HTAAForwardAuth = NULL; /* Authorization: line to forward */ + + /* (used by gateway httpds) */ +static HTAASetup *proxy_setup = NULL; /* Same as above, but for Proxy -AJL */ +static char *proxy_hostname = NULL; +static char *proxy_docname = NULL; +static int proxy_portnumber = 80; + +/*** HTAAForwardAuth for enabling gateway-httpds to forward Authorization ***/ + +void HTAAForwardAuth_set(const char *scheme_name, + const char *scheme_specifics) +{ + size_t len = (20 + + (scheme_name ? strlen(scheme_name) : 0) + + (scheme_specifics ? strlen(scheme_specifics) : 0)); + + FREE(HTAAForwardAuth); + if ((HTAAForwardAuth = typecallocn(char, len)) == 0) + outofmem(__FILE__, "HTAAForwardAuth_set"); + + strcpy(HTAAForwardAuth, "Authorization: "); + if (scheme_name) { + strcat(HTAAForwardAuth, scheme_name); + strcat(HTAAForwardAuth, " "); + if (scheme_specifics) { + strcat(HTAAForwardAuth, scheme_specifics); + } + } +} + +void HTAAForwardAuth_reset(void) +{ + FREE(HTAAForwardAuth); +} + +/**************************** HTAAServer ***********************************/ + +static void HTAASetup_delete(HTAASetup * killme); /* Forward */ + +/* static HTAAServer_new() + * ALLOCATE A NEW NODE TO HOLD SERVER INFO + * AND ADD IT TO THE LIST OF SERVERS + * ON ENTRY: + * hostname is the name of the host that the server + * is running in. + * portnumber is the portnumber which the server listens. + * IsProxy should be TRUE if this is a proxy. + * + * ON EXIT: + * returns the newly-allocated node with all the strings + * duplicated. + * Strings will be automatically freed by + * the function HTAAServer_delete(), which also + * frees the node itself. + */ +static HTAAServer *HTAAServer_new(const char *hostname, + int portnumber, + int IsProxy) +{ + HTAAServer *server; + + if ((server = typecalloc(HTAAServer)) == 0) + outofmem(__FILE__, "HTAAServer_new"); + + server->hostname = NULL; + server->portnumber = (portnumber > 0 ? portnumber : 80); + server->IsProxy = (BOOLEAN) IsProxy; + server->setups = HTList_new(); + server->realms = HTList_new(); + + if (hostname) + StrAllocCopy(server->hostname, hostname); + + if (!server_table) + server_table = HTList_new(); + + HTList_addObject(server_table, (void *) server); + + return server; +} + +/* static HTAAServer_delete() + * + * DELETE THE ENTRY FOR THE SERVER FROM THE HOST TABLE, + * AND FREE THE MEMORY USED BY IT. + * + * ON ENTRY: + * killme points to the HTAAServer to be freed. + * + * ON EXIT: + * returns nothing. + */ +static void HTAAServer_delete(HTAAServer *killme) +{ + int n, i; + HTAASetup *setup; + HTAARealm *realm; + HTList *cur; + + if (killme) { + if (killme->setups != NULL) { + n = HTList_count(killme->setups); + for (i = (n - 1); i >= 0; i--) { + if ((setup = (HTAASetup *) HTList_objectAt(killme->setups, + i)) != NULL) { + HTAASetup_delete(setup); + setup = NULL; + } + } + HTList_delete(killme->setups); + killme->setups = NULL; + } + + cur = killme->realms; + while (NULL != (realm = (HTAARealm *) HTList_nextObject(cur))) { + FREE(realm->realmname); + FREE(realm->username); + FREE(realm->password); + FREE(realm); + } + HTList_delete(killme->realms); + killme->realms = NULL; + + FREE(killme->hostname); + + HTList_removeObject(server_table, (void *) killme); + FREE(killme); + } +} + +/* static HTAAServer_lookup() + * LOOK UP SERVER BY HOSTNAME AND PORTNUMBER + * ON ENTRY: + * hostname obvious. + * portnumber if non-positive defaults to 80. + * IsProxy should be TRUE if this is a proxy. + * + * Looks up the server in the module-global server_table. + * + * ON EXIT: + * returns pointer to a HTAAServer structure + * representing the looked-up server. + * NULL, if not found. + */ +static HTAAServer *HTAAServer_lookup(const char *hostname, + int portnumber, + int IsProxy) +{ + if (hostname) { + HTList *cur = server_table; + HTAAServer *server; + + if (portnumber <= 0) + portnumber = 80; + + while (NULL != (server = (HTAAServer *) HTList_nextObject(cur))) { + if (server->portnumber == portnumber && + 0 == strcmp(server->hostname, hostname) && + server->IsProxy == IsProxy) + return server; + } + } + return NULL; /* NULL parameter, or not found */ +} + +/*************************** HTAASetup *******************************/ + +/* static HTAASetup_lookup() + * FIGURE OUT WHICH AUTHENTICATION SETUP THE SERVER + * IS USING FOR A GIVEN FILE ON A GIVEN HOST AND PORT + * + * ON ENTRY: + * hostname is the name of the server host machine. + * portnumber is the port that the server is running in. + * docname is the (URL-)pathname of the document we + * are trying to access. + * IsProxy should be TRUE if this is a proxy. + * + * This function goes through the information known about + * all the setups of the server, and finds out if the given + * filename resides in one of the protected directories. + * + * ON EXIT: + * returns NULL if no match. + * Otherwise, a HTAASetup structure representing + * the protected server setup on the corresponding + * document tree. + * + */ +static HTAASetup *HTAASetup_lookup(const char *hostname, + int portnumber, + const char *docname, + int IsProxy) +{ + HTAAServer *server; + HTAASetup *setup; + + if (portnumber <= 0) + portnumber = 80; + + if (hostname && docname && *hostname && *docname && + NULL != (server = HTAAServer_lookup(hostname, + portnumber, + IsProxy))) { + + HTList *cur = server->setups; + + CTRACE((tfp, "%s %s (%s:%d:%s)\n", + "HTAASetup_lookup: resolving setup for", + (IsProxy ? "proxy" : "server"), + hostname, portnumber, docname)); + + while (NULL != (setup = (HTAASetup *) HTList_nextObject(cur))) { + if (HTAA_templateMatch(setup->ctemplate, docname)) { + CTRACE((tfp, "%s `%s' %s `%s'\n", + "HTAASetup_lookup:", docname, + "matched template", setup->ctemplate)); + return setup; + } else { + CTRACE((tfp, "%s `%s' %s `%s'\n", + "HTAASetup_lookup:", docname, + "did NOT match template", setup->ctemplate)); + } + } /* while setups remain */ + } + /* if valid parameters and server found */ + CTRACE((tfp, "%s `%s' %s\n", + "HTAASetup_lookup: No template matched", + NONNULL(docname), + "(so probably not protected)")); + + return NULL; /* NULL in parameters, or not found */ +} + +/* static HTAASetup_new() + * CREATE A NEW SETUP NODE + * ON ENTRY: + * server is a pointer to a HTAAServer structure + * to which this setup belongs. + * ctemplate documents matching this template + * are protected according to this setup. + * valid_schemes a list containing all valid authentication + * schemes for this setup. + * If NULL, all schemes are disallowed. + * scheme_specifics is an array of assoc lists, which + * contain scheme specific parameters given + * by server in Authenticate: fields. + * If NULL, all scheme specifics are + * set to NULL. + * ON EXIT: + * returns a new HTAASetup node, and also adds it as + * part of the HTAAServer given as parameter. + */ +static HTAASetup *HTAASetup_new(HTAAServer *server, char *ctemplate, + HTList *valid_schemes, + HTAssocList **scheme_specifics) +{ + HTAASetup *setup; + + if (!server || isEmpty(ctemplate)) + return NULL; + + if ((setup = typecalloc(HTAASetup)) == 0) + outofmem(__FILE__, "HTAASetup_new"); + + setup->retry = NO; + setup->server = server; + setup->ctemplate = NULL; + if (ctemplate) + StrAllocCopy(setup->ctemplate, ctemplate); + setup->valid_schemes = valid_schemes; + setup->scheme_specifics = scheme_specifics; + + HTList_addObject(server->setups, (void *) setup); + + return setup; +} + +/* static HTAASetup_delete() + * FREE A HTAASetup STRUCTURE + * ON ENTRY: + * killme is a pointer to the structure to free(). + * + * ON EXIT: + * returns nothing. + */ +static void HTAASetup_delete(HTAASetup * killme) +{ + int scheme; + + if (killme) { + FREE(killme->ctemplate); + if (killme->valid_schemes) { + HTList_delete(killme->valid_schemes); + killme->valid_schemes = NULL; + } + for (scheme = 0; scheme < HTAA_MAX_SCHEMES; scheme++) + if (killme->scheme_specifics[scheme]) + HTAssocList_delete(killme->scheme_specifics[scheme]); + FREE(killme->scheme_specifics); + FREE(killme); + } +} + +/* static HTAASetup_updateSpecifics() + * COPY SCHEME SPECIFIC PARAMETERS + * TO HTAASetup STRUCTURE + * ON ENTRY: + * setup destination setup structure. + * specifics string array containing scheme + * specific parameters for each scheme. + * If NULL, all the scheme specific + * parameters are set to NULL. + * + * ON EXIT: + * returns nothing. + */ +static void HTAASetup_updateSpecifics(HTAASetup * setup, HTAssocList **specifics) +{ + int scheme; + + if (setup) { + if (setup->scheme_specifics) { + for (scheme = 0; scheme < HTAA_MAX_SCHEMES; scheme++) { + if (setup->scheme_specifics[scheme]) + HTAssocList_delete(setup->scheme_specifics[scheme]); + } + FREE(setup->scheme_specifics); + } + setup->scheme_specifics = specifics; + } +} + +/*************************** HTAARealm **********************************/ + +/* static HTAARealm_lookup() + * LOOKUP HTAARealm STRUCTURE BY REALM NAME + * ON ENTRY: + * realm_table a list of realm objects. + * realmname is the name of realm to look for. + * + * ON EXIT: + * returns the realm. NULL, if not found. + */ +static HTAARealm *HTAARealm_lookup(HTList *realm_table, + const char *realmname) +{ + if (realm_table && realmname) { + HTList *cur = realm_table; + HTAARealm *realm; + + while (NULL != (realm = (HTAARealm *) HTList_nextObject(cur))) { + if (0 == strcmp(realm->realmname, realmname)) + return realm; + } + } + return NULL; /* No table, NULL param, or not found */ +} + +/* static HTAARealm_new() + * CREATE A NODE CONTAINING USERNAME AND + * PASSWORD USED FOR THE GIVEN REALM. + * IF REALM ALREADY EXISTS, CHANGE + * USERNAME/PASSWORD. + * ON ENTRY: + * realm_table a list of realms to where to add + * the new one, too. + * realmname is the name of the password domain. + * username and + * password are what you can expect them to be. + * + * ON EXIT: + * returns the created realm. + */ +static HTAARealm *HTAARealm_new(HTList *realm_table, + const char *realmname, + const char *username, + const char *password) +{ + HTAARealm *realm; + + realm = HTAARealm_lookup(realm_table, realmname); + + if (!realm) { + if ((realm = typecalloc(HTAARealm)) == 0) + outofmem(__FILE__, "HTAARealm_new"); + + realm->realmname = NULL; + realm->username = NULL; + realm->password = NULL; + StrAllocCopy(realm->realmname, realmname); + if (realm_table) + HTList_addObject(realm_table, (void *) realm); + } + if (username) + StrAllocCopy(realm->username, username); + if (password) + StrAllocCopy(realm->password, password); + + return realm; +} + +BOOL HTAA_HaveUserinfo(const char *hostname) +{ + int gen_delims = 0; + BOOL result = FALSE; + char *my_info = NULL; + + if (StrAllocCopy(my_info, hostname) != NULL) { + char *at_sign = HTSkipToAt(my_info, &gen_delims); + + free(my_info); + if (at_sign != NULL && gen_delims == 0) + result = TRUE; + } + return result; +} + +/* + * If there is userinfo in the hostname string, update the realm to use that + * information. The command-line "-auth" option will override this. + */ +static void fill_in_userinfo(HTAARealm *realm, const char *hostname) +{ + int gen_delims = 0; + char *my_info = NULL; + char *at_sign = HTSkipToAt(StrAllocCopy(my_info, hostname), &gen_delims); + + if (at_sign != NULL && gen_delims == 0) { + char *colon; + + *at_sign = '\0'; + if ((colon = StrChr(my_info, ':')) != 0) { + *colon++ = '\0'; + } + if (non_empty(my_info)) { + char *msg; + BOOL prior = non_empty(realm->username); + + if (prior && strcmp(realm->username, my_info)) { + msg = 0; + HTSprintf0(&msg, + gettext("username for realm %s changed from %s to %s"), + realm->realmname, + realm->username, + my_info); + HTAlert(msg); + free(msg); + FREE(realm->username); + StrAllocCopy(realm->username, my_info); + } else if (!prior) { + StrAllocCopy(realm->username, my_info); + } + if (non_empty(colon)) { + prior = non_empty(realm->password); + if (prior && strcmp(realm->password, colon)) { + msg = 0; + HTSprintf0(&msg, + gettext("password for realm %s user %s changed"), + realm->realmname, + realm->username); + HTAlert(msg); + free(msg); + FREE(realm->password); + StrAllocCopy(realm->password, colon); + } else if (!prior) { + StrAllocCopy(realm->password, colon); + } + } + } + } + free(my_info); +} + +/***************** Basic and Pubkey Authentication ************************/ + +/* static compose_auth_string() + * + * COMPOSE Basic OR Pubkey AUTHENTICATION STRING; + * PROMPTS FOR USERNAME AND PASSWORD IF NEEDED + * + * ON ENTRY: + * hostname may include user- and password information + * scheme is either HTAA_BASIC or HTAA_PUBKEY. + * setup is the current server setup. + * IsProxy should be TRUE if this is a proxy. + * + * ON EXIT: + * returns a newly composed authorization string, + * (with, of course, a newly generated secret + * key and fresh timestamp, if Pubkey-scheme + * is being used). + * NULL, if something fails. + * NOTE: + * Like throughout the entire AA package, no string or structure + * returned by AA package needs to (or should) be freed. + * + */ +static char *compose_auth_string(const char *hostname, + HTAAScheme scheme, + HTAASetup * setup, + int IsProxy) +{ + char *cleartext = NULL; /* Cleartext presentation */ + char *ciphertext = NULL; /* Encrypted presentation */ + size_t len; + char *msg = NULL; + char *username = NULL; + char *password = NULL; + char *realmname = NULL; + char *theHost = NULL; + char *proxiedHost = NULL; + char *thePort = NULL; + HTAARealm *realm; + const char *i_net_addr = "0.0.0.0"; /* Change... @@@@ */ + const char *timestamp = "42"; /* ... these @@@@ */ + + FREE(compose_auth_stringResult); /* From previous call */ + + if ((scheme != HTAA_BASIC && scheme != HTAA_PUBKEY) || + !(setup && + setup->scheme_specifics && + setup->scheme_specifics[scheme] && + setup->server && + setup->server->realms)) + return NULL; + + realmname = HTAssocList_lookup(setup->scheme_specifics[scheme], "realm"); + if (!realmname) + return NULL; + + realm = HTAARealm_lookup(setup->server->realms, realmname); + setup->retry |= HTAA_HaveUserinfo(hostname); + + if (!(realm && + non_empty(realm->username) && + non_empty(realm->password)) || setup->retry) { + if (!realm) { + CTRACE((tfp, "%s `%s' %s\n", + "compose_auth_string: realm:", realmname, + "not found -- creating")); + realm = HTAARealm_new(setup->server->realms, + realmname, NULL, NULL); + } + fill_in_userinfo(realm, hostname); + /* + * The template should be either the '*' global for everything on the + * server (always true for proxy authorization setups), or a path for + * the start of a protected limb, with no host field, but we'll check + * for a host anyway in case a WWW-Protection-Template header set an + * absolute URL instead of a path. If we do get a host from this, it + * will include the port. - FM + */ + if ((!IsProxy) && using_proxy && setup->ctemplate) { + proxiedHost = HTParse(setup->ctemplate, "", PARSE_HOST); + if (proxiedHost && *proxiedHost != '\0') { + theHost = proxiedHost; + } + } + /* + * If we didn't get a host field from the template, set up the host + * name and port from the setup->server elements. - FM + */ + if (!theHost) + theHost = setup->server->hostname; + if (setup->server->portnumber > 0 && + setup->server->portnumber != 80) { + HTSprintf0(&thePort, ":%d", setup->server->portnumber); + } + + HTSprintf0(&msg, gettext("Username for '%s' at %s '%s%s':"), + realm->realmname, + (IsProxy ? "proxy" : "server"), + (theHost ? theHost : "??"), + NonNull(thePort)); + FREE(proxiedHost); + FREE(thePort); + if (non_empty(realm->username)) { + StrAllocCopy(username, realm->username); + } + if (non_empty(realm->password)) { + StrAllocCopy(password, realm->password); + } + HTPromptUsernameAndPassword(msg, &username, &password, IsProxy); + + FREE(msg); + FREE(realm->username); + FREE(realm->password); + + realm->username = username; + realm->password = password; + + if (!realm->username || !realm->password) { + /* + * Signals to retry. - FM + */ + return NULL; + } else if (*realm->username == '\0') { + /* + * Signals to abort. - FM + */ + StrAllocCopy(compose_auth_stringResult, ""); + return compose_auth_stringResult; + } + } + + len = (strlen(NonNull(realm->username)) + + strlen(NonNull(realm->password)) + 3); + + if (scheme == HTAA_PUBKEY) { +#ifdef PUBKEY + /* Generate new secret key */ + StrAllocCopy(secret_key, HTAA_generateRandomKey()); +#endif /* PUBKEY */ + /* Room for secret key, timestamp and inet address */ + len += strlen(NonNull(secret_key)) + 30; + } else { + FREE(secret_key); + } + + if ((cleartext = typecallocn(char, len)) == 0) + outofmem(__FILE__, "compose_auth_string"); + + if (realm->username) + strcpy(cleartext, realm->username); + else + *cleartext = '\0'; + + strcat(cleartext, ":"); + + if (realm->password) + strcat(cleartext, realm->password); + + if (scheme == HTAA_PUBKEY) { + strcat(cleartext, ":"); + strcat(cleartext, i_net_addr); + strcat(cleartext, ":"); + strcat(cleartext, timestamp); + strcat(cleartext, ":"); + if (secret_key) + strcat(cleartext, secret_key); + + if (!((ciphertext = typecallocn(char, 2 * len)) && + (compose_auth_stringResult = typecallocn(char, 3 * len)))) + outofmem(__FILE__, "compose_auth_string"); + +#ifdef PUBKEY + HTPK_encrypt(cleartext, ciphertext, server->public_key); + HTUU_encode((unsigned char *) ciphertext, strlen(ciphertext), + compose_auth_stringResult); +#endif /* PUBKEY */ + FREE(cleartext); + FREE(ciphertext); + } else { /* scheme == HTAA_BASIC */ + if (!(compose_auth_stringResult = + typecallocn(char, (4 * ((len + 2) / 3)) + 1))) + outofmem(__FILE__, "compose_auth_string"); + + HTUU_encode((unsigned char *) cleartext, strlen(cleartext), + compose_auth_stringResult); + FREE(cleartext); + } + return compose_auth_stringResult; +} + +/* BROWSER static HTAA_selectScheme() + * SELECT THE AUTHENTICATION SCHEME TO USE + * ON ENTRY: + * setup is the server setup structure which can + * be used to make the decision about the + * used scheme. + * + * When new authentication methods are added to library + * this function makes the decision about which one to + * use at a given time. This can be done by inspecting + * environment variables etc. + * + * Currently only searches for the first valid scheme, + * and if nothing found suggests Basic scheme; + * + * ON EXIT: + * returns the authentication scheme to use. + */ +static HTAAScheme HTAA_selectScheme(HTAASetup * setup) +{ + int scheme; + + if (setup && setup->valid_schemes) { + for (scheme = HTAA_BASIC; scheme < HTAA_MAX_SCHEMES; scheme++) { + void *object = (void *) (intptr_t) scheme; + + if (-1 < HTList_indexOf(setup->valid_schemes, object)) + return (HTAAScheme) scheme; + } + } + return HTAA_BASIC; +} + +/* + * Purpose: Free off all module globals. + * Arguments: void + * Return Value: void + * Remarks/Portability/Dependencies/Restrictions: + * To be used at program exit. + * Revision History: + * 06-19-96 created - FM + */ +static void free_HTAAGlobals(void) +{ + HTAAServer *server; + int n, i; + + if (server_table != NULL) { + n = HTList_count(server_table); + for (i = (n - 1); i >= 0; i--) { + if ((server = (HTAAServer *) HTList_objectAt(server_table, + i)) != NULL) { + HTAAServer_delete(server); + server = NULL; + } + } + HTList_delete(server_table); + server_table = NULL; + } + + HTAAForwardAuth_reset(); + FREE(HTAA_composeAuthResult); + FREE(current_hostname); + FREE(current_docname); + FREE(proxy_hostname); + FREE(proxy_docname); + FREE(compose_auth_stringResult); + FREE(secret_key); +} + +/* BROWSER PUBLIC HTAA_composeAuth() + * + * SELECT THE AUTHENTICATION SCHEME AND + * COMPOSE THE ENTIRE AUTHORIZATION HEADER LINE + * IF WE ALREADY KNOW THAT THE HOST REQUIRES AUTHENTICATION + * + * ON ENTRY: + * hostname is the hostname of the server. + * portnumber is the portnumber in which the server runs. + * docname is the pathname of the document (as in URL) + * IsProxy should be TRUE if this is a proxy. + * + * ON EXIT: + * returns NULL, if no authorization seems to be needed, or + * if it is the entire Authorization: line, e.g. + * + * "Authorization: Basic username:password" + * + * As usual, this string is automatically freed. + */ +char *HTAA_composeAuth(const char *hostname, + const int portnumber, + const char *docname, + int IsProxy) +{ + char *auth_string; + BOOL retry; + HTAAScheme scheme; + size_t len; + + /* + * Setup atexit() freeing if not done already. - FM + */ + if (!free_HTAAGlobalsSet) { +#ifdef LY_FIND_LEAKS + atexit(free_HTAAGlobals); +#endif + free_HTAAGlobalsSet = TRUE; + } + + /* + * Make gateway httpds pass authorization field as it was received. (This + * still doesn't really work because Authenticate: headers from remote + * server are not forwarded to client yet so it cannot really know that it + * should send authorization; I will not implement it yet because I feel we + * will soon change radically the way requests are represented to allow + * multithreading on server-side. Life is hard.) + */ + if (HTAAForwardAuth) { + CTRACE((tfp, "HTAA_composeAuth: %s\n", + "Forwarding received authorization")); + StrAllocCopy(HTAA_composeAuthResult, HTAAForwardAuth); + HTAAForwardAuth_reset(); /* Just a precaution */ + return HTAA_composeAuthResult; + } + + FREE(HTAA_composeAuthResult); /* From previous call */ + + if (IsProxy) { + /* + * Proxy Authorization required. - AJL + */ + + CTRACE((tfp, "Composing Proxy Authorization for %s:%d/%s\n", + hostname, portnumber, docname)); + + if (proxy_portnumber != portnumber || + !proxy_hostname || !proxy_docname || + !hostname || !docname || + 0 != strcmp(proxy_hostname, hostname) || + 0 != strcmp(proxy_docname, docname)) { + + retry = NO; + + proxy_portnumber = portnumber; + + if (hostname) + StrAllocCopy(proxy_hostname, hostname); + else + FREE(proxy_hostname); + + if (docname) + StrAllocCopy(proxy_docname, docname); + else + FREE(proxy_docname); + } else { + retry = YES; + } + + if (!proxy_setup || !retry) + proxy_setup = HTAASetup_lookup(hostname, portnumber, + docname, IsProxy); + + if (!proxy_setup) + return NULL; + + switch (scheme = HTAA_selectScheme(proxy_setup)) { + case HTAA_BASIC: + case HTAA_PUBKEY: + auth_string = compose_auth_string(hostname, scheme, proxy_setup, IsProxy); + break; + case HTAA_KERBEROS_V4: + /* OTHER AUTHENTICATION ROUTINES ARE CALLED HERE */ + default: + { + char *msg = NULL; + + HTSprintf0(&msg, "%s `%s'", + gettext("This client doesn't know how to compose proxy authorization information for scheme"), + HTAAScheme_name(scheme)); + HTAlert(msg); + FREE(msg); + auth_string = NULL; + } + } /* switch scheme */ + + proxy_setup->retry = NO; + + if (!auth_string) + /* + * Signal a failure. - FM + */ + return NULL; /* Added by marca. */ + if (*auth_string == '\0') { + /* + * Signal an abort. - FM + */ + StrAllocCopy(HTAA_composeAuthResult, ""); + return (HTAA_composeAuthResult); + } + len = strlen(auth_string) + strlen(HTAAScheme_name(scheme)) + 26; + if ((HTAA_composeAuthResult = typecallocn(char, len)) == 0) + outofmem(__FILE__, "HTAA_composeAuth"); + + strcpy(HTAA_composeAuthResult, "Proxy-Authorization: "); + + } else { + /* + * Normal WWW authorization. + */ + CTRACE((tfp, "Composing Authorization for %s:%d/%s\n", + hostname, portnumber, docname)); + + if (current_portnumber != portnumber || + !current_hostname || !current_docname || + !hostname || !docname || + 0 != strcmp(current_hostname, hostname) || + 0 != strcmp(current_docname, docname)) { + + retry = NO; + + current_portnumber = portnumber; + + if (hostname) + StrAllocCopy(current_hostname, hostname); + else + FREE(current_hostname); + + if (docname) + StrAllocCopy(current_docname, docname); + else + FREE(current_docname); + } else { + retry = YES; + } + + if (!current_setup || !retry) + current_setup = HTAASetup_lookup(hostname, portnumber, + docname, IsProxy); + + if (!current_setup) + return NULL; + + switch (scheme = HTAA_selectScheme(current_setup)) { + case HTAA_BASIC: + case HTAA_PUBKEY: + auth_string = compose_auth_string(hostname, scheme, current_setup, IsProxy); + break; + case HTAA_KERBEROS_V4: + /* OTHER AUTHENTICATION ROUTINES ARE CALLED HERE */ + default: + { + char *msg = 0; + + HTSprintf0(&msg, "%s `%s'", + gettext("This client doesn't know how to compose authorization information for scheme"), + HTAAScheme_name(scheme)); + HTAlert(msg); + FREE(msg); + auth_string = NULL; + } + } /* switch scheme */ + + current_setup->retry = NO; + + if (!auth_string) + /* + * Signal a failure. - FM + */ + return NULL; /* Added by marca. */ + if (*auth_string == '\0') { + /* + * Signal an abort. - FM + */ + StrAllocCopy(HTAA_composeAuthResult, ""); + return (HTAA_composeAuthResult); + } + + len = strlen(auth_string) + strlen(HTAAScheme_name(scheme)) + 20; + if ((HTAA_composeAuthResult = typecallocn(char, len)) == 0) + outofmem(__FILE__, "HTAA_composeAuth"); + + strcpy(HTAA_composeAuthResult, "Authorization: "); + } + + strcat(HTAA_composeAuthResult, HTAAScheme_name(scheme)); + strcat(HTAA_composeAuthResult, " "); + strcat(HTAA_composeAuthResult, auth_string); + return HTAA_composeAuthResult; +} + +/* BROWSER PUBLIC HTAA_shouldRetryWithAuth() + * + * DETERMINES IF WE SHOULD RETRY THE SERVER + * WITH AUTHORIZATION + * (OR IF ALREADY RETRIED, WITH A DIFFERENT + * USERNAME AND/OR PASSWORD (IF MISSPELLED)) + * ON ENTRY: + * start_of_headers is the first block already read from socket, + * but status line skipped; i.e., points to the + * start of the header section. + * length is the remaining length of the first block. + * soc is the socket to read the rest of server reply. + * IsProxy should be TRUE if this is a proxy. + * + * This function should only be called when + * server has replied with a 401 (Unauthorized) + * status code. + * ON EXIT: + * returns YES, if connection should be retried. + * The node containing all the necessary + * information is + * * either constructed if it does not exist + * * or password is reset to NULL to indicate + * that username and password should be + * reprompted when composing Authorization: + * field (in function HTAA_composeAuth()). + * NO, otherwise. + */ +BOOL HTAA_shouldRetryWithAuth(char *start_of_headers, + size_t length, + int soc, + int IsProxy) +{ + HTAAScheme scheme; + char *line = NULL; + int num_schemes = 0; + HTList *valid_schemes = HTList_new(); + HTAssocList **scheme_specifics = NULL; + char *ctemplate = NULL; + char *temp = NULL; + BOOL result = NO; + + /* + * Setup atexit() freeing if not done already. - FM + */ + if (!free_HTAAGlobalsSet) { +#ifdef LY_FIND_LEAKS + atexit(free_HTAAGlobals); +#endif + free_HTAAGlobalsSet = TRUE; + } + + /* + * Read server reply header lines + */ + CTRACE((tfp, "Server reply header lines:\n")); + + HTAA_setupReader(start_of_headers, length, soc); + while (NULL != (line = HTAA_getUnfoldedLine()) && *line != '\0') { + CTRACE((tfp, "%s\n", line)); + + if (StrChr(line, ':')) { /* Valid header line */ + + char *p = line; + char *fieldname = HTNextField(&p); + char *arg1 = HTNextField(&p); + char *args = p; + + if ((IsProxy && + 0 == strcasecomp(fieldname, "Proxy-Authenticate:")) || + (!IsProxy && + 0 == strcasecomp(fieldname, "WWW-Authenticate:"))) { + if (isEmpty(arg1) || isEmpty(args)) { + HTSprintf0(&temp, gettext("Invalid header '%s%s%s%s%s'"), line, + (non_empty(arg1) ? " " : ""), + NonNull(arg1), + (non_empty(args) ? " " : ""), + NonNull(args)); + HTAlert(temp); + FREE(temp); + } else if (HTAA_UNKNOWN != (scheme = HTAAScheme_enum(arg1))) { + HTList_addObject(valid_schemes, (void *) scheme); + if (!scheme_specifics) { + int i; + + scheme_specifics = + typecallocn(HTAssocList *, HTAA_MAX_SCHEMES); + + if (!scheme_specifics) + outofmem(__FILE__, "HTAA_shouldRetryWithAuth"); + + for (i = 0; i < HTAA_MAX_SCHEMES; i++) + scheme_specifics[i] = NULL; + } + scheme_specifics[scheme] = HTAA_parseArgList(args); + num_schemes++; + } else { + CTRACE((tfp, "Unknown scheme `%s' %s\n", + NONNULL(arg1), + (IsProxy ? + "in Proxy-Authenticate: field" : + "in WWW-Authenticate: field"))); + } + } + + else if (!IsProxy && + 0 == strcasecomp(fieldname, "WWW-Protection-Template:")) { + CTRACE((tfp, "Protection template set to `%s'\n", arg1)); + StrAllocCopy(ctemplate, arg1); + } + + } else { + CTRACE((tfp, "Invalid header line `%s' ignored\n", line)); + } + + FREE(line); + } /* while header lines remain */ + FREE(line); + + /* + * So should we retry with authorization? + */ + if (IsProxy) { + if (num_schemes == 0) { + /* + * No proxy authorization valid + */ + proxy_setup = NULL; + result = NO; + } + /* + * Doing it for proxy. -AJL + */ + else if (proxy_setup && proxy_setup->server) { + /* + * We have already tried with proxy authorization. Either we don't + * have access or username or password was misspelled. + * + * Update scheme-specific parameters (in case they have expired by + * chance). + */ + HTAASetup_updateSpecifics(proxy_setup, scheme_specifics); + + if (NO == HTConfirm(AUTH_FAILED_PROMPT)) { + proxy_setup = NULL; + result = NO; + } else { + /* + * Re-ask username+password (if misspelled). + */ + HTList_delete(valid_schemes); + proxy_setup->retry = YES; + result = YES; + } + } else { + /* + * proxy_setup == NULL, i.e., we have a first connection to a + * protected server or the server serves a wider set of documents + * than we expected so far. + */ + HTAAServer *server = HTAAServer_lookup(proxy_hostname, + proxy_portnumber, + IsProxy); + + if (!server) { + server = HTAAServer_new(proxy_hostname, + proxy_portnumber, + IsProxy); + } + if (!ctemplate) /* Proxy matches everything -AJL */ + StrAllocCopy(ctemplate, "*"); + proxy_setup = HTAASetup_new(server, + ctemplate, + valid_schemes, + scheme_specifics); + FREE(ctemplate); + + HTAlert(gettext("Proxy authorization required -- retrying")); + result = YES; + } + } + /* + * Normal WWW authorization. + */ + else if (num_schemes == 0) { + /* + * No authorization valid. + */ + current_setup = NULL; + result = NO; + } else if (current_setup && current_setup->server) { + /* + * So we have already tried with WWW authorization. Either we don't + * have access or username or password was misspelled. + * + * Update scheme-specific parameters (in case they have expired by + * chance). + */ + HTAASetup_updateSpecifics(current_setup, scheme_specifics); + + if (NO == HTConfirm(AUTH_FAILED_PROMPT)) { + current_setup = NULL; + result = NO; + } else { + /* + * Re-ask username+password (if misspelled). + */ + current_setup->retry = YES; + result = YES; + } + } else { + /* + * current_setup == NULL, i.e., we have a first connection to a + * protected server or the server serves a wider set of documents than + * we expected so far. + */ + HTAAServer *server = HTAAServer_lookup(current_hostname, + current_portnumber, + IsProxy); + + if (!server) { + server = HTAAServer_new(current_hostname, + current_portnumber, + IsProxy); + } + if (!ctemplate) + ctemplate = HTAA_makeProtectionTemplate(current_docname); + current_setup = HTAASetup_new(server, + ctemplate, + valid_schemes, + scheme_specifics); + FREE(ctemplate); + + HTAlert(gettext("Access without authorization denied -- retrying")); + result = YES; + } + + if (result == NO) { + HTList_delete(valid_schemes); + } + return result; +} + +/* + * This function clears all authorization information by + * invoking the free_HTAAGlobals() function, which normally + * is invoked at exit. It allows a browser command to do + * this at any time, for example, if the user is leaving + * the terminal for a period of time, but does not want + * to end the current session. - FM + */ +void HTClearHTTPAuthInfo(void) +{ + /* + * Need code to check cached documents against the protection templates, + * and do something to ensure that any protected documents no longer can be + * accessed without a new retrieval. - FM + */ + + /* + * Now free all of the authorization info, and reset the + * free_HTAAGlobalsSet flag. - FM + */ + free_HTAAGlobals(); + free_HTAAGlobalsSet = FALSE; +} diff --git a/WWW/Library/Implementation/HTAABrow.h b/WWW/Library/Implementation/HTAABrow.h new file mode 100644 index 0000000..f151deb --- /dev/null +++ b/WWW/Library/Implementation/HTAABrow.h @@ -0,0 +1,142 @@ +/* + * $LynxId: HTAABrow.h,v 1.17 2016/11/24 23:32:22 tom Exp $ + * + * BROWSER SIDE ACCESS AUTHORIZATION MODULE + + This module is the browser side interface to Access Authorization (AA) package. It + contains code only for browser. + + Important to know about memory allocation: + + Routines in this module use dynamic allocation, but free automatically all the memory + reserved by them. + + Therefore the caller never has to (and never should) free() any object returned by + these functions. + + Therefore also all the strings returned by this package are only valid until the next + call to the same function is made. This approach is selected, because of the nature of + access authorization: no string returned by the package needs to be valid longer than + until the next call. + + This also makes it easy to plug the AA package in: you don't have to ponder whether to + free()something here or is it done somewhere else (because it is always done somewhere + else). + + The strings that the package needs to store are copied so the original strings given as + parameters to AA functions may be freed or modified with no side effects. + + Also note:The AA package does not free() anything else than what it has itself + allocated. + + */ + +#ifndef HTAABROW_H +#define HTAABROW_H + +#include /* Common parts of AA */ + +#ifdef __cplusplus +extern "C" { +#endif +/* + Routines for Browser Side Recording of AA Info + + Most of the browser-side AA is done by the following two functions (which are called + from file HTTP.c so the browsers using libwww only need to be linked with the new + library and not be changed at all): + + HTAA_composeAuth() composes the Authorization: line contents, if the AA package + thinks that the given document is protected. Otherwise this function returns NULL. + This function also calls the functions HTPrompt(),HTPromptPassword() and HTConfirm() + to get the username, password and some confirmation from the user. + + HTAA_shouldRetryWithAuth() determines whether to retry the request with AA or with a + new AA (in case username or password was misspelled). + + */ +/* PUBLIC HTAA_composeAuth() + * + * COMPOSE THE ENTIRE AUTHORIZATION HEADER LINE IF WE + * ALREADY KNOW, THAT THE HOST MIGHT REQUIRE AUTHORIZATION + * + * ON ENTRY: + * hostname is the hostname of the server. + * portnumber is the portnumber in which the server runs. + * docname is the pathname of the document (as in URL) + * + * ON EXIT: + * returns NULL, if no authorization seems to be needed, or + * if it is the entire Authorization: line, e.g. + * + * "Authorization: basic username:password" + * + * As usual, this string is automatically freed. + */ + extern char *HTAA_composeAuth(const char *hostname, + const int portnumber, + const char *docname, + int IsProxy); + +/* BROWSER PUBLIC HTAA_shouldRetryWithAuth() + * + * DETERMINES IF WE SHOULD RETRY THE SERVER + * WITH AUTHORIZATION + * (OR IF ALREADY RETRIED, WITH A DIFFERENT + * USERNAME AND/OR PASSWORD (IF MISSPELLED)) + * ON ENTRY: + * start_of_headers is the first block already read from socket, + * but status line skipped; i.e., points to the + * start of the header section. + * length is the remaining length of the first block. + * soc is the socket to read the rest of server reply. + * + * This function should only be called when + * server has replied with a 401 (Unauthorized) + * status code. + * ON EXIT: + * returns YES, if connection should be retried. + * The node containing all the necessary + * information is + * * either constructed if it does not exist + * * or password is reset to NULL to indicate + * that username and password should be + * reprompted when composing Authorization: + * field (in function HTAA_composeAuth()). + * NO, otherwise. + */ + extern BOOL HTAA_shouldRetryWithAuth(char *start_of_headers, + size_t length, + int soc, + int IsProxy); + +/* + * Function to allow clearing of all Authorization info + * via a browser command. - FM + */ + extern void HTClearHTTPAuthInfo(void); + +/* + * Check if a hostname-string contains user information. + */ + extern BOOL HTAA_HaveUserinfo(const char *hostname); + +/* + +Enabling Gateway httpds to Forward Authorization + + These functions should only be called from daemon code, and HTAAForwardAuth_reset() + must be called before the next request is handled to make sure that authorization + string isn't cached in daemon so that other people can access private files using + somebody else's previous authorization information. + + */ + + extern void HTAAForwardAuth_set(const char *scheme_name, + const char *scheme_specifics); + extern void HTAAForwardAuth_reset(void); + +#ifdef __cplusplus +} +#endif +#endif /* NOT HTAABROW_H */ diff --git a/WWW/Library/Implementation/HTAAProt.c b/WWW/Library/Implementation/HTAAProt.c new file mode 100644 index 0000000..d5117f1 --- /dev/null +++ b/WWW/Library/Implementation/HTAAProt.c @@ -0,0 +1,738 @@ +/* + * $LynxId: HTAAProt.c,v 1.34 2016/11/24 15:29:50 tom Exp $ + * + * MODULE HTAAProt.c + * PROTECTION FILE PARSING MODULE + * + * AUTHORS: + * AL Ari Luotonen luotonen@dxcern.cern.ch + * MD Mark Donszelmann duns@vxdeop.cern.ch + * + * HISTORY: + * 20 Oct 93 AL Now finds uid/gid for nobody/nogroup by name + * (doesn't use default 65534 right away). + * Also understands negative uids/gids. + * 14 Nov 93 MD Added VMS compatibility + * + * BUGS: + * + * + */ + +#include + +#ifndef VMS +#ifndef NOUSERS +#include /* Unix password file routine: getpwnam() */ +#include /* Unix group file routine: getgrnam() */ +#endif /* NOUSERS */ +#endif /* not VMS */ + +#include +#include /* Lexical analysor */ +#include /* Implemented here */ + +#include +#include + +#define NOBODY 65534 /* -2 in 16-bit environment */ +#define NONESUCH 65533 /* -3 in 16-bit environment */ + +/* + * Protection setup caching + */ +typedef struct { + char *prot_filename; + HTAAProt *prot; +} HTAAProtCache; + +static HTList *prot_cache = NULL; /* Protection setup cache. */ +static HTAAProt *default_prot = NULL; /* Default protection. */ +static HTAAProt *current_prot = NULL; /* Current protection mode */ + + /* which is set up by callbacks */ + /* from the rule system when */ + /* a "protect" rule is matched. */ + +#ifndef NOUSERS +/* static isNumber() + * DOES A CHARACTER STRING REPRESENT A NUMBER + */ +static BOOL isNumber(const char *s) +{ + const char *cur = s; + + if (isEmpty(s)) + return NO; + + if (*cur == '-') + cur++; /* Allow initial minus sign in a number */ + + while (*cur) { + if (*cur < '0' || *cur > '9') + return NO; + cur++; + } + return YES; +} + +/* PUBLIC HTAA_getUid() + * GET THE USER ID TO CHANGE THE PROCESS UID TO + * ON ENTRY: + * No arguments. + * + * ON EXIT: + * returns the uid number to give to setuid() system call. + * Default is 65534 (nobody). + */ +int HTAA_getUid(void) +{ + int uid; + + if (current_prot && current_prot->uid_name) { + if (isNumber(current_prot->uid_name)) { + uid = atoi(current_prot->uid_name); + if ((*HTAA_UidToName(uid)) != '\0') { + return uid; + } + } else { /* User name (not a number) */ + if ((uid = HTAA_NameToUid(current_prot->uid_name)) != NONESUCH) { + return uid; + } + } + } + /* + * Ok, then let's get uid for nobody. + */ + if ((uid = HTAA_NameToUid("nobody")) != NONESUCH) { + return uid; + } + /* + * Ok, then use default. + */ + return NOBODY; /* nobody */ +} + +/* PUBLIC HTAA_getGid() + * GET THE GROUP ID TO CHANGE THE PROCESS GID TO + * ON ENTRY: + * No arguments. + * + * ON EXIT: + * returns the uid number to give to setgid() system call. + * Default is 65534 (nogroup). + */ +int HTAA_getGid(void) +{ + int gid; + + if (current_prot && current_prot->gid_name) { + if (isNumber(current_prot->gid_name)) { + gid = atoi(current_prot->gid_name); + if (*HTAA_GidToName(gid) != '\0') { + return gid; + } + } else { /* Group name (not number) */ + if ((gid = HTAA_NameToGid(current_prot->gid_name)) != NONESUCH) { + return gid; + } + } + } + /* + * Ok, then let's get gid for nogroup. + */ + if ((gid = HTAA_NameToGid("nogroup")) != NONESUCH) { + return gid; + } + /* + * Ok, then use default. + */ + return NOBODY; /* nogroup */ +} +#endif /* !NOUSERS */ + +/* static HTAA_setIds() + * SET UID AND GID (AS NAMES OR NUMBERS) + * TO HTAAProt STRUCTURE + * ON ENTRY: + * prot destination. + * ids is a string like "james.www" or "1422.69" etc. + * giving uid and gid. + * + * ON EXIT: + * returns nothing. + */ +static void HTAA_setIds(HTAAProt *prot, const char *ids) +{ + if (ids) { + char *local_copy = NULL; + char *point; + + StrAllocCopy(local_copy, ids); + point = StrChr(local_copy, '.'); + if (point) { + *(point++) = (char) 0; + StrAllocCopy(prot->gid_name, point); + } else { + StrAllocCopy(prot->gid_name, "nogroup"); + } + StrAllocCopy(prot->uid_name, local_copy); + FREE(local_copy); + } else { + StrAllocCopy(prot->uid_name, "nobody"); + StrAllocCopy(prot->gid_name, "nogroup"); + } +} + +/* static HTAA_parseProtFile() + * PARSE A PROTECTION SETUP FILE AND + * PUT THE RESULT IN A HTAAProt STRUCTURE + * ON ENTRY: + * prot destination structure. + * fp open protection file. + * + * ON EXIT: + * returns nothing. + */ +static void HTAA_parseProtFile(HTAAProt *prot, FILE *fp) +{ + if (prot && fp) { + LexItem lex_item; + char *fieldname = NULL; + + while (LEX_EOF != (lex_item = lex(fp))) { + + while (lex_item == LEX_REC_SEP) /* Ignore empty lines */ + lex_item = lex(fp); + + if (lex_item == LEX_EOF) /* End of file */ + break; + + if (lex_item == LEX_ALPH_STR) { /* Valid setup record */ + + StrAllocCopy(fieldname, HTlex_buffer); + + if (LEX_FIELD_SEP != (lex_item = lex(fp))) + unlex(lex_item); /* If someone wants to use colon */ + /* after field name it's ok, but */ + /* not required. Here we read it. */ + + if (0 == strncasecomp(fieldname, "Auth", 4)) { + lex_item = lex(fp); + while (lex_item == LEX_ALPH_STR) { + HTAAScheme scheme = HTAAScheme_enum(HTlex_buffer); + + if (scheme != HTAA_UNKNOWN) { + if (!prot->valid_schemes) + prot->valid_schemes = HTList_new(); + HTList_addObject(prot->valid_schemes, (void *) scheme); + CTRACE((tfp, "%s %s `%s'\n", + "HTAA_parseProtFile: valid", + "authentication scheme:", + HTAAScheme_name(scheme))); + } else { + CTRACE((tfp, "%s %s `%s'\n", + "HTAA_parseProtFile: unknown", + "authentication scheme:", + HTlex_buffer)); + } + + if (LEX_ITEM_SEP != (lex_item = lex(fp))) + break; + /* + * Here lex_item == LEX_ITEM_SEP; after item separator + * it is ok to have one or more newlines (LEX_REC_SEP) + * and they are ignored (continuation line). + */ + do { + lex_item = lex(fp); + } while (lex_item == LEX_REC_SEP); + } /* while items in list */ + } + /* if "Authenticate" */ + else if (0 == strncasecomp(fieldname, "mask", 4)) { + prot->mask_group = HTAA_parseGroupDef(fp); + lex_item = LEX_REC_SEP; /*groupdef parser read this already */ + if (TRACE) { + if (prot->mask_group) { + fprintf(tfp, + "HTAA_parseProtFile: Mask group:\n"); + HTAA_printGroupDef(prot->mask_group); + } else + fprintf(tfp, + "HTAA_parseProtFile: Mask group syntax error\n"); + } + } + /* if "Mask" */ + else { /* Just a name-value pair, put it to assoclist */ + + if (LEX_ALPH_STR == (lex_item = lex(fp))) { + if (!prot->values) + prot->values = HTAssocList_new(); + HTAssocList_add(prot->values, fieldname, HTlex_buffer); + lex_item = lex(fp); /* Read record separator */ + CTRACE((tfp, "%s `%s' bound to value `%s'\n", + "HTAA_parseProtFile: Name", + fieldname, HTlex_buffer)); + } + } /* else name-value pair */ + + } + /* if valid field */ + if (lex_item != LEX_EOF && lex_item != LEX_REC_SEP) { + CTRACE((tfp, "%s %s %d (that line ignored)\n", + "HTAA_parseProtFile: Syntax error", + "in protection setup file at line", + HTlex_line)); + do { + lex_item = lex(fp); + } while (lex_item != LEX_EOF && lex_item != LEX_REC_SEP); + } /* if syntax error */ + } /* while not end-of-file */ + FREE(fieldname); + } /* if valid parameters */ +} + +/* static HTAAProt_new() + * ALLOCATE A NEW HTAAProt STRUCTURE AND + * INITIALIZE IT FROM PROTECTION SETUP FILE + * ON ENTRY: + * cur_docname current filename after rule translations. + * prot_filename protection setup file name. + * If NULL, not an error. + * ids Uid and gid names or numbers, + * examples: + * james ( <=> james.nogroup) + * .www ( <=> nobody.www) + * james.www + * james.69 + * 1422.69 + * 1422.www + * + * May be NULL, defaults to nobody.nogroup. + * Should be NULL, if prot_file is NULL. + * + * ON EXIT: + * returns returns a new and initialized protection + * setup structure. + * If setup file is already read in (found + * in cache), only sets uid_name and gid + * fields, and returns that. + */ +static HTAAProt *HTAAProt_new(const char *cur_docname, + const char *prot_filename, + const char *ids) +{ + HTList *cur = prot_cache; + HTAAProtCache *cache_item = NULL; + HTAAProt *prot; + FILE *fp; + + if (!prot_cache) + prot_cache = HTList_new(); + + while (NULL != (cache_item = (HTAAProtCache *) HTList_nextObject(cur))) { + if (!strcmp(cache_item->prot_filename, prot_filename)) + break; + } + if (cache_item) { + prot = cache_item->prot; + CTRACE((tfp, "%s `%s' already in cache\n", + "HTAAProt_new: Protection file", prot_filename)); + } else { + CTRACE((tfp, "HTAAProt_new: Loading protection file `%s'\n", + prot_filename)); + + if ((prot = typecalloc(HTAAProt)) == 0) + outofmem(__FILE__, "HTAAProt_new"); + + prot->ctemplate = NULL; + prot->filename = NULL; + prot->uid_name = NULL; + prot->gid_name = NULL; + prot->valid_schemes = HTList_new(); + prot->mask_group = NULL; /* Masking disabled by defaults */ + prot->values = HTAssocList_new(); + + if (prot_filename && NULL != (fp = fopen(prot_filename, TXT_R))) { + HTAA_parseProtFile(prot, fp); + fclose(fp); + if ((cache_item = typecalloc(HTAAProtCache)) == 0) + outofmem(__FILE__, "HTAAProt_new"); + + cache_item->prot = prot; + cache_item->prot_filename = NULL; + StrAllocCopy(cache_item->prot_filename, prot_filename); + HTList_addObject(prot_cache, (void *) cache_item); + } else { + CTRACE((tfp, "HTAAProt_new: %s `%s'\n", + "Unable to open protection setup file", + NONNULL(prot_filename))); + } + } + + if (cur_docname) + StrAllocCopy(prot->filename, cur_docname); + HTAA_setIds(prot, ids); + + return prot; +} + +/* PUBLIC HTAA_setDefaultProtection() + * SET THE DEFAULT PROTECTION MODE + * (called by rule system when a + * "defprot" rule is matched) + * ON ENTRY: + * cur_docname is the current result of rule translations. + * prot_filename is the protection setup file (second argument + * for "defprot" rule, optional) + * ids contains user and group names separated by + * a dot, corresponding to the uid + * gid under which the server should run, + * default is "nobody.nogroup" (third argument + * for "defprot" rule, optional; can be given + * only if protection setup file is also given). + * + * ON EXIT: + * returns nothing. + * Sets the module-wide variable default_prot. + */ +void HTAA_setDefaultProtection(const char *cur_docname, + const char *prot_filename, + const char *ids) +{ + default_prot = NULL; /* Not free()'d because this is in cache */ + + if (prot_filename) { + default_prot = HTAAProt_new(cur_docname, prot_filename, ids); + } else { + CTRACE((tfp, "%s %s\n", + "HTAA_setDefaultProtection: ERROR: Protection file", + "not specified (obligatory for DefProt rule)!!\n")); + } +} + +/* PUBLIC HTAA_setCurrentProtection() + * SET THE CURRENT PROTECTION MODE + * (called by rule system when a + * "protect" rule is matched) + * ON ENTRY: + * cur_docname is the current result of rule translations. + * prot_filename is the protection setup file (second argument + * for "protect" rule, optional) + * ids contains user and group names separated by + * a dot, corresponding to the uid + * gid under which the server should run, + * default is "nobody.nogroup" (third argument + * for "protect" rule, optional; can be given + * only if protection setup file is also given). + * + * ON EXIT: + * returns nothing. + * Sets the module-wide variable current_prot. + */ +void HTAA_setCurrentProtection(const char *cur_docname, + const char *prot_filename, + const char *ids) +{ + current_prot = NULL; /* Not free()'d because this is in cache */ + + if (prot_filename) { + current_prot = HTAAProt_new(cur_docname, prot_filename, ids); + } else { + if (default_prot) { + current_prot = default_prot; + HTAA_setIds(current_prot, ids); + CTRACE((tfp, "%s %s %s\n", + "HTAA_setCurrentProtection: Protection file", + "not specified for Protect rule", + "-- using default protection")); + } else { + CTRACE((tfp, "%s %s %s\n", + "HTAA_setCurrentProtection: ERROR: Protection", + "file not specified for Protect rule, and", + "default protection is not set!!")); + } + } +} + +/* PUBLIC HTAA_getCurrentProtection() + * GET CURRENT PROTECTION SETUP STRUCTURE + * (this is set up by callbacks made from + * the rule system when matching "protect" + * (and "defprot") rules) + * ON ENTRY: + * HTTranslate() must have been called before calling + * this function. + * + * ON EXIT: + * returns a HTAAProt structure representing the + * protection setup of the HTTranslate()'d file. + * This must not be free()'d. + */ +HTAAProt *HTAA_getCurrentProtection(void) +{ + return current_prot; +} + +/* PUBLIC HTAA_getDefaultProtection() + * GET DEFAULT PROTECTION SETUP STRUCTURE + * AND SET IT TO CURRENT PROTECTION + * (this is set up by callbacks made from + * the rule system when matching "defprot" + * rules) + * ON ENTRY: + * HTTranslate() must have been called before calling + * this function. + * + * ON EXIT: + * returns a HTAAProt structure representing the + * default protection setup of the HTTranslate()'d + * file (if HTAA_getCurrentProtection() returned + * NULL, i.e., if there is no "protect" rule + * but ACL exists, and we need to know default + * protection settings). + * This must not be free()'d. + * IMPORTANT: + * As a side-effect this tells the protection system that + * the file is in fact protected and sets the current + * protection mode to default. + */ +HTAAProt *HTAA_getDefaultProtection(void) +{ + if (!current_prot) { + current_prot = default_prot; + default_prot = NULL; + } + return current_prot; +} + +/* SERVER INTERNAL HTAA_clearProtections() + * CLEAR DOCUMENT PROTECTION MODE + * (ALSO DEFAULT PROTECTION) + * (called by the rule system) + * ON ENTRY: + * No arguments. + * + * ON EXIT: + * returns nothing. + * Frees the memory used by protection information. + */ +void HTAA_clearProtections(void) +{ + current_prot = NULL; /* These are not freed because */ + default_prot = NULL; /* they are actually in cache. */ +} + +typedef struct { + char *name; + int user; +} USER_DATA; + +#ifndef NOUSERS +static HTList *known_grp = NULL; +static HTList *known_pwd = NULL; +static BOOL uidgid_cache_inited = NO; +#endif + +#ifdef LY_FIND_LEAKS +static void clear_uidgid_cache(void) +{ +#ifndef NOUSERS + USER_DATA *data; + + if (known_grp) { + while ((data = HTList_removeLastObject(known_grp)) != NULL) { + FREE(data->name); + FREE(data); + } + FREE(known_grp); + } + if (known_pwd) { + while ((data = HTList_removeLastObject(known_pwd)) != NULL) { + FREE(data->name); + FREE(data); + } + FREE(known_pwd); + } +#endif +} +#endif /* LY_FIND_LEAKS */ + +#ifndef NOUSERS +static void save_gid_info(const char *name, int user) +{ + USER_DATA *data = typecalloc(USER_DATA); + + if (!data) + return; + if (!known_grp) { + known_grp = HTList_new(); + if (!uidgid_cache_inited) { +#ifdef LY_FIND_LEAKS + atexit(clear_uidgid_cache); +#endif + uidgid_cache_inited = YES; + } + } + StrAllocCopy(data->name, name); + data->user = user; + HTList_addObject(known_grp, data); +} +#endif /* NOUSERS */ + +#ifndef NOUSERS +static void save_uid_info(const char *name, int user) +{ + USER_DATA *data = typecalloc(USER_DATA); + + if (!data) + return; + if (!known_pwd) { + known_pwd = HTList_new(); + if (!uidgid_cache_inited) { +#ifdef LY_FIND_LEAKS + atexit(clear_uidgid_cache); +#endif + uidgid_cache_inited = YES; + } + } + StrAllocCopy(data->name, name); + data->user = user; + HTList_addObject(known_pwd, data); +} +#endif /* !NOUSERS */ + +/* PUBLIC HTAA_UidToName + * GET THE USER NAME + * ON ENTRY: + * The user-id + * + * ON EXIT: + * returns the user name, or an empty string if not found. + */ +const char *HTAA_UidToName(int uid GCC_UNUSED) +{ +#ifndef NOUSERS + struct passwd *pw; + HTList *me = known_pwd; + + while (HTList_nextObject(me)) { + USER_DATA *data = (USER_DATA *) (me->object); + + if (uid == data->user) + return data->name; + } + + if ((pw = getpwuid((uid_t) uid)) != 0 + && pw->pw_name != 0) { + CTRACE((tfp, "%s(%d) returned (%s:%d:...)\n", + "HTAA_UidToName: getpwuid", + uid, + pw->pw_name, (int) pw->pw_uid)); + save_uid_info(pw->pw_name, (int) pw->pw_uid); + return pw->pw_name; + } +#endif + return ""; +} + +/* PUBLIC HTAA_NameToUid + * GET THE USER ID + * ON ENTRY: + * The user-name + * + * ON EXIT: + * returns the user id, or NONESUCH if not found. + */ +int HTAA_NameToUid(const char *name GCC_UNUSED) +{ +#ifndef NOUSERS + struct passwd *pw; + HTList *me = known_pwd; + + while (HTList_nextObject(me)) { + USER_DATA *data = (USER_DATA *) (me->object); + + if (!strcmp(name, data->name)) + return data->user; + } + + if ((pw = getpwnam(name)) != 0) { + CTRACE((tfp, "%s(%s) returned (%s:%d:...)\n", + "HTAA_NameToUid: getpwnam", + name, + pw->pw_name, (int) pw->pw_uid)); + save_uid_info(pw->pw_name, (int) pw->pw_uid); + return (int) pw->pw_uid; + } +#endif + return NONESUCH; +} + +/* PUBLIC HTAA_GidToName + * GET THE GROUP NAME + * ON ENTRY: + * The group-id + * + * ON EXIT: + * returns the group name, or an empty string if not found. + */ +const char *HTAA_GidToName(int gid GCC_UNUSED) +{ +#ifndef NOUSERS + struct group *gr; + HTList *me = known_grp; + + while (HTList_nextObject(me)) { + USER_DATA *data = (USER_DATA *) (me->object); + + if (gid == data->user) + return data->name; + } + + if ((gr = getgrgid((gid_t) gid)) != 0 + && gr->gr_name != 0) { + CTRACE((tfp, "%s(%d) returned (%s:%d:...)\n", + "HTAA_GidToName: getgrgid", + gid, + gr->gr_name, (int) gr->gr_gid)); + save_gid_info(gr->gr_name, (int) gr->gr_gid); + return gr->gr_name; + } +#endif + return ""; +} + +/* PUBLIC HTAA_NameToGid + * GET THE GROUP ID + * ON ENTRY: + * The group-name + * + * ON EXIT: + * returns the group id, or NONESUCH if not found. + */ +int HTAA_NameToGid(const char *name GCC_UNUSED) +{ +#ifndef NOUSERS + struct group *gr; + HTList *me = known_grp; + + while (HTList_nextObject(me)) { + USER_DATA *data = (USER_DATA *) (me->object); + + if (!strcmp(name, data->name)) + return data->user; + } + + if ((gr = getgrnam(name)) != 0) { + CTRACE((tfp, "%s(%s) returned (%s:%d:...)\n", + "HTAA_NameToGid: getgrnam", + name, + gr->gr_name, (int) gr->gr_gid)); + save_gid_info(gr->gr_name, (int) gr->gr_gid); + return (int) gr->gr_gid; + } +#endif + return NONESUCH; +} diff --git a/WWW/Library/Implementation/HTAAProt.h b/WWW/Library/Implementation/HTAAProt.h new file mode 100644 index 0000000..22e3d92 --- /dev/null +++ b/WWW/Library/Implementation/HTAAProt.h @@ -0,0 +1,226 @@ +/* PROTECTION SETUP FILE + + */ + +#ifndef HTAAPROT_H +#define HTAAPROT_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif +/* + +Server's Representation of Document (Tree) Protections + + */ typedef struct { + char *ctemplate; /* Template for this protection */ + char *filename; /* Current document file */ + char *uid_name; /* Effective uid (name of it) */ + char *gid_name; /* Effective gid (name of it) */ + GroupDef *mask_group; /* Allowed users and IP addresses */ + HTList *valid_schemes; /* Valid authentication schemes */ + HTAssocList *values; /* Association list for scheme specific */ + /* parameters. */ + } HTAAProt; + +/* + +Callbacks for rule system + + The following three functioncs are called by the rule system: + + HTAA_clearProtections() when starting to translate a filename + + HTAA_setDefaultProtection() when "defprot" rule is matched + + HTAA_setCurrentProtection() when "protect" rule is matched + + Protection setup files are cached by these functions. + + */ + +/* PUBLIC HTAA_setDefaultProtection() + * SET THE DEFAULT PROTECTION MODE + * (called by rule system when a + * "defprot" rule is matched) + * ON ENTRY: + * cur_docname is the current result of rule translations. + * prot_filename is the protection setup file (second argument + * for "defprot" rule, optional) + * eff_ids contains user and group names separated by + * a dot, corresponding to the effective uid + * gid under which the server should run, + * default is "nobody.nogroup" (third argument + * for "defprot" rule, optional; can be given + * only if protection setup file is also given). + * + * ON EXIT: + * returns nothing. + * Sets the module-wide variable default_prot. + */ + extern void HTAA_setDefaultProtection(const char *cur_docname, + const char *prot_filename, + const char *eff_ids); + +/* PUBLIC HTAA_setCurrentProtection() + * SET THE CURRENT PROTECTION MODE + * (called by rule system when a + * "protect" rule is matched) + * ON ENTRY: + * cur_docname is the current result of rule translations. + * prot_filename is the protection setup file (second argument + * for "protect" rule, optional) + * eff_ids contains user and group names separated by + * a dot, corresponding to the effective uid + * gid under which the server should run, + * default is "nobody.nogroup" (third argument + * for "protect" rule, optional; can be given + * only if protection setup file is also given). + * + * ON EXIT: + * returns nothing. + * Sets the module-wide variable current_prot. + */ + extern void HTAA_setCurrentProtection(const char *cur_docname, + const char *prot_filename, + const char *eff_ids); + +/* SERVER INTERNAL HTAA_clearProtections() + * CLEAR DOCUMENT PROTECTION MODE + * (ALSO DEFAULT PROTECTION) + * (called by the rule system) + * ON ENTRY: + * No arguments. + * + * ON EXIT: + * returns nothing. + * Frees the memory used by protection information. + */ + extern void HTAA_clearProtections(void); + +/* + +Getting Protection Settings + + HTAA_getCurrentProtection() returns the current protection mode (if there was a + "protect" rule). NULL, if no "protect" rule has been matched. + + HTAA_getDefaultProtection() sets the current protection mode to what it was set to + by "defprot" rule and also returns it (therefore after this call also + HTAA_getCurrentProtection() returns the same structure. + + */ + +/* PUBLIC HTAA_getCurrentProtection() + * GET CURRENT PROTECTION SETUP STRUCTURE + * (this is set up by callbacks made from + * the rule system when matching "protect" + * (and "defprot") rules) + * ON ENTRY: + * HTTranslate() must have been called before calling + * this function. + * + * ON EXIT: + * returns a HTAAProt structure representing the + * protection setup of the HTTranslate()'d file. + * This must not be free()'d. + */ + extern HTAAProt *HTAA_getCurrentProtection(void); + +/* PUBLIC HTAA_getDefaultProtection() + * GET DEFAULT PROTECTION SETUP STRUCTURE + * (this is set up by callbacks made from + * the rule system when matching "defprot" + * rules) + * ON ENTRY: + * HTTranslate() must have been called before calling + * this function. + * + * ON EXIT: + * returns a HTAAProt structure representing the + * default protection setup of the HTTranslate()'d + * file (if HTAA_getCurrentProtection() returned + * NULL, i.e., if there is no "protect" rule + * but ACL exists, and we need to know default + * protection settings). + * This must not be free()'d. + */ + extern HTAAProt *HTAA_getDefaultProtection(void); + +/* + +Get User and Group IDs to Which Set to + + */ + +#ifndef NOUSERS +/* PUBLIC HTAA_getUid() + * GET THE USER ID TO CHANGE THE PROCESS UID TO + * ON ENTRY: + * No arguments. + * + * ON EXIT: + * returns the uid number to give to setuid() system call. + * Default is 65534 (nobody). + */ + extern int HTAA_getUid(void); + +/* PUBLIC HTAA_getGid() + * GET THE GROUP ID TO CHANGE THE PROCESS GID TO + * ON ENTRY: + * No arguments. + * + * ON EXIT: + * returns the uid number to give to setgid() system call. + * Default is 65534 (nogroup). + */ + extern int HTAA_getGid(void); +#endif /* !NOUSERS */ + +/* PUBLIC HTAA_UidToName + * GET THE USER NAME + * ON ENTRY: + * The user-id + * + * ON EXIT: + * returns the user name + */ + extern const char *HTAA_UidToName(int uid); + +/* PUBLIC HTAA_NameToUid + * GET THE USER ID + * ON ENTRY: + * The user-name + * + * ON EXIT: + * returns the user id + */ + extern int HTAA_NameToUid(const char *name); + +/* PUBLIC HTAA_GidToName + * GET THE GROUP NAME + * ON ENTRY: + * The group-id + * + * ON EXIT: + * returns the group name + */ + extern const char *HTAA_GidToName(int gid); + +/* PUBLIC HTAA_NameToGid + * GET THE GROUP ID + * ON ENTRY: + * The group-name + * + * ON EXIT: + * returns the group id + */ + extern int HTAA_NameToGid(const char *name); + +#ifdef __cplusplus +} +#endif +#endif /* not HTAAPROT_H */ diff --git a/WWW/Library/Implementation/HTAAUtil.c b/WWW/Library/Implementation/HTAAUtil.c new file mode 100644 index 0000000..1be26f9 --- /dev/null +++ b/WWW/Library/Implementation/HTAAUtil.c @@ -0,0 +1,605 @@ +/* + * $LynxId: HTAAUtil.c,v 1.36 2016/11/24 15:29:50 tom Exp $ + * + * MODULE HTAAUtil.c + * COMMON PARTS OF ACCESS AUTHORIZATION MODULE + * FOR BOTH SERVER AND BROWSER + * + * IMPORTANT: + * Routines in this module use dynamic allocation, but free + * automatically all the memory reserved by them. + * + * Therefore the caller never has to (and never should) + * free() any object returned by these functions. + * + * Therefore also all the strings returned by this package + * are only valid until the next call to the same function + * is made. This approach is selected, because of the nature + * of access authorization: no string returned by the package + * needs to be valid longer than until the next call. + * + * This also makes it easy to plug the AA package in: + * you don't have to ponder whether to free() something + * here or is it done somewhere else (because it is always + * done somewhere else). + * + * The strings that the package needs to store are copied + * so the original strings given as parameters to AA + * functions may be freed or modified with no side effects. + * + * The AA package does not free() anything else than what + * it has itself allocated. + * + * AA (Access Authorization) package means modules which + * names start with HTAA. + * + * AUTHORS: + * AL Ari Luotonen luotonen@dxcern.cern.ch + * MD Mark Donszelmann duns@vxdeop.cern.ch + * + * HISTORY: + * 8 Nov 93 MD (VMS only) Added case insensitive comparison in HTAA_templateCaseMatch + * + * + * BUGS: + * + * + */ + +#include + +#include /* Implemented here */ +#include /* Assoc list */ +#include +#include + +#include +#include +#include + +/* PUBLIC HTAAScheme_enum() + * TRANSLATE SCHEME NAME INTO + * A SCHEME ENUMERATION + * + * ON ENTRY: + * name is a string representing the scheme name. + * + * ON EXIT: + * returns the enumerated constant for that scheme. + */ +HTAAScheme HTAAScheme_enum(const char *name) +{ + char *upcased = NULL; + + if (!name) + return HTAA_UNKNOWN; + + StrAllocCopy(upcased, name); + LYUpperCase(upcased); + + if (!StrNCmp(upcased, "NONE", 4)) { + FREE(upcased); + return HTAA_NONE; + } else if (!StrNCmp(upcased, "BASIC", 5)) { + FREE(upcased); + return HTAA_BASIC; + } else if (!StrNCmp(upcased, "PUBKEY", 6)) { + FREE(upcased); + return HTAA_PUBKEY; + } else if (!StrNCmp(upcased, "KERBEROSV4", 10)) { + FREE(upcased); + return HTAA_KERBEROS_V4; + } else if (!StrNCmp(upcased, "KERBEROSV5", 10)) { + FREE(upcased); + return HTAA_KERBEROS_V5; + } else { + FREE(upcased); + return HTAA_UNKNOWN; + } +} + +/* PUBLIC HTAAScheme_name() + * GET THE NAME OF A GIVEN SCHEME + * ON ENTRY: + * scheme is one of the scheme enum values: + * HTAA_NONE, HTAA_BASIC, HTAA_PUBKEY, ... + * + * ON EXIT: + * returns the name of the scheme, i.e. + * "None", "Basic", "Pubkey", ... + */ +const char *HTAAScheme_name(HTAAScheme scheme) +{ + switch (scheme) { + case HTAA_NONE: + return "None"; + case HTAA_BASIC: + return "Basic"; + case HTAA_PUBKEY: + return "Pubkey"; + case HTAA_KERBEROS_V4: + return "KerberosV4"; + case HTAA_KERBEROS_V5: + return "KerberosV5"; + case HTAA_UNKNOWN: + return "UNKNOWN"; + default: + return "THIS-IS-A-BUG"; + } +} + +/* PUBLIC HTAAMethod_enum() + * TRANSLATE METHOD NAME INTO AN ENUMERATED VALUE + * ON ENTRY: + * name is the method name to translate. + * + * ON EXIT: + * returns HTAAMethod enumerated value corresponding + * to the given name. + */ +HTAAMethod HTAAMethod_enum(const char *name) +{ + if (!name) + return METHOD_UNKNOWN; + + if (0 == strcasecomp(name, "GET")) + return METHOD_GET; + else if (0 == strcasecomp(name, "PUT")) + return METHOD_PUT; + else + return METHOD_UNKNOWN; +} + +/* PUBLIC HTAAMethod_name() + * GET THE NAME OF A GIVEN METHOD + * ON ENTRY: + * method is one of the method enum values: + * METHOD_GET, METHOD_PUT, ... + * + * ON EXIT: + * returns the name of the scheme, i.e. + * "GET", "PUT", ... + */ +const char *HTAAMethod_name(HTAAMethod method) +{ + switch (method) { + case METHOD_GET: + return "GET"; + case METHOD_PUT: + return "PUT"; + case METHOD_UNKNOWN: + return "UNKNOWN"; + default: + return "THIS-IS-A-BUG"; + } +} + +/* PUBLIC HTAAMethod_inList() + * IS A METHOD IN A LIST OF METHOD NAMES + * ON ENTRY: + * method is the method to look for. + * list is a list of method names. + * + * ON EXIT: + * returns YES, if method was found. + * NO, if not found. + */ +BOOL HTAAMethod_inList(HTAAMethod method, HTList *list) +{ + HTList *cur = list; + char *item; + + while (NULL != (item = (char *) HTList_nextObject(cur))) { + CTRACE((tfp, " %s", item)); + if (method == HTAAMethod_enum(item)) + return YES; + } + + return NO; /* Not found */ +} + +/* PUBLIC HTAA_templateMatch() + * STRING COMPARISON FUNCTION FOR FILE NAMES + * WITH ONE WILDCARD * IN THE TEMPLATE + * NOTE: + * This is essentially the same code as in HTRules.c, but it + * cannot be used because it is embedded in between other code. + * (In fact, HTRules.c should use this routine, but then this + * routine would have to be more sophisticated... why is life + * sometimes so hard...) + * + * ON ENTRY: + * ctemplate is a template string to match the file name + * against, may contain a single wildcard + * character * which matches zero or more + * arbitrary characters. + * filename is the filename (or pathname) to be matched + * against the template. + * + * ON EXIT: + * returns YES, if filename matches the template. + * NO, otherwise. + */ +BOOL HTAA_templateMatch(const char *ctemplate, + const char *filename) +{ + const char *p = ctemplate; + const char *q = filename; + int m; + + for (; *p && *q && *p == *q; p++, q++) /* Find first mismatch */ + ; /* do nothing else */ + + if (!*p && !*q) + return YES; /* Equally long equal strings */ + else if ('*' == *p) { /* Wildcard */ + p++; /* Skip wildcard character */ + m = (int) (strlen(q) - strlen(p)); /* Amount to match to wildcard */ + if (m < 0) + return NO; /* No match, filename too short */ + else { /* Skip the matched characters and compare */ + if (strcmp(p, q + m)) + return NO; /* Tail mismatch */ + else + return YES; /* Tail match */ + } + /* if wildcard */ + } else + return NO; /* Length or character mismatch */ +} + +/* PUBLIC HTAA_templateCaseMatch() + * STRING COMPARISON FUNCTION FOR FILE NAMES + * WITH ONE WILDCARD * IN THE TEMPLATE (Case Insensitive) + * NOTE: + * This is essentially the same code as in HTAA_templateMatch, but + * it compares case insensitive (for VMS). Reason for this routine + * is that HTAA_templateMatch gets called from several places, also + * there where a case sensitive match is needed, so one cannot just + * change the HTAA_templateMatch routine for VMS. + * + * ON ENTRY: + * template is a template string to match the file name + * against, may contain a single wildcard + * character * which matches zero or more + * arbitrary characters. + * filename is the filename (or pathname) to be matched + * against the template. + * + * ON EXIT: + * returns YES, if filename matches the template. + * NO, otherwise. + */ +BOOL HTAA_templateCaseMatch(const char *ctemplate, + const char *filename) +{ + const char *p = ctemplate; + const char *q = filename; + int m; + + /* Find first mismatch */ + for (; *p && *q && TOUPPER(*p) == TOUPPER(*q); p++, q++) ; /* do nothing else */ + + if (!*p && !*q) + return YES; /* Equally long equal strings */ + else if ('*' == *p) { /* Wildcard */ + p++; /* Skip wildcard character */ + m = (int) (strlen(q) - strlen(p)); /* Amount to match to wildcard */ + if (m < 0) + return NO; /* No match, filename too short */ + else { /* Skip the matched characters and compare */ + if (strcasecomp(p, q + m)) + return NO; /* Tail mismatch */ + else + return YES; /* Tail match */ + } + /* if wildcard */ + } else + return NO; /* Length or character mismatch */ +} + +/* PUBLIC HTAA_makeProtectionTemplate() + * CREATE A PROTECTION TEMPLATE FOR THE FILES + * IN THE SAME DIRECTORY AS THE GIVEN FILE + * (Used by server if there is no fancier way for + * it to tell the client, and by browser if server + * didn't send WWW-ProtectionTemplate: field) + * ON ENTRY: + * docname is the document pathname (from URL). + * + * ON EXIT: + * returns a template matching docname, and other files + * files in that directory. + * + * E.g. /foo/bar/x.html => /foo/bar/ * + * ^ + * Space only to prevent it from + * being a comment marker here, + * there really isn't any space. + */ +char *HTAA_makeProtectionTemplate(const char *docname) +{ + char *ctemplate = NULL; + char *slash = NULL; + + if (docname) { + StrAllocCopy(ctemplate, docname); + slash = strrchr(ctemplate, '/'); + if (slash) + slash++; + else + slash = ctemplate; + *slash = '\0'; + StrAllocCat(ctemplate, "*"); + } else + StrAllocCopy(ctemplate, "*"); + + CTRACE((tfp, "make_template: made template `%s' for file `%s'\n", + ctemplate, docname)); + + return ctemplate; +} + +/* + * Skip leading whitespace from *s forward + */ +#define SKIPWS(s) while (*s==' ' || *s=='\t') s++; + +/* + * Kill trailing whitespace starting from *(s-1) backwards + */ +#define KILLWS(s) {char *c=s-1; while (*c==' ' || *c=='\t') *(c--)='\0';} + +/* PUBLIC HTAA_parseArgList() + * PARSE AN ARGUMENT LIST GIVEN IN A HEADER FIELD + * ON ENTRY: + * str is a comma-separated list: + * + * item, item, item + * where + * item ::= value + * | name=value + * | name="value" + * + * Leading and trailing whitespace is ignored + * everywhere except inside quotes, so the following + * examples are equal: + * + * name=value,foo=bar + * name="value",foo="bar" + * name = value , foo = bar + * name = "value" , foo = "bar" + * + * ON EXIT: + * returns a list of name-value pairs (actually HTAssocList*). + * For items with no name, just value, the name is + * the number of order number of that item. E.g. + * "1" for the first, etc. + */ +HTAssocList *HTAA_parseArgList(char *str) +{ + HTAssocList *assoc_list = HTAssocList_new(); + char *cur = NULL; + char *name = NULL; + int n = 0; + + if (!str) + return assoc_list; + + while (*str) { + SKIPWS(str); /* Skip leading whitespace */ + cur = str; + n++; + + while (*cur && *cur != '=' && *cur != ',') + cur++; /* Find end of name (or lonely value without a name) */ + KILLWS(cur); /* Kill trailing whitespace */ + + if (*cur == '=') { /* Name followed by a value */ + *(cur++) = '\0'; /* Terminate name */ + StrAllocCopy(name, str); + SKIPWS(cur); /* Skip WS leading the value */ + str = cur; + if (*str == '"') { /* Quoted value */ + str++; + cur = str; + while (*cur && *cur != '"') + cur++; + if (*cur == '"') + *(cur++) = '\0'; /* Terminate value */ + /* else it is lacking terminating quote */ + SKIPWS(cur); /* Skip WS leading comma */ + if (*cur == ',') + cur++; /* Skip separating colon */ + } else { /* Unquoted value */ + while (*cur && *cur != ',') + cur++; + KILLWS(cur); /* Kill trailing whitespace */ + if (*cur == ',') + *(cur++) = '\0'; + /* else *cur already NULL */ + } + } else { /* No name, just a value */ + if (*cur == ',') + *(cur++) = '\0'; /* Terminate value */ + /* else last value on line (already terminated by NULL) */ + HTSprintf0(&name, "%d", n); /* Item order number for name */ + } + HTAssocList_add(assoc_list, name, str); + str = cur; + } /* while *str */ + + FREE(name); + return assoc_list; +} + +/************** HEADER LINE READER -- DOES UNFOLDING *************************/ + +#define BUFFER_SIZE 1024 + +static size_t buffer_length; +static char *buffer = 0; +static char *start_pointer; +static char *end_pointer; +static int in_soc = -1; + +#ifdef LY_FIND_LEAKS +static void FreeHTAAUtil(void) +{ + FREE(buffer); +} +#endif /* LY_FIND_LEAKS */ + +/* PUBLIC HTAA_setupReader() + * SET UP HEADER LINE READER, i.e., give + * the already-read-but-not-yet-processed + * buffer of text to be read before more + * is read from the socket. + * ON ENTRY: + * start_of_headers is a pointer to a buffer containing + * the beginning of the header lines + * (rest will be read from a socket). + * length is the number of valid characters in + * 'start_of_headers' buffer. + * soc is the socket to use when start_of_headers + * buffer is used up. + * ON EXIT: + * returns nothing. + * Subsequent calls to HTAA_getUnfoldedLine() + * will use this buffer first and then + * proceed to read from socket. + */ +void HTAA_setupReader(char *start_of_headers, + size_t length, + int soc) +{ + if (!start_of_headers) + length = 0; /* initialize length (is this reached at all?) */ + if (buffer == NULL) { /* first call? */ + buffer_length = length; + if (buffer_length < BUFFER_SIZE) /* would fall below BUFFER_SIZE? */ + buffer_length = BUFFER_SIZE; + buffer = (char *) malloc((size_t) (sizeof(char) * (buffer_length + 1))); + } else if (length > buffer_length) { /* need more space? */ + buffer_length = length; + buffer = (char *) realloc((char *) buffer, + (size_t) (sizeof(char) * (buffer_length + 1))); + } + if (buffer == NULL) + outofmem(__FILE__, "HTAA_setupReader"); + +#ifdef LY_FIND_LEAKS + atexit(FreeHTAAUtil); +#endif + start_pointer = buffer; + if (start_of_headers) { + LYStrNCpy(buffer, start_of_headers, length); + end_pointer = buffer + length; + } else { + *start_pointer = '\0'; + end_pointer = start_pointer; + } + in_soc = soc; +} + +/* PUBLIC HTAA_getUnfoldedLine() + * READ AN UNFOLDED HEADER LINE FROM SOCKET + * ON ENTRY: + * HTAA_setupReader must absolutely be called before + * this function to set up internal buffer. + * + * ON EXIT: + * returns a newly-allocated character string representing + * the read line. The line is unfolded, i.e. + * lines that begin with whitespace are appended + * to current line. E.g. + * + * Field-Name: Blaa-Blaa + * This-Is-A-Continuation-Line + * Here-Is_Another + * + * is seen by the caller as: + * + * Field-Name: Blaa-Blaa This-Is-A-Continuation-Line Here-Is_Another + * + */ +char *HTAA_getUnfoldedLine(void) +{ + char *line = NULL; + char *cur; + int count; + BOOL peek_for_folding = NO; + + if (in_soc < 0) { + CTRACE((tfp, "%s %s\n", + "HTAA_getUnfoldedLine: buffer not initialized", + "with function HTAA_setupReader()")); + return NULL; + } + + for (;;) { + + /* Reading from socket */ + + if (start_pointer >= end_pointer) { /*Read the next block and continue */ +#ifdef USE_SSL + if (SSL_handle) + count = SSL_read(SSL_handle, buffer, BUFFER_SIZE); + else + count = NETREAD(in_soc, buffer, BUFFER_SIZE); +#else + count = NETREAD(in_soc, buffer, BUFFER_SIZE); +#endif /* USE_SSL */ + if (count <= 0) { + in_soc = -1; + return line; + } + if (count > (int) buffer_length) + count = (int) buffer_length; + start_pointer = buffer; + end_pointer = buffer + count; + *end_pointer = '\0'; +#ifdef NOT_ASCII + cur = start_pointer; + while (cur < end_pointer) { + *cur = TOASCII(*cur); + cur++; + } +#endif /*NOT_ASCII */ + } + cur = start_pointer; + + /* Unfolding */ + + if (peek_for_folding) { + if (*cur != ' ' && *cur != '\t') + return line; /* Ok, no continuation line */ + else /* So this is a continuation line, continue */ + peek_for_folding = NO; + } + + /* Finding end-of-line */ + + while (cur < end_pointer && *cur != '\n') /* Find the end-of-line */ + cur++; /* (or end-of-buffer). */ + + /* Terminating line */ + + if (cur < end_pointer) { /* So *cur==LF, terminate line */ + *cur = '\0'; /* Overwrite LF */ + if (*(cur - 1) == '\r') + *(cur - 1) = '\0'; /* Overwrite CR */ + peek_for_folding = YES; /* Check for a continuation line */ + } + + /* Copying the result */ + + if (line) + StrAllocCat(line, start_pointer); /* Append */ + else + StrAllocCopy(line, start_pointer); /* A new line */ + + start_pointer = cur + 1; /* Skip the read line */ + + } /* forever */ +} diff --git a/WWW/Library/Implementation/HTAAUtil.h b/WWW/Library/Implementation/HTAAUtil.h new file mode 100644 index 0000000..33a8ee3 --- /dev/null +++ b/WWW/Library/Implementation/HTAAUtil.h @@ -0,0 +1,318 @@ +/* + * $LynxId: HTAAUtil.h,v 1.13 2010/10/27 00:09:52 tom Exp $ + * + * Utilities for the Authorization parts of libwww + * COMMON PARTS OF AUTHORIZATION MODULE TO BOTH SERVER AND BROWSER + * + * This module is the interface to the common parts of Access Authorization (AA) package + * for both server and browser. Important to know about memory allocation: + * + * Routines in this module use dynamic allocation, but free automatically all the memory + * reserved by them. + * + * Therefore the caller never has to (and never should) free() any object returned by + * these functions. + * + * Therefore also all the strings returned by this package are only valid until the next + * call to the same function is made. This approach is selected, because of the nature of + * access authorization: no string returned by the package needs to be valid longer than + * until the next call. + * + * This also makes it easy to plug the AA package in: you don't have to ponder whether to + * free() something here or is it done somewhere else (because it is always done somewhere + * else). + * + * The strings that the package needs to store are copied so the original strings given as + * parameters to AA functions may be freed or modified with no side effects. + * + * Also note: The AA package does not free() anything else than what it has itself + * allocated. + * + */ + +#ifndef HTAAUTIL_H +#define HTAAUTIL_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif +/* + * Numeric constants + */ +#define MAX_USERNAME_LEN 16 /* @@ Longest allowed username */ +#define MAX_PASSWORD_LEN 3*13 /* @@ Longest allowed password */ + /* (encrypted, so really only 3*8) */ +#define MAX_METHODNAME_LEN 12 /* @@ Longest allowed method name */ +#define MAX_FIELDNAME_LEN 16 /* @@ Longest field name in */ + /* protection setup file */ +#define MAX_PATHNAME_LEN 80 /* @@ Longest passwd/group file */ +/* pathname to allow */ +/* + + Datatype definitions + + HTAASCHEME + + The enumeration HTAAScheme represents the possible authentication schemes used by the + WWW Access Authorization. + + */ typedef enum { + HTAA_UNKNOWN, + HTAA_NONE, + HTAA_BASIC, + HTAA_PUBKEY, + HTAA_KERBEROS_V4, + HTAA_KERBEROS_V5, + HTAA_MAX_SCHEMES /* THIS MUST ALWAYS BE LAST! Number of schemes */ + } HTAAScheme; + +/* + + ENUMERATION TO REPRESENT HTTP METHODS + + */ + + typedef enum { + METHOD_UNKNOWN, + METHOD_GET, + METHOD_PUT + } HTAAMethod; + +/* + +Authentication Schemes + + */ + +/* PUBLIC HTAAScheme_enum() + * TRANSLATE SCHEME NAME TO A SCHEME ENUMERATION + * ON ENTRY: + * name is a string representing the scheme name. + * + * ON EXIT: + * returns the enumerated constant for that scheme. + */ + extern HTAAScheme HTAAScheme_enum(const char *name); + +/* PUBLIC HTAAScheme_name() + * GET THE NAME OF A GIVEN SCHEME + * ON ENTRY: + * scheme is one of the scheme enum values: + * HTAA_NONE, HTAA_BASIC, HTAA_PUBKEY, ... + * + * ON EXIT: + * returns the name of the scheme, i.e. + * "none", "basic", "pubkey", ... + */ + extern const char *HTAAScheme_name(HTAAScheme scheme); + +/* + +Methods + + */ + +/* PUBLIC HTAAMethod_enum() + * TRANSLATE METHOD NAME INTO AN ENUMERATED VALUE + * ON ENTRY: + * name is the method name to translate. + * + * ON EXIT: + * returns HTAAMethod enumerated value corresponding + * to the given name. + */ + extern HTAAMethod HTAAMethod_enum(const char *name); + +/* PUBLIC HTAAMethod_name() + * GET THE NAME OF A GIVEN METHOD + * ON ENTRY: + * method is one of the method enum values: + * METHOD_GET, METHOD_PUT, ... + * + * ON EXIT: + * returns the name of the scheme, i.e. + * "GET", "PUT", ... + */ + extern const char *HTAAMethod_name(HTAAMethod method); + +/* PUBLIC HTAAMethod_inList() + * IS A METHOD IN A LIST OF METHOD NAMES + * ON ENTRY: + * method is the method to look for. + * list is a list of method names. + * + * ON EXIT: + * returns YES, if method was found. + * NO, if not found. + */ + extern BOOL HTAAMethod_inList(HTAAMethod method, HTList *list); + +/* + +Match Template Against Filename + + */ + +/* PUBLIC HTAA_templateMatch() + * STRING COMPARISON FUNCTION FOR FILE NAMES + * WITH ONE WILDCARD * IN THE TEMPLATE + * NOTE: + * This is essentially the same code as in HTRules.c, but it + * cannot be used because it is embedded in between other code. + * (In fact, HTRules.c should use this routine, but then this + * routine would have to be more sophisticated... why is life + * sometimes so hard...) + * + * ON ENTRY: + * ctemplate is a template string to match the file name + * against, may contain a single wildcard + * character * which matches zero or more + * arbitrary characters. + * filename is the filename (or pathname) to be matched + * against the template. + * + * ON EXIT: + * returns YES, if filename matches the template. + * NO, otherwise. + */ + extern BOOL HTAA_templateMatch(const char *ctemplate, + const char *filename); + +/* PUBLIC HTAA_templateCaseMatch() + * STRING COMPARISON FUNCTION FOR FILE NAMES + * WITH ONE WILDCARD * IN THE TEMPLATE (Case Insensitive) + * NOTE: + * This is essentially the same code as in HTAA_templateMatch, but + * it compares case insensitive (for VMS). Reason for this routine + * is that HTAA_templateMatch gets called from several places, also + * there where a case sensitive match is needed, so one cannot just + * change the HTAA_templateMatch routine for VMS. + * + * ON ENTRY: + * ctemplate is a template string to match the file name + * against, may contain a single wildcard + * character * which matches zero or more + * arbitrary characters. + * filename is the filename (or pathname) to be matched + * against the template. + * + * ON EXIT: + * returns YES, if filename matches the template. + * NO, otherwise. + */ + extern BOOL HTAA_templateCaseMatch(const char *ctemplate, + const char *filename); + +/* PUBLIC HTAA_makeProtectionTemplate() + * CREATE A PROTECTION TEMPLATE FOR THE FILES + * IN THE SAME DIRECTORY AS THE GIVEN FILE + * (Used by server if there is no fancier way for + * it to tell the client, and by browser if server + * didn't send WWW-ProtectionTemplate: field) + * ON ENTRY: + * docname is the document pathname (from URL). + * + * ON EXIT: + * returns a template matching docname, and other files + * files in that directory. + * + * E.g. /foo/bar/x.html => /foo/bar/ * + * ^ + * Space only to prevent it from + * being a comment marker here, + * there really isn't any space. + */ + extern char *HTAA_makeProtectionTemplate(const char *docname); + +/* + +MIME Argument List Parser + + */ + +/* PUBLIC HTAA_parseArgList() + * PARSE AN ARGUMENT LIST GIVEN IN A HEADER FIELD + * ON ENTRY: + * str is a comma-separated list: + * + * item, item, item + * where + * item ::= value + * | name=value + * | name="value" + * + * Leading and trailing whitespace is ignored + * everywhere except inside quotes, so the following + * examples are equal: + * + * name=value,foo=bar + * name="value",foo="bar" + * name = value , foo = bar + * name = "value" , foo = "bar" + * + * ON EXIT: + * returns a list of name-value pairs (actually HTAssocList*). + * For items with no name, just value, the name is + * the number of order number of that item. E.g. + * "1" for the first, etc. + */ + extern HTList *HTAA_parseArgList(char *str); + +/* + +Header Line Reader + + */ + +/* PUBLIC HTAA_setupReader() + * SET UP HEADER LINE READER, i.e., give + * the already-read-but-not-yet-processed + * buffer of text to be read before more + * is read from the socket. + * ON ENTRY: + * start_of_headers is a pointer to a buffer containing + * the beginning of the header lines + * (rest will be read from a socket). + * length is the number of valid characters in + * 'start_of_headers' buffer. + * soc is the socket to use when start_of_headers + * buffer is used up. + * ON EXIT: + * returns nothing. + * Subsequent calls to HTAA_getUnfoldedLine() + * will use this buffer first and then + * proceed to read from socket. + */ + extern void HTAA_setupReader(char *start_of_headers, + size_t length, + int soc); + +/* PUBLIC HTAA_getUnfoldedLine() + * READ AN UNFOLDED HEADER LINE FROM SOCKET + * ON ENTRY: + * HTAA_setupReader must absolutely be called before + * this function to set up internal buffer. + * + * ON EXIT: + * returns a newly-allocated character string representing + * the read line. The line is unfolded, i.e. + * lines that begin with whitespace are appended + * to current line. E.g. + * + * Field-Name: Blaa-Blaa + * This-Is-A-Continuation-Line + * Here-Is_Another + * + * is seen by the caller as: + * + * Field-Name: Blaa-Blaa This-Is-A-Continuation-Line Here-Is_Another + * + */ + extern char *HTAA_getUnfoldedLine(void); + +#ifdef __cplusplus +} +#endif +#endif /* NOT HTAAUTIL_H */ diff --git a/WWW/Library/Implementation/HTAccess.c b/WWW/Library/Implementation/HTAccess.c new file mode 100644 index 0000000..ba81b83 --- /dev/null +++ b/WWW/Library/Implementation/HTAccess.c @@ -0,0 +1,1303 @@ +/* + * $LynxId: HTAccess.c,v 1.87 2024/01/10 23:52:47 tom Exp $ + * + * Access Manager HTAccess.c + * ============== + * + * Authors + * TBL Tim Berners-Lee timbl@info.cern.ch + * JFG Jean-Francois Groff jfg@dxcern.cern.ch + * DD Denis DeLaRoca (310) 825-4580 + * FM Foteos Macrides macrides@sci.wfeb.edu + * PDM Danny Mayer mayer@ljo.dec.com + * + * History + * 8 Jun 92 Telnet hopping prohibited as telnet is not secure TBL + * 26 Jun 92 When over DECnet, suppressed FTP, Gopher and News. JFG + * 6 Oct 92 Moved HTClientHost and logfile into here. TBL + * 17 Dec 92 Tn3270 added, bug fix. DD + * 4 Feb 93 Access registration, Search escapes bad chars TBL + * PARAMETERS TO HTSEARCH AND HTLOADRELATIVE CHANGED + * 28 May 93 WAIS gateway explicit if no WAIS library linked in. + * 31 May 94 Added DIRECT_WAIS support for VMS. FM + * 27 Jan 95 Fixed proxy support to use NNTPSERVER for checking + * whether or not to use the proxy server. PDM + * 27 Jan 95 Ensured that proxy service will be overridden for files + * on the local host (because HTLoadFile() doesn't try ftp + * for those) and will substitute ftp for remote files. FM + * 28 Jan 95 Tweaked PDM's proxy override mods to handle port info + * for news and wais URL's. FM + * + * Bugs + * This module assumes that that the graphic object is hypertext, as it + * needs to select it when it has been loaded. A superclass needs to be + * defined which accepts select and select_anchor. + */ + +#ifdef VMS +#define DIRECT_WAIS +#endif /* VMS */ + +#include +#include +#include +/* + * Implements: + */ +#include + +/* + * Uses: + */ +#include +#include /* SCW */ + +#ifndef NO_RULES +#include +#endif + +#include +#include /* See bugs above */ +#include +#include +#include + +#include +#include +#include +#include +#include + +/* + * These flags may be set to modify the operation of this module + */ +char *HTClientHost = NULL; /* Name of remote login host if any */ +FILE *HTlogfile = NULL; /* File to which to output one-liners */ +BOOL HTSecure = NO; /* Disable access for telnet users? */ +BOOL HTPermitRedir = NO; /* Always allow redirection in getfile()? */ + +BOOL using_proxy = NO; /* are we using a proxy gateway? */ + +/* + * To generate other things, play with these: + */ +HTFormat HTOutputFormat = NULL; +HTStream *HTOutputStream = NULL; /* For non-interactive, set this */ + +static HTList *protocols = NULL; /* List of registered protocol descriptors */ + +char *use_this_url_instead = NULL; + +static int pushed_assume_LYhndl = -1; /* see LYUC* functions below - kw */ +static char *pushed_assume_MIMEname = NULL; + +#ifdef LY_FIND_LEAKS +static void free_protocols(void) +{ + HTList_delete(protocols); + protocols = NULL; + FREE(pushed_assume_MIMEname); /* shouldn't happen, just in case - kw */ +} +#endif /* LY_FIND_LEAKS */ + +/* Register a Protocol. HTRegisterProtocol() + * -------------------- + */ +BOOL HTRegisterProtocol(HTProtocol * protocol) +{ + if (!protocols) { + protocols = HTList_new(); +#ifdef LY_FIND_LEAKS + atexit(free_protocols); +#endif + } + HTList_addObject(protocols, protocol); + return YES; +} + +/* Register all known protocols. HTAccessInit() + * ----------------------------- + * + * Add to or subtract from this list if you add or remove protocol + * modules. This routine is called the first time the protocol list + * is needed, unless any protocols are already registered, in which + * case it is not called. Therefore the application can override + * this list. + * + * Compiling with NO_INIT prevents all known protocols from being + * forced in at link time. + */ +#ifndef NO_INIT +#ifdef GLOBALREF_IS_MACRO +extern GLOBALREF (HTProtocol, HTTP); +extern GLOBALREF (HTProtocol, HTTPS); +extern GLOBALREF (HTProtocol, HTFile); +extern GLOBALREF (HTProtocol, HTTelnet); +extern GLOBALREF (HTProtocol, HTTn3270); +extern GLOBALREF (HTProtocol, HTRlogin); + +#ifndef DECNET +#ifndef DISABLE_FTP +extern GLOBALREF (HTProtocol, HTFTP); +#endif /* DISABLE_FTP */ +#ifndef DISABLE_NEWS +extern GLOBALREF (HTProtocol, HTNews); +extern GLOBALREF (HTProtocol, HTNNTP); +extern GLOBALREF (HTProtocol, HTNewsPost); +extern GLOBALREF (HTProtocol, HTNewsReply); +extern GLOBALREF (HTProtocol, HTSNews); +extern GLOBALREF (HTProtocol, HTSNewsPost); +extern GLOBALREF (HTProtocol, HTSNewsReply); +#endif /* not DISABLE_NEWS */ +#ifndef DISABLE_GOPHER +extern GLOBALREF (HTProtocol, HTGopher); +extern GLOBALREF (HTProtocol, HTCSO); +#endif /* not DISABLE_GOPHER */ +#ifndef DISABLE_FINGER +extern GLOBALREF (HTProtocol, HTFinger); +#endif /* not DISABLE_FINGER */ +#ifdef DIRECT_WAIS +extern GLOBALREF (HTProtocol, HTWAIS); +#endif /* DIRECT_WAIS */ +#endif /* !DECNET */ +#else +GLOBALREF HTProtocol HTTP, HTTPS, HTFile, HTTelnet, HTTn3270, HTRlogin; + +#ifndef DECNET +#ifndef DISABLE_FTP +GLOBALREF HTProtocol HTFTP; +#endif /* DISABLE_FTP */ +#ifndef DISABLE_NEWS +GLOBALREF HTProtocol HTNews, HTNNTP, HTNewsPost, HTNewsReply; +GLOBALREF HTProtocol HTSNews, HTSNewsPost, HTSNewsReply; +#endif /* not DISABLE_NEWS */ +#ifndef DISABLE_GOPHER +GLOBALREF HTProtocol HTGopher, HTCSO; +#endif /* not DISABLE_GOPHER */ +#ifndef DISABLE_FINGER +GLOBALREF HTProtocol HTFinger; +#endif /* not DISABLE_FINGER */ +#ifdef DIRECT_WAIS +GLOBALREF HTProtocol HTWAIS; +#endif /* DIRECT_WAIS */ +#endif /* !DECNET */ +#endif /* GLOBALREF_IS_MACRO */ + +static void HTAccessInit(void) /* Call me once */ +{ + HTRegisterProtocol(&HTTP); + HTRegisterProtocol(&HTTPS); + HTRegisterProtocol(&HTFile); + HTRegisterProtocol(&HTTelnet); + HTRegisterProtocol(&HTTn3270); + HTRegisterProtocol(&HTRlogin); +#ifndef DECNET +#ifndef DISABLE_FTP + HTRegisterProtocol(&HTFTP); +#endif /* DISABLE_FTP */ +#ifndef DISABLE_NEWS + HTRegisterProtocol(&HTNews); + HTRegisterProtocol(&HTNNTP); + HTRegisterProtocol(&HTNewsPost); + HTRegisterProtocol(&HTNewsReply); + HTRegisterProtocol(&HTSNews); + HTRegisterProtocol(&HTSNewsPost); + HTRegisterProtocol(&HTSNewsReply); +#endif /* not DISABLE_NEWS */ +#ifndef DISABLE_GOPHER + HTRegisterProtocol(&HTGopher); + HTRegisterProtocol(&HTCSO); +#endif /* not DISABLE_GOPHER */ +#ifndef DISABLE_FINGER + HTRegisterProtocol(&HTFinger); +#endif /* not DISABLE_FINGER */ +#ifdef DIRECT_WAIS + HTRegisterProtocol(&HTWAIS); +#endif /* DIRECT_WAIS */ +#endif /* !DECNET */ + LYRegisterLynxProtocols(); +} +#endif /* !NO_INIT */ + +/* Check for proxy override. override_proxy() + * ------------------------- + * + * Check the no_proxy environment variable to get the list + * of hosts for which proxy server is not consulted. + * + * no_proxy is a comma- or space-separated list of machine + * or domain names, with optional :port part. If no :port + * part is present, it applies to all ports on that domain. + * + * Example: + * no_proxy="cern.ch,some.domain:8001" + * + * Use "*" to override all proxy service: + * no_proxy="*" + */ +BOOL override_proxy(const char *addr) +{ + const char *no_proxy = getenv("no_proxy"); + char *p = NULL; + char *at = NULL; + char *host = NULL; + char *Host = NULL; + char *acc_method = NULL; + int port = 0; + int h_len = 0; + + /* + * Check for global override. + */ + if (no_proxy) { + if (!strcmp(no_proxy, "*")) + return YES; + } + + /* + * Never proxy file:// URLs if they are on the local host. HTLoadFile() + * will not attempt ftp for those if direct access fails. We'll check that + * first, in case no_proxy hasn't been defined. - FM + */ + if (!addr) + return NO; + if (!(host = HTParse(addr, "", PARSE_HOST))) + return NO; + if (!*host) { + FREE(host); + return NO; + } + Host = (((at = StrChr(host, '@')) != NULL) ? (at + 1) : host); + + if ((acc_method = HTParse(addr, "", PARSE_ACCESS))) { + if (!strcmp("file", acc_method) && + (LYSameHostname(Host, "localhost") || + LYSameHostname(Host, HTHostName()))) { + FREE(host); + FREE(acc_method); + return YES; + } + FREE(acc_method); + } + + if (!no_proxy) { + FREE(host); + return NO; + } + + if (NULL != (p = HTParsePort(Host, &port))) { /* Port specified */ + *p = 0; /* Chop off port */ + } else { /* Use default port */ + acc_method = HTParse(addr, "", PARSE_ACCESS); + if (acc_method != NULL) { + /* *INDENT-OFF* */ + if (!strcmp(acc_method, "http")) port = 80; + else if (!strcmp(acc_method, "https")) port = 443; + else if (!strcmp(acc_method, "ftp")) port = 21; +#ifndef DISABLE_GOPHER + else if (!strcmp(acc_method, "gopher")) port = 70; +#endif + else if (!strcmp(acc_method, "cso")) port = 105; +#ifndef DISABLE_NEWS + else if (!strcmp(acc_method, "news")) port = 119; + else if (!strcmp(acc_method, "nntp")) port = 119; + else if (!strcmp(acc_method, "newspost")) port = 119; + else if (!strcmp(acc_method, "newsreply")) port = 119; + else if (!strcmp(acc_method, "snews")) port = 563; + else if (!strcmp(acc_method, "snewspost")) port = 563; + else if (!strcmp(acc_method, "snewsreply")) port = 563; +#endif + else if (!strcmp(acc_method, "wais")) port = 210; +#ifndef DISABLE_FINGER + else if (!strcmp(acc_method, "finger")) port = 79; +#endif + else if (!strcmp(acc_method, "telnet")) port = 23; + else if (!strcmp(acc_method, "tn3270")) port = 23; + else if (!strcmp(acc_method, "rlogin")) port = 513; + /* *INDENT-ON* */ + + FREE(acc_method); + } + } + if (!port) + port = 80; /* Default */ + h_len = (int) strlen(Host); + + while (*no_proxy) { + const char *end; + const char *colon = NULL; + int templ_port = 0; + int t_len; + int brackets = 0; + + while (*no_proxy && (WHITE(*no_proxy) || *no_proxy == ',')) + no_proxy++; /* Skip whitespace and separators */ + + end = no_proxy; + while (*end && !WHITE(*end) && *end != ',') { /* Find separator */ + if (!brackets && (*end == ':')) + colon = end; /* Port number given */ + else if (*end == '[') + ++brackets; + else if (*end == ']') + --brackets; + end++; + } + + if (colon) { + /* unlike HTParsePort(), this may be followed by non-digits */ + templ_port = atoi(colon + 1); + t_len = (int) (colon - no_proxy); + } else { + t_len = (int) (end - no_proxy); + } + + if ((!templ_port || templ_port == port) && + (t_len > 0 && t_len <= h_len && + !strncasecomp(Host + h_len - t_len, no_proxy, t_len))) { + FREE(host); + return YES; + } +#ifdef CJK_EX /* ASATAKU PROXY HACK */ + if ((!templ_port || templ_port == port) && + (t_len > 0 && t_len <= h_len && + isdigit(UCH(*no_proxy)) && + !StrNCmp(host, no_proxy, t_len))) { + FREE(host); + return YES; + } +#endif /* ASATAKU PROXY HACK */ + + if (*end) + no_proxy = (end + 1); + else + break; + } + + FREE(host); + return NO; +} + +/* Find physical name and access protocol get_physical() + * -------------------------------------- + * + * On entry, + * addr must point to the fully qualified hypertext reference. + * anchor a parent anchor with whose address is addr + * + * On exit, + * returns HT_NO_ACCESS Error has occurred. + * HT_OK Success + */ +static int get_physical(const char *addr, + HTParentAnchor *anchor) +{ + int result; + char *acc_method = NULL; /* Name of access method */ + char *physical = NULL; + char *Server_addr = NULL; + BOOL override_flag = NO; + + CTRACE((tfp, "get_physical %s\n", addr)); + + /* + * Make sure the using_proxy variable is FALSE. + */ + using_proxy = NO; + +#ifndef NO_RULES + if ((physical = HTTranslate(addr)) == 0) { + if (redirecting_url) { + return HT_REDIRECTING; + } + return HT_FORBIDDEN; + } + if (anchor->isISMAPScript == TRUE) { + StrAllocCat(physical, "?0,0"); + CTRACE((tfp, "HTAccess: Appending '?0,0' coordinate pair.\n")); + } + if (!StrNCmp(physical, "Proxied=", 8)) { + HTAnchor_setPhysical(anchor, physical + 8); + using_proxy = YES; + } else if (!StrNCmp(physical, "NoProxy=", 8)) { + HTAnchor_setPhysical(anchor, physical + 8); + override_flag = YES; + } else { + HTAnchor_setPhysical(anchor, physical); + } + FREE(physical); /* free our copy */ +#else + if (anchor->isISMAPScript == TRUE) { + StrAllocCopy(physical, addr); + StrAllocCat(physical, "?0,0"); + CTRACE((tfp, "HTAccess: Appending '?0,0' coordinate pair.\n")); + HTAnchor_setPhysical(anchor, physical); + FREE(physical); /* free our copy */ + } else { + HTAnchor_setPhysical(anchor, addr); + } +#endif /* NO_RULES */ + + acc_method = HTParse(HTAnchor_physical(anchor), STR_FILE_URL, PARSE_ACCESS); + + /* + * Check whether gateway access has been set up for this. + * + * This function can be replaced by the rule system above. + * + * If the rule system has already determined that we should use a proxy, or + * that we shouldn't, ignore proxy-related settings, don't use no_proxy + * either. + */ +#define USE_GATEWAYS +#ifdef USE_GATEWAYS + + if (!override_flag && !using_proxy) { /* else ignore no_proxy env var */ + char *host = NULL; + int port; + + if (!strcasecomp(acc_method, "news")) { + /* + * News is different, so we need to check the name of the server, + * as well as the default port for selective exclusions. + */ + if ((host = HTParse(addr, "", PARSE_HOST))) { + if (HTParsePort(host, &port) == NULL) { + StrAllocCopy(Server_addr, "news://"); + StrAllocCat(Server_addr, host); + StrAllocCat(Server_addr, ":119/"); + } + FREE(host); + } else if (LYGetEnv("NNTPSERVER") != NULL) { + StrAllocCopy(Server_addr, "news://"); + StrAllocCat(Server_addr, LYGetEnv("NNTPSERVER")); + StrAllocCat(Server_addr, ":119/"); + } + } else if (!strcasecomp(acc_method, "wais")) { + /* + * Wais also needs checking of the default port for selective + * exclusions. + */ + if ((host = HTParse(addr, "", PARSE_HOST))) { + if (!(HTParsePort(host, &port))) { + StrAllocCopy(Server_addr, "wais://"); + StrAllocCat(Server_addr, host); + StrAllocCat(Server_addr, ":210/"); + } + FREE(host); + } else + StrAllocCopy(Server_addr, addr); + } else { + StrAllocCopy(Server_addr, addr); + } + override_flag = override_proxy(Server_addr); + } + + if (!override_flag && !using_proxy) { + char *gateway_parameter = NULL, *gateway, *proxy; + + /* + * Search for gateways. + */ + HTSprintf0(&gateway_parameter, "WWW_%s_GATEWAY", acc_method); + gateway = LYGetEnv(gateway_parameter); /* coerce for decstation */ + + /* + * Search for proxy servers. + */ + if (!strcmp(acc_method, "file")) + /* + * If we got to here, a file URL is for ftp on a remote host. - FM + */ + strcpy(gateway_parameter, "ftp_proxy"); + else + sprintf(gateway_parameter, "%s_proxy", acc_method); + proxy = LYGetEnv(gateway_parameter); + FREE(gateway_parameter); + + if (gateway) + CTRACE((tfp, "Gateway found: %s\n", gateway)); + if (proxy) + CTRACE((tfp, "proxy server found: %s\n", proxy)); + + /* + * Proxy servers have precedence over gateway servers. + */ + if (proxy) { + char *gatewayed = NULL; + + StrAllocCopy(gatewayed, proxy); + if (!StrNCmp(gatewayed, "http", 4)) { + char *cp = strrchr(gatewayed, '/'); + + /* Append a slash to the proxy specification if it doesn't + * end in one but otherwise looks normal (starts with "http", + * has no '/' other than ones before the hostname). - kw */ + if (cp && (cp - gatewayed) <= 7) + LYAddHtmlSep(&gatewayed); + } + /* + * Ensure that the proxy server uses ftp for file URLs. - FM + */ + if (!StrNCmp(addr, "file", 4)) { + StrAllocCat(gatewayed, "ftp"); + StrAllocCat(gatewayed, (addr + 4)); + } else + StrAllocCat(gatewayed, addr); + using_proxy = YES; + if (anchor->isISMAPScript == TRUE) + StrAllocCat(gatewayed, "?0,0"); + HTAnchor_setPhysical(anchor, gatewayed); + FREE(gatewayed); + FREE(acc_method); + + acc_method = HTParse(HTAnchor_physical(anchor), + STR_HTTP_URL, PARSE_ACCESS); + + } else if (gateway) { + char *path = HTParse(addr, "", + PARSE_HOST + PARSE_PATH + PARSE_PUNCTUATION); + + /* Chop leading / off to make host into part of path */ + char *gatewayed = HTParse(path + 1, gateway, PARSE_ALL); + + FREE(path); + HTAnchor_setPhysical(anchor, gatewayed); + FREE(gatewayed); + FREE(acc_method); + + acc_method = HTParse(HTAnchor_physical(anchor), + STR_HTTP_URL, PARSE_ACCESS); + } + } + FREE(Server_addr); +#endif /* use gateways */ + + /* + * Search registered protocols to find suitable one. + */ + result = HT_NO_ACCESS; + { + int i, n; + +#ifndef NO_INIT + if (!protocols) + HTAccessInit(); +#endif + n = HTList_count(protocols); + for (i = 0; i < n; i++) { + HTProtocol *p = (HTProtocol *) HTList_objectAt(protocols, i); + + if (!strcmp(p->name, acc_method)) { + HTAnchor_setProtocol(anchor, p); + FREE(acc_method); + result = HT_OK; + break; + } + } + } + + FREE(acc_method); + return result; +} + +/* + * Temporarily set the int UCLYhndl_for_unspec and string UCLYhndl_for_unspec + * used for charset "assuming" to the values implied by a HTParentAnchor's + * UCStages, after saving the current values for later restoration. - kw @@@ + * These functions may not really belong here, but where else? I want the + * "pop" to occur as soon as possible after loading has finished. - kw @@@ + */ +void LYUCPushAssumed(HTParentAnchor *anchor) +{ + int anchor_LYhndl = -1; + LYUCcharset *anchor_UCI = NULL; + + if (anchor) { + anchor_LYhndl = HTAnchor_getUCLYhndl(anchor, UCT_STAGE_PARSER); + if (anchor_LYhndl >= 0) + anchor_UCI = HTAnchor_getUCInfoStage(anchor, + UCT_STAGE_PARSER); + if (anchor_UCI && anchor_UCI->MIMEname) { + pushed_assume_MIMEname = UCAssume_MIMEcharset; + UCAssume_MIMEcharset = NULL; + if (HTCJK == JAPANESE) + StrAllocCopy(UCAssume_MIMEcharset, pushed_assume_MIMEname); + else + StrAllocCopy(UCAssume_MIMEcharset, anchor_UCI->MIMEname); + pushed_assume_LYhndl = anchor_LYhndl; + /* some diagnostics */ + if (UCLYhndl_for_unspec != anchor_LYhndl) + CTRACE((tfp, + "LYUCPushAssumed: UCLYhndl_for_unspec changed %d -> %d\n", + UCLYhndl_for_unspec, + anchor_LYhndl)); + UCLYhndl_for_unspec = anchor_LYhndl; + return; + } + } + pushed_assume_LYhndl = -1; + FREE(pushed_assume_MIMEname); +} + +/* + * Restore the int UCLYhndl_for_unspec and string UCLYhndl_for_unspec used for + * charset "assuming" from the values saved by LYUCPushAssumed, if any. - kw + */ +int LYUCPopAssumed(void) +{ + if (pushed_assume_LYhndl >= 0) { + /* some diagnostics */ + if (UCLYhndl_for_unspec != pushed_assume_LYhndl) + CTRACE((tfp, + "LYUCPopAssumed: UCLYhndl_for_unspec changed %d -> %d\n", + UCLYhndl_for_unspec, + pushed_assume_LYhndl)); + UCLYhndl_for_unspec = pushed_assume_LYhndl; + pushed_assume_LYhndl = -1; + FREE(UCAssume_MIMEcharset); + UCAssume_MIMEcharset = pushed_assume_MIMEname; + pushed_assume_MIMEname = NULL; + return UCLYhndl_for_unspec; + } + return -1; +} + +/* Load a document HTLoad() + * --------------- + * + * This is an internal routine, which has an address AND a matching + * anchor. (The public routines are called with one OR the other.) + * + * On entry, + * addr must point to the fully qualified hypertext reference. + * anchor a parent anchor with whose address is addr + * + * On exit, + * returns <0 Error has occurred. + * HT_LOADED Success + * HT_NO_DATA Success, but no document loaded. + * (telnet session started etc) + */ +static int HTLoad(const char *addr, + HTParentAnchor *anchor, + HTFormat format_out, + HTStream *sink) +{ + HTProtocol *p; + int status = get_physical(addr, anchor); + + if (reloading) { + FREE(anchor->charset); + FREE(anchor->UCStages); + } + + if (status == HT_FORBIDDEN) { + /* prevent crash if telnet or similar was forbidden by rule. - kw */ + LYFixCursesOn("show alert:"); + status = HTLoadError(sink, 500, gettext("Access forbidden by rule")); + } else if (status == HT_REDIRECTING) { + ; /* fake redirection by rule, to redirecting_url */ + } else if (status >= 0) { + /* prevent crash if telnet or similar mapped or proxied by rule. - kw */ + LYFixCursesOnForAccess(addr, HTAnchor_physical(anchor)); + p = (HTProtocol *) HTAnchor_protocol(anchor); + anchor->parent->underway = TRUE; /* Hack to deal with caching */ + status = p->load(HTAnchor_physical(anchor), + anchor, format_out, sink); + anchor->parent->underway = FALSE; + LYUCPopAssumed(); + } + return status; +} + +/* Get a save stream for a document HTSaveStream() + * -------------------------------- + */ +HTStream *HTSaveStream(HTParentAnchor *anchor) +{ + HTProtocol *p = (HTProtocol *) HTAnchor_protocol(anchor); + + if (!p) + return NULL; + + return p->saveStream(anchor); +} + +int redirection_limit = 10; +int redirection_attempts = 0; /* counter in HTLoadDocument */ + +static BOOL too_many_redirections(void) +{ + if (redirection_attempts > redirection_limit) { + char *msg = NULL; + + HTSprintf0(&msg, TOO_MANY_REDIRECTIONS, redirection_limit); + free(msg); + redirection_attempts = 0; + return TRUE; + } + return FALSE; +} + +/* Load a document - with logging etc HTLoadDocument() + * ---------------------------------- + * + * - Checks or documents already loaded + * - Logs the access + * - Allows stdin filter option + * - Trace output and error messages + * + * On Entry, + * anchor is the node_anchor for the document + * full_address The address of the document to be accessed. + * filter if YES, treat stdin as HTML + * + * On Exit, + * returns YES Success in opening document + * NO Failure + */ +static BOOL HTLoadDocument(const char *full_address, /* may include #fragment */ + HTParentAnchor *anchor, + HTFormat format_out, + HTStream *sink) +{ + int status; + HText *text; + const char *address_to_load = full_address; + char *cp; + BOOL ForcingNoCache = LYforce_no_cache; + + CTRACE((tfp, "HTAccess: loading document %s\n", NonNull(address_to_load))); + if (isEmpty(address_to_load)) + return NO; + + /* + * Free use_this_url_instead and reset permanent_redirection if not done + * elsewhere. - FM + */ + FREE(use_this_url_instead); + permanent_redirection = FALSE; + + if (too_many_redirections()) { + return NO; + } + + /* + * If this is marked as an internal link but we don't have the document + * loaded any more, and we haven't explicitly flagged that we want to + * reload with LYforce_no_cache, then something has disappeared from the + * cache when we expected it to be still there. The user probably doesn't + * expect a new network access. So if we have POST data and safe is not + * set in the anchor, ask for confirmation, and fail if not granted. The + * exception are LYNXIMGMAP documents, for which we defer to LYLoadIMGmap + * for prompting if necessary. - kw + */ + text = (HText *) HTAnchor_document(anchor); + if (LYinternal_flag && !text && !LYforce_no_cache && + anchor->post_data && !anchor->safe && + !isLYNXIMGMAP(full_address) && + HTConfirm(gettext("Document with POST content not found in cache. Resubmit?")) + != TRUE) { + return NO; + } + + /* + * If we don't have POST content, check whether this is a previous + * redirecting URL, and keep re-checking until we get to the final + * destination or redirection limit. If we do have POST content, we didn't + * allow permanent redirection, and an interactive user will be deciding + * whether to keep redirecting. - FM + */ + if (!anchor->post_data) { + while ((cp = HTAnchor_physical(anchor)) != NULL && + !StrNCmp(cp, "Location=", 9)) { + DocAddress NewDoc; + + CTRACE((tfp, "HTAccess: '%s' is a redirection URL.\n", + anchor->address)); + CTRACE((tfp, "HTAccess: Redirecting to '%s'\n", cp + 9)); + + /* + * Don't exceed the redirection_attempts limit. - FM + */ + ++redirection_attempts; + if (too_many_redirections()) { + FREE(use_this_url_instead); + return NO; + } + + /* + * Set up the redirection. - FM + */ + StrAllocCopy(use_this_url_instead, cp + 9); + NewDoc.address = use_this_url_instead; + NewDoc.post_data = NULL; + NewDoc.post_content_type = NULL; + NewDoc.bookmark = anchor->bookmark; + NewDoc.isHEAD = anchor->isHEAD; + NewDoc.safe = anchor->safe; + anchor = HTAnchor_findAddress(&NewDoc); + } + } + /* + * If we had previous redirection, go back and check out that the URL under + * the current restrictions. - FM + */ + if (use_this_url_instead) { + FREE(redirecting_url); + return (NO); + } + + /* + * See if we can use an already loaded document. + */ + text = (HText *) HTAnchor_document(anchor); + if (text && !LYforce_no_cache) { + /* + * We have a cached rendition of the target document. Check if it's OK + * to re-use it. We consider it OK if: + * (1) the anchor does not have the no_cache element set, or + * (2) we've overridden it, e.g., because we are acting on a PREV_DOC + * command or a link in the History Page and it's not a reply from a + * POST with the LYresubmit_posts flag set, or + * (3) we are repositioning within the currently loaded document based + * on the target anchor's address (URL_Reference). + * + * If track_internal_links is false, HText_AreDifferent() is + * used to determine whether (3) applies. If the target address + * differs from that of the current document only by a fragment and the + * target address has an appended fragment, repositioning without + * reloading is always assumed. Note that HText_AreDifferent() + * currently always returns TRUE if the target has a LYNXIMGMAP URL, so + * that an internally generated pseudo-document will normally not be + * re-used unless condition (2) applies. (Condition (1) cannot apply + * since in LYMap.c, no_cache is always set in the anchor object). + * This doesn't guarantee that the resource from which the MAP element + * is taken will be read again (reloaded) when the list of links for a + * client-side image map is regenerated, when in some cases it should + * (e.g., user requested RELOAD, or HTTP response with no-cache header + * and we are not overriding). + * + * If track_internal_links is true, a target address that + * points to the same URL as the current document may still result in + * reloading, depending on whether the original URL-Reference was given + * as an internal link in the context of the previously loaded + * document. HText_AreDifferent() is not used here for testing whether + * we are just repositioning. For an internal link, the potential + * callers of this function from mainloop() down will either avoid + * making the call (and do the repositioning differently) or set + * LYinternal_flag (or LYoverride_no_cache). Note that (a) LYNXIMGMAP + * pseudo-documents and (b) The "List Page" document are treated + * logically as being part of the document on which they are based, for + * the purpose of whether to treat a link as internal, but the logic + * for this (by setting LYinternal_flag as necessary) is implemented + * elsewhere. There is a specific test for LYNXIMGMAP here so that the + * generated pseudo-document will not be re-used unless + * LYoverride_no_cache is set. The same caveat as above applies w.r.t. + * reloading of the underlying resource. + * + * We also should be checking other aspects of cache regulation (e.g., + * based on an If-Modified-Since check, etc.) but the code for doing + * those other things isn't available yet. + */ + if ((reloading != REAL_RELOAD) && + (LYoverride_no_cache || + ((!track_internal_links && + (!HText_hasNoCacheSet(text) || + !HText_AreDifferent(anchor, full_address))) || + (track_internal_links && + (((LYinternal_flag || !HText_hasNoCacheSet(text)) && + !isLYNXIMGMAP(full_address))))))) { + CTRACE((tfp, "HTAccess: Document already in memory.\n")); + HText_select(text); + +#ifdef DIRED_SUPPORT + if (HTAnchor_format(anchor) == WWW_DIRED) + lynx_edit_mode = TRUE; +#endif + redirection_attempts = 0; + return YES; + } else { + ForcingNoCache = YES; + BStrFree(anchor->post_data); + CTRACE((tfp, "HTAccess: Auto-reloading document.\n")); + } + } + + if (HText_HaveUserChangedForms(text)) { + /* + * Issue a warning. User forms content will be lost. + * Will not restore changed forms, currently. + */ + HTAlert(RELOADING_FORM); + } + + /* + * Get the document from the net. If we are auto-reloading, the mutable + * anchor elements from the previous rendition should be freed in + * conjunction with loading of the new rendition. - FM + */ + LYforce_no_cache = NO; /* reset after each time through */ + if (ForcingNoCache) { + FREE(anchor->title); /* ??? */ + } + status = HTLoad(address_to_load, anchor, format_out, sink); + CTRACE((tfp, "HTAccess: status=%d\n", status)); + + /* + * RECOVERY: if the loading failed, and we had a cached HText copy, and no + * new HText created - use a previous copy, issue a warning. + */ + if (text && status < 0 && (HText *) HTAnchor_document(anchor) == text) { + HTAlert(gettext("Loading failed, use a previous copy.")); + CTRACE((tfp, "HTAccess: Loading failed, use a previous copy.\n")); + HText_select(text); + +#ifdef DIRED_SUPPORT + if (HTAnchor_format(anchor) == WWW_DIRED) + lynx_edit_mode = TRUE; +#endif + redirection_attempts = 0; + return YES; + } + + /* + * Log the access if necessary. + */ + if (HTlogfile) { + time_t theTime; + + time(&theTime); + fprintf(HTlogfile, "%24.24s %s %s %s\n", + ctime(&theTime), + HTClientHost ? HTClientHost : "local", + status < 0 ? "FAIL" : "GET", + full_address); + fflush(HTlogfile); /* Actually update it on disk */ + CTRACE((tfp, "Log: %24.24s %s %s %s\n", + ctime(&theTime), + HTClientHost ? HTClientHost : "local", + status < 0 ? "FAIL" : "GET", + full_address)); + } + + /* + * Check out what we received from the net. + */ + if (status == HT_REDIRECTING) { + /* Exported from HTMIME.c, of all places. */ + /* NO!! - FM */ + /* + * Doing this via HTMIME.c meant that the redirection cover page was + * already loaded before we learned that we want a different URL. + * Also, changing anchor->address, as Lynx was doing, meant we could + * never again access its hash table entry, creating an insolvable + * memory leak. Instead, if we had a 301 status and set + * permanent_redirection, we'll load the new URL in anchor->physical, + * preceded by a token, which we can check to make replacements on + * subsequent access attempts. We'll check recursively, and retrieve + * the final URL if we had multiple redirections to it. If we just + * went to HTLoad now, as Lou originally had this, we couldn't do + * Lynx's security checks and alternate handling of some URL types. + * So, instead, we'll go all the way back to the top of getfile in + * LYGetFile.c when the status is HT_REDIRECTING. This may seem + * bizarre, but it works like a charm! - FM + * + * Actually, the location header for redirections is now again picked + * up in HTMIME.c. But that's an internal matter between HTTP.c and + * HTMIME.c, is still under control of HTLoadHTTP for http URLs, is + * done in a way that doesn't load the redirection response's body + * (except when wanted as an error fallback), and thus need not concern + * us here. - kw 1999-12-02 + */ + CTRACE((tfp, "HTAccess: '%s' is a redirection URL.\n", + address_to_load)); + CTRACE((tfp, "HTAccess: Redirecting to '%s'\n", + redirecting_url)); + /* + * Prevent circular references. + */ + if (strcmp(address_to_load, redirecting_url)) { /* if different */ + /* + * Load token and redirecting url into anchor->physical if we had + * 301 Permanent redirection. HTTP.c does not allow this if we + * have POST content. - FM + */ + if (permanent_redirection) { + StrAllocCopy(anchor->physical, "Location="); + StrAllocCat(anchor->physical, redirecting_url); + } + + /* + * Set up flags before return to getfile. - FM + */ + StrAllocCopy(use_this_url_instead, redirecting_url); + if (ForcingNoCache) + LYforce_no_cache = YES; + ++redirection_attempts; + FREE(redirecting_url); + permanent_redirection = FALSE; + return (NO); + } + ++redirection_attempts; + FREE(redirecting_url); + permanent_redirection = FALSE; + return (YES); + } + + /* + * We did not receive a redirecting URL. - FM + */ + redirection_attempts = 0; + FREE(redirecting_url); + permanent_redirection = FALSE; + + if (status == HT_LOADED) { + CTRACE((tfp, "HTAccess: `%s' has been accessed.\n", + full_address)); + return YES; + } + if (status == HT_PARTIAL_CONTENT) { + HTAlert(gettext("Loading incomplete.")); + CTRACE((tfp, "HTAccess: `%s' has been accessed, partial content.\n", + full_address)); + return YES; + } + + if (status == HT_NO_DATA) { + CTRACE((tfp, "HTAccess: `%s' has been accessed, No data left.\n", + full_address)); + return NO; + } + + if (status == HT_NOT_LOADED) { + CTRACE((tfp, "HTAccess: `%s' has been accessed, No data loaded.\n", + full_address)); + return NO; + } + + if (status == HT_INTERRUPTED) { + CTRACE((tfp, + "HTAccess: `%s' has been accessed, transfer interrupted.\n", + full_address)); + return NO; + } + + if (status > 0) { + /* + * If you get this, then please find which routine is returning a + * positive unrecognized error code! + */ + fprintf(stderr, + gettext("**** HTAccess: socket or file number returned by obsolete load routine!\n")); + fprintf(stderr, + gettext("**** HTAccess: Internal software error. Please mail lynx-dev@nongnu.org!\n")); + fprintf(stderr, gettext("**** HTAccess: Status returned was: %d\n"), status); + exit_immediately(EXIT_FAILURE); + } + + /* Failure in accessing a document */ + cp = NULL; + StrAllocCopy(cp, gettext("Can't Access")); + StrAllocCat(cp, " `"); + StrAllocCat(cp, full_address); + StrAllocCat(cp, "'"); + _HTProgress(cp); + FREE(cp); + + CTRACE((tfp, "HTAccess: Can't access `%s'\n", full_address)); + HTLoadError(sink, 500, gettext("Unable to access document.")); + return NO; +} /* HTLoadDocument */ + +/* Load a document from absolute name. HTLoadAbsolute() + * ----------------------------------- + * + * On Entry, + * addr The absolute address of the document to be accessed. + * filter if YES, treat document as HTML + * + * On Exit, + * returns YES Success in opening document + * NO Failure + */ +BOOL HTLoadAbsolute(const DocAddress *docaddr) +{ + BOOL result; + HTParentAnchor *anchor = HTAnchor_findAddress(docaddr); + + result = HTLoadDocument(docaddr->address, + anchor, + (HTOutputFormat ? HTOutputFormat : WWW_PRESENT), + HTOutputStream); + if (!result) { + HTAnchor_delete(anchor->parent); + } + return result; +} + +/* Load a document from relative name. HTLoadRelative() + * ----------------------------------- + * + * On Entry, + * relative_name The relative address of the document + * to be accessed. + * + * On Exit, + * returns YES Success in opening document + * NO Failure + */ +BOOL HTLoadRelative(const char *relative_name, + HTParentAnchor *here) +{ + DocAddress full_address; + BOOL result; + char *mycopy = NULL; + char *stripped = NULL; + + full_address.address = NULL; + full_address.post_data = NULL; + full_address.post_content_type = NULL; + full_address.bookmark = NULL; + full_address.isHEAD = FALSE; + full_address.safe = FALSE; + + StrAllocCopy(mycopy, relative_name); + + stripped = HTStrip(mycopy); + full_address.address = + HTParse(stripped, + here->address, + PARSE_ALL_WITHOUT_ANCHOR); + result = HTLoadAbsolute(&full_address); + /* + * If we got redirection, result will be NO, but use_this_url_instead will + * be set. The calling routine should check both and do whatever is + * appropriate. - FM + */ + FREE(full_address.address); + FREE(mycopy); /* Memory leak fixed 10/7/92 -- JFG */ + return result; +} + +/* Search. HTSearch() + * ------- + * + * Performs a keyword search on word given by the user. Adds the + * keyword to the end of the current address and attempts to open + * the new address. + * + * On Entry, + * *keywords space-separated keyword list or similar search list + * here is anchor search is to be done on. + */ +static char hex(int i) +{ + const char *hexchars = "0123456789ABCDEF"; + + return hexchars[i]; +} + +BOOL HTSearch(const char *keywords, + HTParentAnchor *here) +{ +#define acceptable \ +"1234567890abcdefghijlkmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-_" + + char *q, *u; + const char *p, *s, *e; /* Pointers into keywords */ + char *address = NULL; + BOOL result; + char *escaped = typecallocn(char, (strlen(keywords) * 3) + 1); + static const BOOL isAcceptable[96] = + /* *INDENT-OFF* */ + /* 0 1 2 3 4 5 6 7 8 9 A B C D E F */ + { 0,0,0,0,0,0,0,0,0,0,1,0,0,1,1,0, /* 2x !"#$%&'()*+,-./ */ + 1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0, /* 3x 0123456789:;<=>? */ + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 4x @ABCDEFGHIJKLMNO */ + 1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,1, /* 5X PQRSTUVWXYZ[\]^_ */ + 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 6x `abcdefghijklmno */ + 1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0 }; /* 7X pqrstuvwxyz{\}~ DEL */ + /* *INDENT-ON* */ + + if (escaped == NULL) + outofmem(__FILE__, "HTSearch"); + + if (here->isIndexAction == NULL) { + free(escaped); + return FALSE; + } + StrAllocCopy(address, here->isIndexAction); + + /* + * Convert spaces to + and hex escape unacceptable characters. + */ + for (s = keywords; *s && WHITE(*s); s++) /* Scan */ + ; /* Skip white space */ + for (e = s + strlen(s); e > s && WHITE(*(e - 1)); e--) /* Scan */ + ; /* Skip trailers */ + for (q = escaped, p = s; p < e; p++) { /* Scan stripped field */ + unsigned char c = UCH(TOASCII(*p)); + + if (WHITE(*p)) { + *q++ = '+'; + } else if (IS_CJK_TTY) { + *q++ = *p; + } else if (c >= 32 && c <= UCH(127) && isAcceptable[c - 32]) { + *q++ = *p; /* 930706 TBL for MVS bug */ + } else { + *q++ = '%'; + *q++ = hex((int) (c >> 4)); + *q++ = hex((int) (c & 15)); + } + } /* Loop over string */ + *q = '\0'; /* Terminate escaped string */ + u = StrChr(address, '?'); /* Find old search string */ + if (u != NULL) + *u = '\0'; /* Chop old search off */ + + StrAllocCat(address, "?"); + StrAllocCat(address, escaped); + FREE(escaped); + result = HTLoadRelative(address, here); + FREE(address); + + /* + * If we got redirection, result will be NO, but use_this_url_instead will + * be set. The calling routine should check both and do whatever is + * appropriate. Only an http server (not a gopher or wais server) could + * return redirection. Lynx will go all the way back to its mainloop() and + * subject a redirecting URL to all of its security and restrictions + * checks. - FM + */ + return result; +} + +/* Search Given Indexname. HTSearchAbsolute() + * ----------------------- + * + * Performs a keyword search on word given by the user. Adds the + * keyword to the end of the current address and attempts to open + * the new address. + * + * On Entry, + * *keywords space-separated keyword list or similar search list + * *indexname is name of object search is to be done on. + */ +BOOL HTSearchAbsolute(const char *keywords, + char *indexname) +{ + DocAddress abs_doc; + HTParentAnchor *anchor; + + abs_doc.address = indexname; + abs_doc.post_data = NULL; + abs_doc.post_content_type = NULL; + abs_doc.bookmark = NULL; + abs_doc.isHEAD = FALSE; + abs_doc.safe = FALSE; + + anchor = HTAnchor_findAddress(&abs_doc); + return HTSearch(keywords, anchor); +} diff --git a/WWW/Library/Implementation/HTAccess.h b/WWW/Library/Implementation/HTAccess.h new file mode 100644 index 0000000..5ee5ba9 --- /dev/null +++ b/WWW/Library/Implementation/HTAccess.h @@ -0,0 +1,236 @@ +/* + * $LynxId: HTAccess.h,v 1.22 2024/01/10 23:54:11 tom Exp $ + * HTAccess: Access manager for libwww + * ACCESS MANAGER + * + * This module keeps a list of valid protocol (naming scheme) specifiers with + * associated access code. It allows documents to be loaded given various + * combinations of parameters. New access protocols may be registered at any + * time. + * + * Part of the libwww library . + * + */ +#ifndef HTACCESS_H +#define HTACCESS_H + +/* Definition uses: +*/ +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + extern char *use_this_url_instead; + + extern int redirection_limit; + extern int redirection_attempts; + +/* Return codes from load routines: + * + * These codes may be returned by the protocol modules, + * and by the HTLoad routines. + * In general, positive codes are OK and negative ones are bad. + */ + +/* + +Default Addresses + + These control the home page selection. To mess with these for normal browses is asking + for user confusion. + + */ +#define LOGICAL_DEFAULT "WWW_HOME" /* Defined to be the home page */ + +#ifndef PERSONAL_DEFAULT +#define PERSONAL_DEFAULT "WWW/default.html" /* in home directory */ +#endif +#ifndef LOCAL_DEFAULT_FILE +#define LOCAL_DEFAULT_FILE "/usr/local/lib/WWW/default.html" +#endif +/* If one telnets to a www access point, + it will look in this file for home page */ +#ifndef REMOTE_POINTER +#define REMOTE_POINTER "/etc/www-remote.url" /* can't be file */ +#endif +/* and if that fails it will use this. */ +#ifndef REMOTE_ADDRESS +#define REMOTE_ADDRESS "http://www.w3.org/remote.html" /* can't be file */ +#endif + +/* If run from telnet daemon and no -l specified, use this file: +*/ +#ifndef DEFAULT_LOGFILE +#define DEFAULT_LOGFILE "/usr/adm/www-log/www-log" +#endif + +/* If the home page isn't found, use this file: +*/ +#ifndef LAST_RESORT +#define LAST_RESORT "http://www.w3.org/default.html" +#endif + +/* + +Flags which may be set to control this module + + */ +#ifdef NOT + extern int HTDiag; /* Flag: load source as plain text */ +#endif /* NOT */ + extern char *HTClientHost; /* Name or number of telnetting host */ + extern FILE *HTlogfile; /* File to output one-liners to */ + extern BOOL HTSecure; /* Disable security holes? */ + extern BOOL HTPermitRedir; /* Special flag for getfile() */ + extern HTStream *HTOutputStream; /* For non-interactive, set this */ + extern HTFormat HTOutputFormat; /* To convert on load, set this */ + +/* Check for proxy override. override_proxy() + * + * Check the no_proxy environment variable to get the list + * of hosts for which proxy server is not consulted. + * + * no_proxy is a comma- or space-separated list of machine + * or domain names, with optional :port part. If no :port + * part is present, it applies to all ports on that domain. + * + * Example: + * no_proxy="cern.ch,some.domain:8001" + * + * Use "*" to override all proxy service: + * no_proxy="*" + */ + extern BOOL override_proxy(const char *addr); + +/* + +Load a document from relative name + + ON ENTRY, + relative_name The relative address of the file to be accessed. + here The anchor of the object being searched + + ON EXIT, + returns YES Success in opening file + NO Failure + + */ + extern BOOL HTLoadRelative(const char *relative_name, + HTParentAnchor *here); + +/* + +Load a document from absolute name + + ON ENTRY, + addr The absolute address of the document to be accessed. + filter_it if YES, treat document as HTML + + ON EXIT, + returns YES Success in opening document + NO Failure + + */ + extern BOOL HTLoadAbsolute(const DocAddress *addr); + +/* + +Make a stream for Saving object back + + ON ENTRY, + anchor is valid anchor which has previously been loaded + + ON EXIT, + returns 0 if error else a stream to save the object to. + + */ + extern HTStream *HTSaveStream(HTParentAnchor *anchor); + +/* + +Search + + Performs a search on word given by the user. Adds the search words to the end of the + current address and attempts to open the new address. + + ON ENTRY, + *keywords space-separated keyword list or similar search list + here The anchor of the object being searched + + */ + extern BOOL HTSearch(const char *keywords, HTParentAnchor *here); + +/* + +Search Given Indexname + + Performs a keyword search on word given by the user. Adds the keyword to the end of + the current address and attempts to open the new address. + + ON ENTRY, + *keywords space-separated keyword list or similar search list + *indexname is name of object search is to be done on. + + */ + extern BOOL HTSearchAbsolute(const char *keywords, + char *indexname); + +/* + +Register an access method + + */ + + typedef struct _HTProtocol { + const char *name; + + int (*load) (const char *full_address, + HTParentAnchor *anchor, + HTFormat format_out, + HTStream *sink); + + HTStream *(*saveStream) (HTParentAnchor *anchor); + + } HTProtocol; + + extern BOOL HTRegisterProtocol(HTProtocol * protocol); + +/* + +Generate the anchor for the home page + + */ + +/* + + As it involves file access, this should only be done once when the program first runs. + This is a default algorithm -- browser don't HAVE to use this. + + */ + extern HTParentAnchor *HTHomeAnchor(void); + +/* + +Return Host Name + + */ + extern const char *HTHostName(void); + +/* + +For registering protocols supported by Lynx + +*/ + extern void LYRegisterLynxProtocols(void); + + extern void LYUCPushAssumed(HTParentAnchor *anchor); + extern int LYUCPopAssumed(void); + + extern BOOL using_proxy; /* Are we using an NNTP proxy? */ + +#ifdef __cplusplus +} +#endif +#endif /* HTACCESS_H */ diff --git a/WWW/Library/Implementation/HTAnchor.c b/WWW/Library/Implementation/HTAnchor.c new file mode 100644 index 0000000..b5ca251 --- /dev/null +++ b/WWW/Library/Implementation/HTAnchor.c @@ -0,0 +1,1460 @@ +/* + * $LynxId: HTAnchor.c,v 1.82 2020/01/21 21:58:52 tom Exp $ + * + * Hypertext "Anchor" Object HTAnchor.c + * ========================== + * + * An anchor represents a region of a hypertext document which is linked to + * another anchor in the same or a different document. + * + * History + * + * Nov 1990 Written in Objective-C for the NeXT browser (TBL) + * 24-Oct-1991 (JFG), written in C, browser-independent + * 21-Nov-1991 (JFG), first complete version + * + * (c) Copyright CERN 1991 - See Copyright.html + */ + +#define HASH_SIZE 997 /* Arbitrary prime. Memory/speed tradeoff */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define HASH_OF(h, v) ((HASH_TYPE)((h) * 3 + UCH(v)) % HASH_SIZE) + +static HASH_TYPE anchor_hash(const char *cp_address) +{ + HASH_TYPE hash; + const char *p; + + for (p = cp_address, hash = 0; *p; p++) + hash = HASH_OF(hash, *p); + + return (hash); +} + +typedef struct _HyperDoc Hyperdoc; + +#ifdef VMS +struct _HyperDoc { + int junk; /* VMS cannot handle pointers to undefined structs */ +}; +#endif /* VMS */ + +/* Table of lists of all parents */ +static HTList adult_table[HASH_SIZE] = +{ + {NULL, NULL}}; + +/* Creation Methods + * ================ + * + * Do not use "new" by itself outside this module. In order to enforce + * consistency, we insist that you furnish more information about the + * anchor you are creating : use newWithParent or newWithAddress. + */ +static HTParentAnchor0 *HTParentAnchor0_new(const char *address, + unsigned hash) +{ + HTParentAnchor0 *newAnchor = typecalloc(HTParentAnchor0); + + if (newAnchor == NULL) + outofmem(__FILE__, "HTParentAnchor0_new"); + + newAnchor->parent = newAnchor; /* self */ + StrAllocCopy(newAnchor->address, address); + newAnchor->adult_hash = (HASH_TYPE) hash; + + return (newAnchor); +} + +static HTParentAnchor *HTParentAnchor_new(HTParentAnchor0 *parent) +{ + HTParentAnchor *newAnchor = typecalloc(HTParentAnchor); + + if (newAnchor == NULL) + outofmem(__FILE__, "HTParentAnchor_new"); + + newAnchor->parent = parent; /* cross reference */ + parent->info = newAnchor; /* cross reference */ + newAnchor->address = parent->address; /* copy pointer */ + + newAnchor->isISMAPScript = FALSE; /* Lynx appends ?0,0 if TRUE. - FM */ + newAnchor->isHEAD = FALSE; /* HEAD request if TRUE. - FM */ + newAnchor->safe = FALSE; /* Safe. - FM */ + newAnchor->no_cache = FALSE; /* no-cache? - FM */ + newAnchor->inBASE = FALSE; /* duplicated from HTML.c/h */ + newAnchor->content_length = 0; /* Content-Length. - FM */ + return (newAnchor); +} + +static HTChildAnchor *HTChildAnchor_new(HTParentAnchor0 *parent) +{ + HTChildAnchor *p = typecalloc(HTChildAnchor); + + if (p == NULL) + outofmem(__FILE__, "HTChildAnchor_new"); + + p->parent = parent; /* parent reference */ + return p; +} + +static HTChildAnchor *HText_pool_ChildAnchor_new(HTParentAnchor *parent) +{ + HTChildAnchor *p = (HTChildAnchor *) HText_pool_calloc((HText *) (parent->document), + (unsigned) sizeof(HTChildAnchor)); + + if (p == NULL) + outofmem(__FILE__, "HText_pool_ChildAnchor_new"); + + p->parent = parent->parent; /* parent reference */ + return p; +} + +#ifdef CASE_INSENSITIVE_ANCHORS +/* Case insensitive string comparison */ +#define HT_EQUIV(a,b) (TOUPPER(a) == TOUPPER(b)) +#else +/* Case sensitive string comparison */ +#define HT_EQUIV(a,b) ((a) == (b)) +#endif + +/* Null-terminated string comparison + * --------------------------------- + * On entry, + * s Points to one string, null terminated + * t points to the other. + * On exit, + * returns YES if the strings are equivalent + * NO if they differ. + */ +static BOOL HTSEquivalent(const char *s, + const char *t) +{ + if (s && t) { /* Make sure they point to something */ + for (; *s && *t; s++, t++) { + if (!HT_EQUIV(*s, *t)) { + return (NO); + } + } + return (BOOL) (HT_EQUIV(*s, *t)); + } else { + return (BOOL) (s == t); /* Two NULLs are equivalent, aren't they ? */ + } +} + +/* Binary string comparison + * ------------------------ + * On entry, + * s Points to one bstring + * t points to the other. + * On exit, + * returns YES if the strings are equivalent + * NO if they differ. + */ +static BOOL HTBEquivalent(const bstring *s, + const bstring *t) +{ + if (s && t && BStrLen(s) == BStrLen(t)) { + int j; + int len = BStrLen(s); + + for (j = 0; j < len; ++j) { + if (!HT_EQUIV(BStrData(s)[j], BStrData(t)[j])) { + return (NO); + } + } + return (YES); + } else { + return (BOOL) (s == t); /* Two NULLs are equivalent, aren't they ? */ + } +} + +/* + * Three-way compare function + */ +static int compare_anchors(void *l, + void *r) +{ + const char *a = ((HTChildAnchor *) l)->tag; + const char *b = ((HTChildAnchor *) r)->tag; + + /* both tags are not NULL */ + +#ifdef CASE_INSENSITIVE_ANCHORS + return strcasecomp(a, b); /* Case insensitive */ +#else + return strcmp(a, b); /* Case sensitive - FM */ +#endif /* CASE_INSENSITIVE_ANCHORS */ +} + +/* Create new or find old sub-anchor + * --------------------------------- + * + * This one is for a named child. + * The parent anchor must already exist. + */ +static HTChildAnchor *HTAnchor_findNamedChild(HTParentAnchor0 *parent, + const char *tag) +{ + HTChildAnchor *child; + + if (parent && tag && *tag) { /* TBL */ + if (parent->children) { + /* + * Parent has children. Search them. + */ + HTChildAnchor sample; + + sample.tag = DeConst(tag); /* for compare_anchors() only */ + + child = (HTChildAnchor *) HTBTree_search(parent->children, &sample); + if (child != NULL) { + CTRACE((tfp, + "Child anchor %p of parent %p with name `%s' already exists.\n", + (void *) child, (void *) parent, tag)); + return (child); + } + } else { /* parent doesn't have any children yet : create family */ + parent->children = HTBTree_new(compare_anchors); + } + + child = HTChildAnchor_new(parent); + CTRACE((tfp, "HTAnchor: New Anchor %p named `%s' is child of %p\n", + (void *) child, + NonNull(tag), + (void *) child->parent)); + + StrAllocCopy(child->tag, tag); /* should be set before HTBTree_add */ + HTBTree_add(parent->children, child); + return (child); + + } else { + CTRACE((tfp, "HTAnchor_findNamedChild called with NULL parent.\n")); + return (NULL); + } + +} + +/* + * This one is for a new unnamed child being edited into an existing + * document. The parent anchor and the document must already exist. + * (Just add new unnamed child). + */ +static HTChildAnchor *HTAnchor_addChild(HTParentAnchor *parent) +{ + HTChildAnchor *child; + + if (!parent) { + CTRACE((tfp, "HTAnchor_addChild called with NULL parent.\n")); + return (NULL); + } + + child = HText_pool_ChildAnchor_new(parent); + CTRACE((tfp, "HTAnchor: New unnamed Anchor %p is child of %p\n", + (void *) child, + (void *) child->parent)); + + child->tag = 0; + HTList_linkObject(&parent->children_notag, child, &child->_add_children_notag); + + return (child); +} + +static HTParentAnchor0 *HTAnchor_findAddress_in_adult_table(const DocAddress *newdoc); + +static BOOL HTAnchor_link(HTChildAnchor *child, + HTAnchor * destination, + HTLinkType *type); + +/* Create or find a child anchor with a possible link + * -------------------------------------------------- + * + * Create new anchor with a given parent and possibly + * a name, and possibly a link to a _relatively_ named anchor. + * (Code originally in ParseHTML.h) + */ +HTChildAnchor *HTAnchor_findChildAndLink(HTParentAnchor *parent, /* May not be 0 */ + const char *tag, /* May be "" or 0 */ + const char *href, /* May be "" or 0 */ + HTLinkType *ltype) /* May be 0 */ +{ + HTChildAnchor *child; + + CTRACE((tfp, "Entered HTAnchor_findChildAndLink: tag=`%s',%s href=`%s'\n", + NonNull(tag), + (ltype == HTInternalLink) ? " (internal link)" : "", + NonNull(href))); + + if (parent == 0) { + child = 0; + } else { + if (non_empty(tag)) { + child = HTAnchor_findNamedChild(parent->parent, tag); + } else { + child = HTAnchor_addChild(parent); + } + + if (non_empty(href)) { + const char *fragment = NULL; + HTParentAnchor0 *dest; + + if (ltype == HTInternalLink && *href == '#') { + dest = parent->parent; + } else { + const char *relative_to = ((parent->inBASE && *href != '#') + ? parent->content_base + : parent->address); + DocAddress parsed_doc; + + parsed_doc.address = HTParse(href, relative_to, + PARSE_ALL_WITHOUT_ANCHOR); + + parsed_doc.post_data = NULL; + parsed_doc.post_content_type = NULL; + if (ltype && parent->post_data && ltype == HTInternalLink) { + /* for internal links, find a destination with the same + post data if the source of the link has post data. - kw + Example: LYNXIMGMAP: */ + parsed_doc.post_data = parent->post_data; + parsed_doc.post_content_type = parent->post_content_type; + } + parsed_doc.bookmark = NULL; + parsed_doc.isHEAD = FALSE; + parsed_doc.safe = FALSE; + + dest = HTAnchor_findAddress_in_adult_table(&parsed_doc); + FREE(parsed_doc.address); + } + + /* + * [from HTAnchor_findAddress()] + * If the address represents a sub-anchor, we load its parent (above), + * then we create a named child anchor within that parent. + */ + fragment = (*href == '#') ? href + 1 : HTParseAnchor(href); + + if (*fragment) + dest = (HTParentAnchor0 *) HTAnchor_findNamedChild(dest, fragment); + + if (tag && *tag) { + if (child->dest) { /* DUPLICATE_ANCHOR_NAME_WORKAROUND - kw */ + CTRACE((tfp, + "*** Duplicate ChildAnchor %p named `%s'", + (void *) child, tag)); + if ((HTAnchor *) dest != child->dest || ltype != child->type) { + CTRACE((tfp, + ", different dest %p or type, creating unnamed child\n", + (void *) child->dest)); + child = HTAnchor_addChild(parent); + } + } + } + HTAnchor_link(child, (HTAnchor *) dest, ltype); + } + } + return child; +} + +/* Create new or find old parent anchor + * ------------------------------------ + * + * Me one is for a reference which is found in a document, and might + * not be already loaded. + * Note: You are not guaranteed a new anchor -- you might get an old one, + * like with fonts. + */ +HTParentAnchor *HTAnchor_findAddress(const DocAddress *newdoc) +{ + /* Anchor tag specified ? */ + const char *tag = HTParseAnchor(newdoc->address); + + CTRACE((tfp, "Entered HTAnchor_findAddress\n")); + + /* + * If the address represents a sub-anchor, we load its parent, then we + * create a named child anchor within that parent. + */ + if (*tag) { + DocAddress parsed_doc; + HTParentAnchor0 *foundParent; + + parsed_doc.address = HTParse(newdoc->address, "", + PARSE_ALL_WITHOUT_ANCHOR); + parsed_doc.post_data = newdoc->post_data; + parsed_doc.post_content_type = newdoc->post_content_type; + parsed_doc.bookmark = newdoc->bookmark; + parsed_doc.isHEAD = newdoc->isHEAD; + parsed_doc.safe = newdoc->safe; + + foundParent = HTAnchor_findAddress_in_adult_table(&parsed_doc); + (void) HTAnchor_findNamedChild(foundParent, tag); + FREE(parsed_doc.address); + return HTAnchor_parent((HTAnchor *) foundParent); + } + return HTAnchor_parent((HTAnchor *) HTAnchor_findAddress_in_adult_table(newdoc)); +} + +/* The address has no anchor tag, for sure. + */ +static HTParentAnchor0 *HTAnchor_findAddress_in_adult_table(const DocAddress *newdoc) +{ + /* + * Check whether we have this node. + */ + HASH_TYPE hash; + HTList *adults; + HTList *grownups; + HTParentAnchor0 *foundAnchor; + BOOL need_extra_info = (BOOL) (newdoc->post_data || + newdoc->post_content_type || + newdoc->bookmark || + newdoc->isHEAD || + newdoc->safe); + + /* + * We need not free adult_table[] atexit - it should be perfectly empty + * after free'ing all HText's. (There is an error if it is not empty at + * exit). -LP + */ + + /* + * Select list from hash table, + */ + hash = anchor_hash(newdoc->address); + adults = &(adult_table[hash]); + + /* + * Search list for anchor. + */ + grownups = adults; + while (NULL != (foundAnchor = + (HTParentAnchor0 *) HTList_nextObject(grownups))) { + if (HTSEquivalent(foundAnchor->address, newdoc->address) && + + ((!foundAnchor->info && !need_extra_info) || + (foundAnchor->info && + HTBEquivalent(foundAnchor->info->post_data, newdoc->post_data) && + foundAnchor->info->isHEAD == newdoc->isHEAD))) { + CTRACE((tfp, "Anchor %p with address `%s' already exists.\n", + (void *) foundAnchor, newdoc->address)); + return foundAnchor; + } + } + + /* + * Node not found: create new anchor. + */ + foundAnchor = HTParentAnchor0_new(newdoc->address, hash); + CTRACE((tfp, "New anchor %p has hash %d and address `%s'\n", + (void *) foundAnchor, hash, newdoc->address)); + + if (need_extra_info) { + /* rare case, create a big structure */ + HTParentAnchor *p = HTParentAnchor_new(foundAnchor); + + if (newdoc->post_data) + BStrCopy(p->post_data, newdoc->post_data); + if (newdoc->post_content_type) + StrAllocCopy(p->post_content_type, + newdoc->post_content_type); + if (newdoc->bookmark) + StrAllocCopy(p->bookmark, newdoc->bookmark); + p->isHEAD = newdoc->isHEAD; + p->safe = newdoc->safe; + } + HTList_linkObject(adults, foundAnchor, &foundAnchor->_add_adult); + + return foundAnchor; +} + +/* Create new or find old named anchor - simple form + * ------------------------------------------------- + * + * Like HTAnchor_findAddress, but simpler to use for simple cases. + * No post data etc. can be supplied. - kw + */ +HTParentAnchor *HTAnchor_findSimpleAddress(const char *url) +{ + DocAddress urldoc; + + urldoc.address = DeConst(url); /* ignore warning, it IS treated like const - kw */ + urldoc.post_data = NULL; + urldoc.post_content_type = NULL; + urldoc.bookmark = NULL; + urldoc.isHEAD = FALSE; + urldoc.safe = FALSE; + return HTAnchor_findAddress(&urldoc); +} + +/* Link me Anchor to another given one + * ------------------------------------- + */ +static BOOL HTAnchor_link(HTChildAnchor *child, + HTAnchor * destination, + HTLinkType *type) +{ + if (!(child && destination)) + return (NO); /* Can't link to/from non-existing anchor */ + + CTRACE((tfp, "Linking child %p to anchor %p\n", (void *) child, (void *) destination)); + if (child->dest) { + CTRACE((tfp, "*** child anchor already has destination, exiting!\n")); + return (NO); + } + + child->dest = destination; + child->type = type; + + if (child->parent != destination->parent) + /* link only foreign children */ + HTList_linkObject(&destination->parent->sources, child, &child->_add_sources); + + return (YES); /* Success */ +} + +/* Delete an anchor and possibly related things (auto garbage collection) + * -------------------------------------------- + * + * The anchor is only deleted if the corresponding document is not loaded. + * All outgoing links from children are deleted, and children are + * removed from the sources lists of theirs targets. + * We also try to delete the targets whose documents are not loaded. + * If this anchor's sources list is empty, we delete it and its children. + */ + +/* + * Recursively try to delete destination anchor of this child. + * In any event, this will tell destination anchor that we + * no longer consider it a destination. + */ +static void deleteLinks(HTChildAnchor *me) +{ + /* + * Unregister me with our destination anchor's parent. + */ + if (me->dest) { + HTParentAnchor0 *parent = me->dest->parent; + + /* + * Start. Set the dest pointer to zero. + */ + me->dest = NULL; + + /* + * Remove me from the parent's sources so that the parent knows one + * less anchor is its dest. + */ + if ((me->parent != parent) && !HTList_isEmpty(&parent->sources)) { + /* + * Really should only need to deregister once. + */ + HTList_unlinkObject(&parent->sources, (void *) me); + } + + /* + * Recursive call. Test here to avoid calling overhead. Don't delete + * if document is loaded or being loaded. + */ + if ((me->parent != parent) && + parent != NULL && + !parent->underway && + (!parent->info || !parent->info->document)) { + HTAnchor_delete(parent); + } + + /* + * At this point, we haven't a destination. Set it to be so. Leave + * the HTAtom pointed to by type up to other code to handle (reusable, + * near static). + */ + me->type = NULL; + } +} + +static void HTParentAnchor_free(HTParentAnchor *me); + +BOOL HTAnchor_delete(HTParentAnchor0 *me) +{ + /* + * Memory leaks fixed. + * 05-27-94 Lynx 2-3-1 Garrett Arch Blythe + */ + HTBTElement *ele; + HTChildAnchor *child; + + /* + * Do nothing if nothing to do. + */ + if (!me) { + return (NO); + } + + /* + * Don't delete if document is loaded or being loaded. + */ + if (me->underway || (me->info && me->info->document)) { + return (NO); + } + + /* + * Mark ourselves busy, so that recursive calls of this function on this + * HTParentAnchor0 will not free it from under our feet. - kw + */ + me->underway = TRUE; + + { + /* + * Delete all outgoing links from named children. Do not delete named + * children itself (may have incoming links). + */ + if (me->children) { + ele = HTBTree_next(me->children, NULL); + while (ele != NULL) { + child = (HTChildAnchor *) HTBTree_object(ele); + if (child->dest) + deleteLinks(child); + ele = HTBTree_next(me->children, ele); + } + } + } + me->underway = FALSE; + + /* + * There are still incoming links to this one (we are the + * destination of another anchor). + */ + if (!HTList_isEmpty(&me->sources)) { + /* + * Can't delete parent, still have sources. + */ + return (NO); + } + + /* + * No more incoming and outgoing links : kill everything First, delete + * named children. + */ + if (me->children) { + ele = HTBTree_next(me->children, NULL); + while (ele != NULL) { + child = (HTChildAnchor *) HTBTree_object(ele); + FREE(child->tag); + FREE(child); + ele = HTBTree_next(me->children, ele); + } + HTBTree_free(me->children); + } + + /* + * Delete the ParentAnchor, if any. (Document was already deleted). + */ + if (me->info) { + HTParentAnchor_free(me->info); + FREE(me->info); + } + + /* + * Remove ourselves from the hash table's list. + */ + HTList_unlinkObject(&(adult_table[me->adult_hash]), (void *) me); + + /* + * Free the address. + */ + FREE(me->address); + + /* + * Finally, kill the parent anchor passed in. + */ + FREE(me); + + return (YES); +} + +/* + * Unnamed children (children_notag) have no sense without HText - delete them + * and their links if we are about to free HText. Document currently exists. + * Called within HText_free(). + */ +void HTAnchor_delete_links(HTParentAnchor *me) +{ + HTList *cur; + HTChildAnchor *child; + + /* + * Do nothing if nothing to do. + */ + if (!me || !me->document) { + return; + } + + /* + * Mark ourselves busy, so that recursive calls on this HTParentAnchor0 + * will not free it from under our feet. - kw + */ + me->parent->underway = TRUE; + + /* + * Delete all outgoing links from unnamed children. + */ + if (!HTList_isEmpty(&me->children_notag)) { + cur = &me->children_notag; + while ((child = + (HTChildAnchor *) HTList_unlinkLastObject(cur)) != 0) { + deleteLinks(child); + /* child allocated in HText pool, HText_free() will free it later */ + } + } + me->parent->underway = FALSE; +} + +static void HTParentAnchor_free(HTParentAnchor *me) +{ + /* + * Delete the methods list. + */ + if (me->methods) { + /* + * Leave what the methods point to up in memory for other code (near + * static stuff). + */ + HTList_delete(me->methods); + me->methods = NULL; + } + + /* + * Free up all allocated members. + */ + FREE(me->charset); + FREE(me->isIndexAction); + FREE(me->isIndexPrompt); + FREE(me->title); + FREE(me->physical); + BStrFree(me->post_data); + FREE(me->post_content_type); + FREE(me->bookmark); + FREE(me->owner); + FREE(me->RevTitle); + FREE(me->citehost); +#ifdef USE_SOURCE_CACHE + HTAnchor_clearSourceCache(me); +#endif + if (me->FileCache) { + FILE *fd; + + if ((fd = fopen(me->FileCache, "r")) != NULL) { + fclose(fd); + (void) remove(me->FileCache); + } + FREE(me->FileCache); + } + FREE(me->SugFname); + FREE(me->cache_control); + HTChunkClear(&(me->http_headers)); + FREE(me->content_type_params); + FREE(me->content_type); + FREE(me->content_language); + FREE(me->content_encoding); + FREE(me->content_base); + FREE(me->content_disposition); + FREE(me->content_location); + FREE(me->content_md5); + FREE(me->message_id); + FREE(me->subject); + FREE(me->date); + FREE(me->expires); + + FREE(me->last_modified); + FREE(me->ETag); + FREE(me->server); +#ifdef USE_COLOR_STYLE + FREE(me->style); +#endif + + /* + * Original code wanted a way to clean out the HTFormat if no longer needed + * (ref count?). I'll leave it alone since those HTAtom objects are a + * little harder to know where they are being referenced all at one time. + * (near static) + */ + + FREE(me->UCStages); + ImageMapList_free(me->imaps); +} + +#ifdef USE_SOURCE_CACHE +void HTAnchor_clearSourceCache(HTParentAnchor *me) +{ + /* + * Clean up the source cache, if any. + */ + if (me->source_cache_file) { + CTRACE((tfp, "SourceCache: Removing file %s\n", + me->source_cache_file)); + (void) LYRemoveTemp(me->source_cache_file); + FREE(me->source_cache_file); + } + if (me->source_cache_chunk) { + CTRACE((tfp, "SourceCache: Removing memory chunk %p\n", + (void *) me->source_cache_chunk)); + HTChunkFree(me->source_cache_chunk); + me->source_cache_chunk = NULL; + } +} +#endif /* USE_SOURCE_CACHE */ + +/* Data access functions + * --------------------- + */ +HTParentAnchor *HTAnchor_parent(HTAnchor * me) +{ + if (!me) + return NULL; + + if (me->parent->info) + return me->parent->info; + + /* else: create a new structure */ + return HTParentAnchor_new(me->parent); +} + +void HTAnchor_setDocument(HTParentAnchor *me, + HyperDoc *doc) +{ + if (me) + me->document = doc; +} + +HyperDoc *HTAnchor_document(HTParentAnchor *me) +{ + return (me ? me->document : NULL); +} + +char *HTAnchor_address(HTAnchor * me) +{ + char *addr = NULL; + + if (me) { + if (((HTParentAnchor0 *) me == me->parent) || + ((HTParentAnchor *) me == me->parent->info) || + !((HTChildAnchor *) me)->tag) { /* it's an adult or no tag */ + StrAllocCopy(addr, me->parent->address); + } else { /* it's a named child */ + HTSprintf0(&addr, "%s#%s", + me->parent->address, ((HTChildAnchor *) me)->tag); + } + } + return (addr); +} + +char *HTAnchor_short_address(HTAnchor * me) +{ + const char chop[] = "file://localhost/"; + char *addr = HTAnchor_address(me); + + if (!strncmp(addr, chop, sizeof(chop) - 1)) { + char *a = addr + 7; + char *b = addr + sizeof(chop) - 2; + + while ((*a++ = *b++) != '\0') { + ; + } + } + return addr; +} + +void HTAnchor_setFormat(HTParentAnchor *me, + HTFormat form) +{ + if (me) + me->format = form; +} + +HTFormat HTAnchor_format(HTParentAnchor *me) +{ + return (me ? me->format : NULL); +} + +void HTAnchor_setIndex(HTParentAnchor *me, + const char *address) +{ + if (me) { + me->isIndex = YES; + StrAllocCopy(me->isIndexAction, address); + } +} + +void HTAnchor_setPrompt(HTParentAnchor *me, + const char *prompt) +{ + if (me) { + StrAllocCopy(me->isIndexPrompt, prompt); + } +} + +BOOL HTAnchor_isIndex(HTParentAnchor *me) +{ + return (BOOL) (me + ? me->isIndex + : NO); +} + +/* Whether Anchor has been designated as an ISMAP link + * (normally by presence of an ISMAP attribute on A or IMG) - KW + */ +BOOL HTAnchor_isISMAPScript(HTAnchor * me) +{ + return (BOOL) ((me && me->parent->info) + ? me->parent->info->isISMAPScript + : NO); +} + +#if defined(USE_COLOR_STYLE) +/* Style handling. +*/ +const char *HTAnchor_style(HTParentAnchor *me) +{ + return (me ? me->style : NULL); +} + +void HTAnchor_setStyle(HTParentAnchor *me, + const char *style) +{ + if (me) { + StrAllocCopy(me->style, style); + } +} +#endif + +/* Title handling. +*/ +const char *HTAnchor_title(HTParentAnchor *me) +{ + return (me ? me->title : NULL); +} + +void HTAnchor_setTitle(HTParentAnchor *me, + const char *title) +{ + int i; + + if (me) { + if (title) { + StrAllocCopy(me->title, title); + for (i = 0; me->title[i]; i++) { + if (UCH(me->title[i]) == 1 || + UCH(me->title[i]) == 2) { + me->title[i] = ' '; + } + } + } else { + CTRACE((tfp, "HTAnchor_setTitle: New title is NULL! ")); + if (me->title) { + CTRACE((tfp, "Old title was \"%s\".\n", me->title)); + FREE(me->title); + } else { + CTRACE((tfp, "Old title was NULL.\n")); + } + } + } +} + +void HTAnchor_appendTitle(HTParentAnchor *me, + const char *title) +{ + int i; + + if (me) { + StrAllocCat(me->title, title); + for (i = 0; me->title[i]; i++) { + if (UCH(me->title[i]) == 1 || + UCH(me->title[i]) == 2) { + me->title[i] = ' '; + } + } + } +} + +/* Bookmark handling. +*/ +const char *HTAnchor_bookmark(HTParentAnchor *me) +{ + return (me ? me->bookmark : NULL); +} + +void HTAnchor_setBookmark(HTParentAnchor *me, + const char *bookmark) +{ + if (me) + StrAllocCopy(me->bookmark, bookmark); +} + +/* Owner handling. +*/ +const char *HTAnchor_owner(HTParentAnchor *me) +{ + return (me ? me->owner : NULL); +} + +void HTAnchor_setOwner(HTParentAnchor *me, + const char *owner) +{ + if (me) { + StrAllocCopy(me->owner, owner); + } +} + +/* TITLE handling in LINKs with REV="made" or REV="owner". - FM +*/ +const char *HTAnchor_RevTitle(HTParentAnchor *me) +{ + return (me ? me->RevTitle : NULL); +} + +void HTAnchor_setRevTitle(HTParentAnchor *me, + const char *title) +{ + int i; + + if (me) { + StrAllocCopy(me->RevTitle, title); + for (i = 0; me->RevTitle[i]; i++) { + if (UCH(me->RevTitle[i]) == 1 || + UCH(me->RevTitle[i]) == 2) { + me->RevTitle[i] = ' '; + } + } + } +} + +#ifndef DISABLE_BIBP +/* Citehost for bibp links from LINKs with REL="citehost". - RDC +*/ +const char *HTAnchor_citehost(HTParentAnchor *me) +{ + return (me ? me->citehost : NULL); +} + +void HTAnchor_setCitehost(HTParentAnchor *me, + const char *citehost) +{ + if (me) { + StrAllocCopy(me->citehost, citehost); + } +} +#endif /* !DISABLE_BIBP */ + +/* Suggested filename handling. - FM + * (will be loaded if we had a Content-Disposition + * header or META element with filename=name.suffix) + */ +const char *HTAnchor_SugFname(HTParentAnchor *me) +{ + return (me ? me->SugFname : NULL); +} + +/* HTTP Headers. +*/ +const char *HTAnchor_http_headers(HTParentAnchor *me) +{ + return (me ? me->http_headers.data : NULL); +} + +/* Content-Type handling (parameter list). +*/ +const char *HTAnchor_content_type_params(HTParentAnchor *me) +{ + return (me ? me->content_type_params : NULL); +} + +/* Content-Encoding handling. - FM + * (will be loaded if we had a Content-Encoding + * header.) + */ +const char *HTAnchor_content_encoding(HTParentAnchor *me) +{ + return (me ? me->content_encoding : NULL); +} + +/* Content-Type handling. - FM +*/ +const char *HTAnchor_content_type(HTParentAnchor *me) +{ + return (me ? me->content_type : NULL); +} + +/* Last-Modified header handling. - FM +*/ +const char *HTAnchor_last_modified(HTParentAnchor *me) +{ + return (me ? me->last_modified : NULL); +} + +/* Date header handling. - FM +*/ +const char *HTAnchor_date(HTParentAnchor *me) +{ + return (me ? me->date : NULL); +} + +/* Server header handling. - FM +*/ +const char *HTAnchor_server(HTParentAnchor *me) +{ + return (me ? me->server : NULL); +} + +/* Safe header handling. - FM +*/ +BOOL HTAnchor_safe(HTParentAnchor *me) +{ + return (BOOL) (me ? me->safe : FALSE); +} + +/* Content-Base header handling. - FM +*/ +const char *HTAnchor_content_base(HTParentAnchor *me) +{ + return (me ? me->content_base : NULL); +} + +/* Content-Location header handling. - FM +*/ +const char *HTAnchor_content_location(HTParentAnchor *me) +{ + return (me ? me->content_location : NULL); +} + +/* Message-ID, used for mail replies - kw +*/ +const char *HTAnchor_messageID(HTParentAnchor *me) +{ + return (me ? me->message_id : NULL); +} + +BOOL HTAnchor_setMessageID(HTParentAnchor *me, + const char *messageid) +{ + if (!(me && messageid && *messageid)) { + return FALSE; + } + StrAllocCopy(me->message_id, messageid); + return TRUE; +} + +/* Subject, used for mail replies - kw +*/ +const char *HTAnchor_subject(HTParentAnchor *me) +{ + return (me ? me->subject : NULL); +} + +BOOL HTAnchor_setSubject(HTParentAnchor *me, + const char *subject) +{ + if (!(me && subject && *subject)) { + return FALSE; + } + StrAllocCopy(me->subject, subject); + return TRUE; +} + +/* Manipulation of links + * --------------------- + */ +HTAnchor *HTAnchor_followLink(HTChildAnchor *me) +{ + return (me->dest); +} + +HTAnchor *HTAnchor_followTypedLink(HTChildAnchor *me, + HTLinkType *type) +{ + if (me->type == type) + return (me->dest); + return (NULL); /* No link of me type */ +} + +/* Methods List + * ------------ + */ +HTList *HTAnchor_methods(HTParentAnchor *me) +{ + if (!me->methods) { + me->methods = HTList_new(); + } + return (me->methods); +} + +/* Protocol + * -------- + */ +void *HTAnchor_protocol(HTParentAnchor *me) +{ + return (me->protocol); +} + +void HTAnchor_setProtocol(HTParentAnchor *me, + void *protocol) +{ + me->protocol = protocol; +} + +/* Physical Address + * ---------------- + */ +char *HTAnchor_physical(HTParentAnchor *me) +{ + return (me->physical); +} + +void HTAnchor_setPhysical(HTParentAnchor *me, + char *physical) +{ + if (me) { + StrAllocCopy(me->physical, physical); + } +} + +#ifdef DEBUG +static void show_stages(HTParentAnchor *me, const char *tag, int which_stage) +{ + int j; + + CTRACE((tfp, "Stages %s*%s", NonNull(me->charset), tag)); + for (j = 0; j < UCT_STAGEMAX; j++) { + CTRACE((tfp, " ")); + if (j == which_stage) + CTRACE((tfp, "(")); + CTRACE((tfp, "%d:%d:%s", + j, + me->UCStages->s[j].LYhndl, + NonNull(me->UCStages->s[j].C.MIMEname))); + if (j == which_stage) + CTRACE((tfp, ")")); + } + CTRACE((tfp, "\n")); +} +#else +#define show_stages(me,tag,which_stage) /* nothing */ +#endif + +/* + * We store charset info in the HTParentAnchor object, for several + * "stages". (See UCDefs.h) + * A stream method is supposed to know what stage in the model it is. + * + * General model MIME -> parser -> structured -> HText + * e.g., text/html + * from HTTP: HTMIME.c -> SGML.c -> HTML.c -> GridText.c + * text/plain + * from file: HTFile.c -> HTPlain.c -> GridText.c + * + * The lock/set_by is used to lock e.g. a charset set by an explicit + * HTTP MIME header against overriding by a HTML META tag - the MIME + * header has higher priority. Defaults (from -assume_.. options etc.) + * will not override charset explicitly given by server. + * + * Some advantages of keeping this in the HTAnchor: + * - Global variables are bad. + * - Can remember a charset given by META tag when toggling to SOURCE view. + * - Can remember a charset given by href in another doc. + * + * We don't modify the HTParentAnchor's charset element + * here, that one will only be set when explicitly given. + */ +LYUCcharset *HTAnchor_getUCInfoStage(HTParentAnchor *me, + int which_stage) +{ + LYUCcharset *result = NULL; + + if (me) { + if (!me->UCStages) { + int i; + int chndl = UCLYhndl_for_unspec; /* always >= 0 */ + UCAnchorInfo *stages = typecalloc(UCAnchorInfo); + + if (stages == NULL) + outofmem(__FILE__, "HTAnchor_getUCInfoStage"); + + for (i = 0; i < UCT_STAGEMAX; i++) { + stages->s[i].C.MIMEname = ""; + stages->s[i].LYhndl = -1; + } + if (me->charset) { + chndl = UCGetLYhndl_byMIME(me->charset); + if (chndl < 0) + chndl = UCLYhndl_for_unrec; + if (chndl < 0) + /* + * UCLYhndl_for_unrec not defined :-( + * fallback to UCLYhndl_for_unspec which always valid. + */ + chndl = UCLYhndl_for_unspec; /* always >= 0 */ + } + MemCpy(&stages->s[UCT_STAGE_MIME].C, &LYCharSet_UC[chndl], + sizeof(LYUCcharset)); + + stages->s[UCT_STAGE_MIME].lock = UCT_SETBY_DEFAULT; + stages->s[UCT_STAGE_MIME].LYhndl = chndl; + me->UCStages = stages; + } + result = (&me->UCStages->s[which_stage].C); + show_stages(me, "_getUCInfoStage", which_stage); + } + return (result); +} + +int HTAnchor_getUCLYhndl(HTParentAnchor *me, + int which_stage) +{ + if (me) { + if (!me->UCStages) { + /* + * This will allocate and initialize, if not yet done. + */ + (void) HTAnchor_getUCInfoStage(me, which_stage); + } + if (me->UCStages->s[which_stage].lock > UCT_SETBY_NONE) { + return (me->UCStages->s[which_stage].LYhndl); + } + } + return (-1); +} + +#ifdef CAN_SWITCH_DISPLAY_CHARSET +static void setup_switch_display_charset(HTParentAnchor *me, int h) +{ + if (!Switch_Display_Charset(h, SWITCH_DISPLAY_CHARSET_MAYBE)) + return; + HTAnchor_setUCInfoStage(me, current_char_set, + UCT_STAGE_HTEXT, UCT_SETBY_MIME); /* highest priority! */ + HTAnchor_setUCInfoStage(me, current_char_set, + UCT_STAGE_STRUCTURED, UCT_SETBY_MIME); /* highest priority! */ + CTRACE((tfp, + "changing UCInfoStage: HTEXT/STRUCTURED stages charset='%s'.\n", + LYCharSet_UC[current_char_set].MIMEname)); +} +#endif + +LYUCcharset *HTAnchor_setUCInfoStage(HTParentAnchor *me, + int LYhndl, + int which_stage, + int set_by) +{ + if (me) { + /* + * This will allocate and initialize, if not yet done. + */ + LYUCcharset *p = HTAnchor_getUCInfoStage(me, which_stage); + + /* + * Can we override? + */ + if (set_by >= me->UCStages->s[which_stage].lock) { +#ifdef CAN_SWITCH_DISPLAY_CHARSET + int ohandle = me->UCStages->s[which_stage].LYhndl; +#endif + me->UCStages->s[which_stage].lock = set_by; + me->UCStages->s[which_stage].LYhndl = LYhndl; + if (LYhndl >= 0) { + MemCpy(p, &LYCharSet_UC[LYhndl], sizeof(LYUCcharset)); + +#ifdef CAN_SWITCH_DISPLAY_CHARSET + /* Allow a switch to a more suitable display charset */ + if (LYhndl != ohandle && which_stage == UCT_STAGE_PARSER) + setup_switch_display_charset(me, LYhndl); +#endif + } else { + p->UChndl = -1; + } + show_stages(me, "_setUCInfoStage", which_stage); + return (p); + } + } + return (NULL); +} + +LYUCcharset *HTAnchor_resetUCInfoStage(HTParentAnchor *me, + int LYhndl, + int which_stage, + int set_by) +{ + LYUCcharset *result = NULL; + int ohandle; + + if (me && me->UCStages) { + me->UCStages->s[which_stage].lock = set_by; + ohandle = me->UCStages->s[which_stage].LYhndl; + me->UCStages->s[which_stage].LYhndl = LYhndl; +#ifdef CAN_SWITCH_DISPLAY_CHARSET + /* Allow a switch to a more suitable display charset */ + if (LYhndl >= 0 && LYhndl != ohandle + && which_stage == UCT_STAGE_PARSER) + setup_switch_display_charset(me, LYhndl); +#else + (void) ohandle; +#endif + show_stages(me, "_resetUCInfoStage", which_stage); + result = (&me->UCStages->s[which_stage].C); + } + return result; +} + +/* + * A set_by of (-1) means use the lock value from the from_stage. + */ +LYUCcharset *HTAnchor_copyUCInfoStage(HTParentAnchor *me, + int to_stage, + int from_stage, + int set_by) +{ + if (me) { + /* + * This will allocate and initialize, if not yet done. + */ + LYUCcharset *p_from = HTAnchor_getUCInfoStage(me, from_stage); + LYUCcharset *p_to = HTAnchor_getUCInfoStage(me, to_stage); + + /* + * Can we override? + */ + if (set_by == -1) + set_by = me->UCStages->s[from_stage].lock; + if (set_by == UCT_SETBY_NONE) + set_by = UCT_SETBY_DEFAULT; + if (set_by >= me->UCStages->s[to_stage].lock) { +#ifdef CAN_SWITCH_DISPLAY_CHARSET + int ohandle = me->UCStages->s[to_stage].LYhndl; +#endif + me->UCStages->s[to_stage].lock = set_by; + me->UCStages->s[to_stage].LYhndl = + me->UCStages->s[from_stage].LYhndl; +#ifdef CAN_SWITCH_DISPLAY_CHARSET + /* Allow a switch to a more suitable display charset */ + if (me->UCStages->s[to_stage].LYhndl >= 0 + && me->UCStages->s[to_stage].LYhndl != ohandle + && to_stage == UCT_STAGE_PARSER) + setup_switch_display_charset(me, + me->UCStages->s[to_stage].LYhndl); +#endif + if (p_to != p_from) + MemCpy(p_to, p_from, sizeof(LYUCcharset)); + + return (p_to); + } + } + return (NULL); +} diff --git a/WWW/Library/Implementation/HTAnchor.h b/WWW/Library/Implementation/HTAnchor.h new file mode 100644 index 0000000..3b1e6e6 --- /dev/null +++ b/WWW/Library/Implementation/HTAnchor.h @@ -0,0 +1,412 @@ +/* + * $LynxId: HTAnchor.h,v 1.40 2018/03/11 18:43:50 tom Exp $ + * + * Hypertext "Anchor" Object HTAnchor.h + * ========================== + * + * An anchor represents a region of a hypertext document which is linked + * to another anchor in the same or a different document. + */ + +#ifndef HTANCHOR_H +#define HTANCHOR_H + +/* Version 0 (TBL) written in Objective-C for the NeXT browser */ +/* Version 1 of 24-Oct-1991 (JFG), written in C, browser-independent */ + +#include +#include +#include +#include +#include + +typedef struct _HyperDoc HyperDoc; /* Ready for forward references */ +typedef struct _HTAnchor HTAnchor; +typedef struct _HTParentAnchor HTParentAnchor; +typedef struct _HTParentAnchor0 HTParentAnchor0; + +#include + +#ifdef __cplusplus +extern "C" { +#endif + struct _HTAnchor { + /* Generic anchor */ + HTParentAnchor0 *parent; /* Parent of this anchor (self for adults) */ + }; + +#define HASH_TYPE unsigned short + + struct _HTParentAnchor0 { /* One for adult_table, + * generally not used outside HTAnchor.c */ + /* Common part from the generic anchor structure */ + HTParentAnchor0 *parent; /* (self) */ + + /* ParentAnchor0-specific information */ + char *address; /* Absolute address of this node */ + HTParentAnchor *info; /* additional info, allocated on demand */ + + HTBTree *children; /* Subanchors , sorted by tag */ + HTList sources; /* List of anchors pointing to this, if any */ + + HTList _add_adult; /* - just a memory for list entry:) */ + HASH_TYPE adult_hash; /* adult list number */ + BOOL underway; /* Document about to be attached to it */ + }; + + /* + * Separated from the above to save memory: allocated on demand, + * it is nearly 1:1 to HText (well, sometimes without HText...), + * available for SGML, HTML, and HText stages. + * [being precise, we currently allocate it before HTLoadDocument(), + * in HTAnchor_findAddress() and HTAnchor_parent()]. + */ + struct _HTParentAnchor { + /* Common part from the generic anchor structure */ + HTParentAnchor0 *parent; /* Parent of this anchor */ + + /* ParentAnchor-specific information */ + HTList children_notag; /* Subanchors , tag is NULL */ + HyperDoc *document; /* The document within which this is an anchor */ + + char *address; /* parent->address, a pointer */ + bstring *post_data; /* Posting data */ + char *post_content_type; /* Type of post data */ + char *bookmark; /* Bookmark filename */ + HTFormat format; /* Pointer to node format descriptor */ + char *charset; /* Pointer to character set (kludge, for now */ + BOOL isIndex; /* Acceptance of a keyword search */ + char *isIndexAction; /* URL of isIndex server */ + char *isIndexPrompt; /* Prompt for isIndex query */ + char *title; /* Title of document */ + char *owner; /* Owner of document */ + char *RevTitle; /* TITLE in REV="made" or REV="owner" LINK */ + char *citehost; /* Citehost from REL="citehost" LINK */ +#ifdef USE_COLOR_STYLE + char *style; +#endif + + HTList *methods; /* Methods available as HTAtoms */ + void *protocol; /* Protocol object */ + char *physical; /* Physical address */ + BOOL isISMAPScript; /* Script for clickable image map */ + BOOL isHEAD; /* Document is headers from a HEAD request */ + BOOL safe; /* Safe */ +#ifdef USE_SOURCE_CACHE + char *source_cache_file; + HTChunk *source_cache_chunk; +#endif + char *FileCache; /* Path to a disk-cached copy (see src/HTFWriter.c) */ + char *SugFname; /* Suggested filename */ + char *cache_control; /* Cache-Control */ + BOOL no_cache; /* Cache-Control, Pragma or META "no-cache"? */ + BOOL inHEAD; /* HTMIMEConvert is decoding server-headers */ + BOOL inBASE; /* duplicated from HTStructured (HTML.c/h) */ + HTChunk http_headers; + BOOL no_content_encoding; /* server did not use C-T? */ + char *content_type_params; /* Content-Type (with parameters if any) */ + char *content_type; /* Content-Type */ + char *content_language; /* Content-Language */ + char *content_encoding; /* Compression algorithm */ + char *content_base; /* Content-Base */ + char *content_disposition; /* Content-Disposition */ + char *content_location; /* Content-Location */ + char *content_md5; /* Content-MD5 */ + char *message_id; /* Message-ID */ + char *subject; /* Subject */ + off_t header_length; /* length of headers */ + off_t content_length; /* Content-Length */ + off_t actual_length; /* actual length may differ */ + char *date; /* Date */ + char *expires; /* Expires */ + char *last_modified; /* Last-Modified */ + char *ETag; /* ETag (HTTP1.1 cache validator) */ + char *server; /* Server */ + UCAnchorInfo *UCStages; /* chartrans stages */ + HTList *imaps; /* client side image maps */ + }; + + typedef HTAtom HTLinkType; + + typedef struct { + /* Common part from the generic anchor structure */ + HTParentAnchor0 *parent; /* Parent of this anchor */ + + /* ChildAnchor-specific information */ + char *tag; /* #fragment, relative to the parent */ + + HTAnchor *dest; /* The anchor to which this leads */ + HTLinkType *type; /* Semantics of this link */ + + HTList _add_children_notag; /* - just a memory for list entry:) */ + HTList _add_sources; /* - just a memory for list entry:) */ + } HTChildAnchor; + + /* + * DocAddress structure is used for loading an absolute anchor with all + * needed information including posting data and post content type. + */ + typedef struct _DocAddress { + char *address; + bstring *post_data; + char *post_content_type; + char *bookmark; + BOOL isHEAD; + BOOL safe; + } DocAddress; + + /* "internal" means "within the same document, with certainty". */ + extern HTLinkType *HTInternalLink; + + /* Create or find a child anchor with a possible link + * -------------------------------------------------- + * + * Create new anchor with a given parent and possibly + * a name, and possibly a link to a _relatively_ named anchor. + * (Code originally in ParseHTML.h) + */ + extern HTChildAnchor *HTAnchor_findChildAndLink(HTParentAnchor *parent, /* May not be 0 */ + const char *tag, /* May be "" or 0 */ + const char *href, /* May be "" or 0 */ + HTLinkType *ltype); /* May be 0 */ + + /* Create new or find old parent anchor + * ------------------------------------ + * + * This one is for a reference which is found in a document, and might + * not be already loaded. + * Note: You are not guaranteed a new anchor -- you might get an old one, + * like with fonts. + */ + extern HTParentAnchor *HTAnchor_findAddress(const DocAddress *address); + + /* Create new or find old named anchor - simple form + * ------------------------------------------------- + * + * Like the previous one, but simpler to use for simple cases. + * No post data etc. can be supplied. - kw + */ + extern HTParentAnchor *HTAnchor_findSimpleAddress(const char *url); + + /* Delete an anchor and possibly related things (auto garbage collection) + * -------------------------------------------- + * + * The anchor is only deleted if the corresponding document is not loaded. + * All outgoing links from children are deleted, and children are + * removed from the sources lists of their targets. + * We also try to delete the targets whose documents are not loaded. + * If this anchor's sources list is empty, we delete it and its children. + */ + extern BOOL HTAnchor_delete(HTParentAnchor0 *me); + + /* + * Unnamed children (children_notag) have no sense without HText - + * delete them and their links if we are about to free HText. + * Document currently exists. Called within HText_free(). + */ + extern void HTAnchor_delete_links(HTParentAnchor *me); + +#ifdef USE_SOURCE_CACHE + extern void HTAnchor_clearSourceCache(HTParentAnchor *me); +#endif + + /* Data access functions + * --------------------- + */ + extern HTParentAnchor *HTAnchor_parent(HTAnchor * me); + + extern void HTAnchor_setDocument(HTParentAnchor *me, + HyperDoc *doc); + + extern HyperDoc *HTAnchor_document(HTParentAnchor *me); + + /* Returns the full URI of the anchor, child or parent + * as a malloc'd string to be freed by the caller. + */ + extern char *HTAnchor_address(HTAnchor * me); + + extern char *HTAnchor_short_address(HTAnchor * me); + + extern void HTAnchor_setFormat(HTParentAnchor *me, + HTFormat form); + + extern HTFormat HTAnchor_format(HTParentAnchor *me); + + extern void HTAnchor_setIndex(HTParentAnchor *me, + const char *address); + + extern void HTAnchor_setPrompt(HTParentAnchor *me, + const char *prompt); + + extern BOOL HTAnchor_isIndex(HTParentAnchor *me); + + extern BOOL HTAnchor_isISMAPScript(HTAnchor * me); + +#if defined(USE_COLOR_STYLE) + extern const char *HTAnchor_style(HTParentAnchor *me); + + extern void HTAnchor_setStyle(HTParentAnchor *me, + const char *style); +#endif + + /* Title handling. + */ + extern const char *HTAnchor_title(HTParentAnchor *me); + + extern void HTAnchor_setTitle(HTParentAnchor *me, + const char *title); + + extern void HTAnchor_appendTitle(HTParentAnchor *me, + const char *title); + + /* Bookmark handling. + */ + extern const char *HTAnchor_bookmark(HTParentAnchor *me); + + extern void HTAnchor_setBookmark(HTParentAnchor *me, + const char *bookmark); + + /* Owner handling. + */ + extern const char *HTAnchor_owner(HTParentAnchor *me); + + extern void HTAnchor_setOwner(HTParentAnchor *me, + const char *owner); + + /* TITLE handling in LINKs with REV="made" or REV="owner". - FM + */ + extern const char *HTAnchor_RevTitle(HTParentAnchor *me); + + extern void HTAnchor_setRevTitle(HTParentAnchor *me, + const char *title); + + /* Citehost for bibp links from LINKs with REL="citehost". - RDC + */ + extern const char *HTAnchor_citehost(HTParentAnchor *me); + + extern void HTAnchor_setCitehost(HTParentAnchor *me, + const char *citehost); + + /* Suggested filename handling. - FM + * (will be loaded if we had a Content-Disposition + * header or META element with filename=name.suffix) + */ + extern const char *HTAnchor_SugFname(HTParentAnchor *me); + + /* HTTP Headers. + */ + extern const char *HTAnchor_http_headers(HTParentAnchor *me); + + /* Content-Type handling (parameter list). + */ + extern const char *HTAnchor_content_type_params(HTParentAnchor *me); + + /* Content-Type handling. - FM + */ + extern const char *HTAnchor_content_type(HTParentAnchor *me); + + /* Content-Encoding handling. - FM + * (will be loaded if we had a Content-Encoding + * header.) + */ + extern const char *HTAnchor_content_encoding(HTParentAnchor *me); + + /* Last-Modified header handling. - FM + */ + extern const char *HTAnchor_last_modified(HTParentAnchor *me); + + /* Date header handling. - FM + */ + extern const char *HTAnchor_date(HTParentAnchor *me); + + /* Server header handling. - FM + */ + extern const char *HTAnchor_server(HTParentAnchor *me); + + /* Safe header handling. - FM + */ + extern BOOL HTAnchor_safe(HTParentAnchor *me); + + /* Content-Base header handling. - FM + */ + extern const char *HTAnchor_content_base(HTParentAnchor *me); + + /* Content-Location header handling. - FM + */ + extern const char *HTAnchor_content_location(HTParentAnchor *me); + + /* Message-ID, used for mail replies - kw + */ + extern const char *HTAnchor_messageID(HTParentAnchor *me); + + extern BOOL HTAnchor_setMessageID(HTParentAnchor *me, + const char *messageid); + + /* Subject, used for mail replies - kw + */ + extern const char *HTAnchor_subject(HTParentAnchor *me); + + extern BOOL HTAnchor_setSubject(HTParentAnchor *me, + const char *subject); + + /* Manipulation of links + * --------------------- + */ + extern HTAnchor *HTAnchor_followLink(HTChildAnchor *me); + + extern HTAnchor *HTAnchor_followTypedLink(HTChildAnchor *me, + HTLinkType *type); + + /* Read and write methods + * ---------------------- + */ + extern HTList *HTAnchor_methods(HTParentAnchor *me); + + /* Protocol + * -------- + */ + extern void *HTAnchor_protocol(HTParentAnchor *me); + + extern void HTAnchor_setProtocol(HTParentAnchor *me, + void *protocol); + + /* Physical address + * ---------------- + */ + extern char *HTAnchor_physical(HTParentAnchor *me); + + extern void HTAnchor_setPhysical(HTParentAnchor *me, + char *protocol); + + extern LYUCcharset *HTAnchor_getUCInfoStage(HTParentAnchor *me, + int which_stage); + + extern int HTAnchor_getUCLYhndl(HTParentAnchor *me, + int which_stage); + + extern LYUCcharset *HTAnchor_setUCInfoStage(HTParentAnchor *me, + int LYhndl, + int which_stage, + int set_by); + + extern LYUCcharset *HTAnchor_setUCInfoStage(HTParentAnchor *me, + int LYhndl, + int which_stage, + int set_by); + + extern LYUCcharset *HTAnchor_resetUCInfoStage(HTParentAnchor *me, + int LYhndl, + int which_stage, + int set_by); + + extern LYUCcharset *HTAnchor_copyUCInfoStage(HTParentAnchor *me, + int to_stage, + int from_stage, + int set_by); + + extern void ImageMapList_free(HTList *list); + +#ifdef __cplusplus +} +#endif +#endif /* HTANCHOR_H */ diff --git a/WWW/Library/Implementation/HTAssoc.c b/WWW/Library/Implementation/HTAssoc.c new file mode 100644 index 0000000..831b196 --- /dev/null +++ b/WWW/Library/Implementation/HTAssoc.c @@ -0,0 +1,82 @@ +/* + * $LynxId: HTAssoc.c,v 1.11 2016/11/24 15:29:50 tom Exp $ + * + * MODULE HTAssoc.c + * ASSOCIATION LIST FOR STORING NAME-VALUE PAIRS. + * NAMES NOT CASE SENSITIVE, AND ONLY COMMON LENGTH + * IS CHECKED (allows abbreviations; well, length is + * taken from lookup-up name, so if table contains + * a shorter abbrev it is not found). + * AUTHORS: + * AL Ari Luotonen luotonen@dxcern.cern.ch + * + * HISTORY: + * + * + * BUGS: + * + * + */ + +#include + +#include + +#include + +HTAssocList *HTAssocList_new(void) +{ + return HTList_new(); +} + +void HTAssocList_delete(HTAssocList *alist) +{ + if (alist) { + HTAssocList *cur = alist; + HTAssoc *assoc; + + while (NULL != (assoc = (HTAssoc *) HTList_nextObject(cur))) { + FREE(assoc->name); + FREE(assoc->value); + FREE(assoc); + } + HTList_delete(alist); + alist = NULL; + } +} + +void HTAssocList_add(HTAssocList *alist, + const char *name, + const char *value) +{ + HTAssoc *assoc; + + if (alist) { + if (!(assoc = (HTAssoc *) malloc(sizeof(HTAssoc)))) + outofmem(__FILE__, "HTAssoc_add"); + + assoc->name = NULL; + assoc->value = NULL; + + if (name) + StrAllocCopy(assoc->name, name); + if (value) + StrAllocCopy(assoc->value, value); + HTList_addObject(alist, (void *) assoc); + } else { + CTRACE((tfp, "HTAssoc_add: ERROR: assoc list NULL!!\n")); + } +} + +char *HTAssocList_lookup(HTAssocList *alist, + const char *name) +{ + HTAssocList *cur = alist; + HTAssoc *assoc; + + while (NULL != (assoc = (HTAssoc *) HTList_nextObject(cur))) { + if (!strncasecomp(assoc->name, name, (int) strlen(name))) + return assoc->value; + } + return NULL; +} diff --git a/WWW/Library/Implementation/HTAssoc.h b/WWW/Library/Implementation/HTAssoc.h new file mode 100644 index 0000000..327809c --- /dev/null +++ b/WWW/Library/Implementation/HTAssoc.h @@ -0,0 +1,35 @@ +/* ASSOCIATION LIST FOR STORING NAME-VALUE PAIRS + + Lookups from association list are not case-sensitive. + + */ + +#ifndef HTASSOC_H +#define HTASSOC_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + typedef HTList HTAssocList; + + typedef struct { + char *name; + char *value; + } HTAssoc; + + extern HTAssocList *HTAssocList_new(void); + extern void HTAssocList_delete(HTAssocList *alist); + + extern void HTAssocList_add(HTAssocList *alist, + const char *name, + const char *value); + + extern char *HTAssocList_lookup(HTAssocList *alist, + const char *name); + +#ifdef __cplusplus +} +#endif +#endif /* not HTASSOC_H */ diff --git a/WWW/Library/Implementation/HTAtom.c b/WWW/Library/Implementation/HTAtom.c new file mode 100644 index 0000000..07eca77 --- /dev/null +++ b/WWW/Library/Implementation/HTAtom.c @@ -0,0 +1,107 @@ +/* + * $LynxId: HTAtom.c,v 1.22 2018/03/06 09:46:58 tom Exp $ + * + * Atoms: Names to numbers HTAtom.c + * ======================= + * + * Atoms are names which are given representative pointer values + * so that they can be stored more efficiently, and comparisons + * for equality done more efficiently. + * + * Atoms are kept in a hash table consisting of an array of linked lists. + * + * Authors: + * TBL Tim Berners-Lee, WorldWideWeb project, CERN + * (c) Copyright CERN 1991 - See Copyright.html + * + */ + +#include +#include +#include +#include +#include + +#define HASH_SIZE 101 /* Arbitrary prime. Memory/speed tradeoff */ + +static HTAtom *hash_table[HASH_SIZE]; +static BOOL initialised = NO; + +/* + * To free off all atoms. + */ +#ifdef LY_FIND_LEAKS +static void free_atoms(void); +#endif + +#define HASH_FUNCTION(cp_hash) ((strlen(cp_hash) * UCH(*cp_hash)) % HASH_SIZE) + +HTAtom *HTAtom_for(const char *string) +{ + size_t hash; + HTAtom *a; + + if (!initialised) { + memset(hash_table, 0, sizeof(hash_table)); + initialised = YES; +#ifdef LY_FIND_LEAKS + atexit(free_atoms); +#endif + } + + hash = HASH_FUNCTION(string); + + for (a = hash_table[hash]; a; a = a->next) { + if (0 == strcasecomp(a->name, string)) { + return a; + } + } + + a = (HTAtom *) malloc(sizeof(*a)); + if (a == NULL) + outofmem(__FILE__, "HTAtom_for"); + + a->name = (char *) malloc(strlen(string) + 1); + if (a->name == NULL) + outofmem(__FILE__, "HTAtom_for"); + + strcpy(a->name, string); + a->next = hash_table[hash]; + hash_table[hash] = a; + return a; +} + +#ifdef LY_FIND_LEAKS +/* + * Purpose: Free off all atoms. + * Arguments: void + * Return Value: void + * Remarks/Portability/Dependencies/Restrictions: + * To be used at program exit. + * Revision History: + * 05-29-94 created Lynx 2-3-1 Garrett Arch Blythe + */ +static void free_atoms(void) +{ + auto int i_counter; + HTAtom *HTAp_freeme; + + /* + * Loop through all lists of atoms. + */ + for (i_counter = 0; i_counter < HASH_SIZE; i_counter++) { + /* + * Loop through the list. + */ + while (hash_table[i_counter] != NULL) { + /* + * Free off atoms and any members. + */ + HTAp_freeme = hash_table[i_counter]; + hash_table[i_counter] = HTAp_freeme->next; + FREE(HTAp_freeme->name); + FREE(HTAp_freeme); + } + } +} +#endif /* LY_FIND_LEAKS */ diff --git a/WWW/Library/Implementation/HTAtom.h b/WWW/Library/Implementation/HTAtom.h new file mode 100644 index 0000000..0d787bc --- /dev/null +++ b/WWW/Library/Implementation/HTAtom.h @@ -0,0 +1,53 @@ +/* */ + +/* Atoms: Names to numbers HTAtom.h + * ======================= + * + * Atoms are names which are given representative pointer values + * so that they can be stored more efficiently, and compaisons + * for equality done more efficiently. + * + * HTAtom_for(string) returns a representative value such that it + * will always (within one run of the program) return the same + * value for the same given string. + * + * Authors: + * TBL Tim Berners-Lee, WorldWideWeb project, CERN + * + * (c) Copyright CERN 1991 - See Copyright.html + * + */ + +#ifndef HTATOM_H +#define HTATOM_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + typedef struct _HTAtom HTAtom; + + struct _HTAtom { + HTAtom *next; + char *name; + }; /* struct _HTAtom */ + + extern HTAtom *HTAtom_for(const char *string); + +#define HTAtom_name(a) ((a)->name) + +/* + +The HTFormat type + + We use the HTAtom object for holding representations. This allows faster manipulation + (comparison and copying) that if we stayed with strings. + + */ + typedef HTAtom *HTFormat; + +#ifdef __cplusplus +} +#endif +#endif /* HTATOM_H */ diff --git a/WWW/Library/Implementation/HTBTree.c b/WWW/Library/Implementation/HTBTree.c new file mode 100644 index 0000000..f595bae --- /dev/null +++ b/WWW/Library/Implementation/HTBTree.c @@ -0,0 +1,680 @@ +/* Binary Tree for sorting things + * ============================== + * Author: Arthur Secret + * + * 4 March 94: Bug fixed in the balancing procedure + * + */ + +#include +#include + +#define MAXIMUM(a,b) ((a)>(b)?(a):(b)) + +#include + +/********************************************************* + * This function returns an HTBTree with memory allocated + * for it when given a mean to compare things + */ +HTBTree *HTBTree_new(HTComparer comp) +{ + HTBTree *tree = typeMalloc(HTBTree); + + if (tree == NULL) + outofmem(__FILE__, "HTBTree_new"); + + tree->compare = comp; + tree->top = NULL; + + return tree; +} + +/********************************************************* + * This void will free the memory allocated for one element + */ +static void HTBTElement_free(HTBTElement *element) +{ + if (element) { + if (element->left != NULL) + HTBTElement_free(element->left); + if (element->right != NULL) + HTBTElement_free(element->right); + FREE(element); + } +} + +/************************************************************* + * This void will free the memory allocated for the whole tree + */ +void HTBTree_free(HTBTree *tree) +{ + HTBTElement_free(tree->top); + FREE(tree); +} + +/********************************************************* + * This void will free the memory allocated for one element + */ +static void HTBTElementAndObject_free(HTBTElement *element) +{ + if (element) { /* Just in case nothing was in the tree anyway */ + if (element->left != NULL) + HTBTElementAndObject_free(element->left); + if (element->right != NULL) + HTBTElementAndObject_free(element->right); + FREE(element->object); + FREE(element); + } +} + +/************************************************************* + * This void will free the memory allocated for the whole tree + */ +void HTBTreeAndObject_free(HTBTree *tree) +{ + HTBTElementAndObject_free(tree->top); + FREE(tree); +} + +/********************************************************************* + * Returns a pointer to equivalent object in a tree or NULL if none. + */ +void *HTBTree_search(HTBTree *tree, + void *object) +{ + HTBTElement *cur = tree->top; + int res; + + while (cur != NULL) { + res = tree->compare(object, cur->object); + + if (res == 0) + return cur->object; + else if (res < 0) + cur = cur->left; + else if (res > 0) + cur = cur->right; + } + return NULL; +} + +/********************************************************************* + * This void is the core of HTBTree.c . It will + * 1/ add a new element to the tree at the right place + * so that the tree remains sorted + * 2/ balance the tree to be as fast as possible when reading it + */ +void HTBTree_add(HTBTree *tree, + void *object) +{ + HTBTElement *father_of_element; + HTBTElement *added_element; + HTBTElement *forefather_of_element; + HTBTElement *father_of_forefather; + BOOL father_found, top_found; + int depth, depth2, corrections; + + /* father_of_element is a pointer to the structure that is the father of + * the new object "object". added_element is a pointer to the structure + * that contains or will contain the new object "object". + * father_of_forefather and forefather_of_element are pointers that are + * used to modify the depths of upper elements, when needed. + * + * father_found indicates by a value NO when the future father of "object" + * is found. top_found indicates by a value NO when, in case of a + * difference of depths < 2, the top of the tree is encountered and forbids + * any further try to balance the tree. corrections is an integer used to + * avoid infinite loops in cases such as: + * + * 3 3 + * 4 4 + * 5 5 + * + * 3 is used here to show that it need not be the top of the tree. + */ + + /* + * 1/ Adding of the element to the binary tree + */ + + if (tree->top == NULL) { + tree->top = typeMalloc(HTBTElement); + + if (tree->top == NULL) + outofmem(__FILE__, "HTBTree_add"); + + tree->top->up = NULL; + tree->top->object = object; + tree->top->left = NULL; + tree->top->left_depth = 0; + tree->top->right = NULL; + tree->top->right_depth = 0; + } else { + father_found = YES; + father_of_element = tree->top; + added_element = NULL; + father_of_forefather = NULL; + forefather_of_element = NULL; + while (father_found) { + int res = tree->compare(object, father_of_element->object); + + if (res < 0) { + if (father_of_element->left != NULL) + father_of_element = father_of_element->left; + else { + father_found = NO; + father_of_element->left = typeMalloc(HTBTElement); + + if (father_of_element->left == NULL) + outofmem(__FILE__, "HTBTree_add"); + + added_element = father_of_element->left; + added_element->up = father_of_element; + added_element->object = object; + added_element->left = NULL; + added_element->left_depth = 0; + added_element->right = NULL; + added_element->right_depth = 0; + } + } else { /* res >= 0 */ + if (father_of_element->right != NULL) { + father_of_element = father_of_element->right; + } else { + father_found = NO; + father_of_element->right = typeMalloc(HTBTElement); + + if (father_of_element->right == NULL) + outofmem(__FILE__, "HTBTree_add"); + + added_element = father_of_element->right; + added_element->up = father_of_element; + added_element->object = object; + added_element->left = NULL; + added_element->left_depth = 0; + added_element->right = NULL; + added_element->right_depth = 0; + } + } + } + + /* + * Changing of all depths that need to be changed + */ + father_of_forefather = father_of_element; + forefather_of_element = added_element; + do { + if (father_of_forefather->left == forefather_of_element) { + depth = father_of_forefather->left_depth; + father_of_forefather->left_depth = 1 + + MAXIMUM(forefather_of_element->right_depth, + forefather_of_element->left_depth); + depth2 = father_of_forefather->left_depth; + } else { + depth = father_of_forefather->right_depth; + father_of_forefather->right_depth = 1 + + MAXIMUM(forefather_of_element->right_depth, + forefather_of_element->left_depth); + depth2 = father_of_forefather->right_depth; + } + forefather_of_element = father_of_forefather; + father_of_forefather = father_of_forefather->up; + } while ((depth != depth2) && (father_of_forefather != NULL)); + + /* + * 2/ Balancing the binary tree, if necessary + */ + top_found = YES; + corrections = 0; + while ((top_found) && (corrections < 7)) { + if ((abs(father_of_element->left_depth + - father_of_element->right_depth)) < 2) { + if (father_of_element->up != NULL) + father_of_element = father_of_element->up; + else + top_found = NO; + } else { /* We start the process of balancing */ + + corrections = corrections + 1; + /* + * corrections is an integer used to avoid infinite + * loops in cases such as: + * + * 3 3 + * 4 4 + * 5 5 + * + * 3 is used to show that it need not be the top of the tree + * But let's avoid these two exceptions anyhow + * with the two following conditions (4 March 94 - AS) + */ + + if (father_of_element->left == NULL) { + if ((father_of_element->right != NULL) + && (father_of_element->right->right == NULL) + && (father_of_element->right->left != NULL) + && (father_of_element->right->left->left == NULL) + && (father_of_element->right->left->right == NULL)) { + corrections = 7; + } + } else { + if ((father_of_element->right == NULL) + && (father_of_element->left->left == NULL) + && (father_of_element->left->right != NULL) + && (father_of_element->left->right->right == NULL) + && (father_of_element->left->right->left == NULL)) { + corrections = 7; + } + } + + if ((father_of_element->left != NULL) + && (father_of_element->left_depth > father_of_element->right_depth)) { + added_element = father_of_element->left; + father_of_element->left_depth = added_element->right_depth; + added_element->right_depth = 1 + + MAXIMUM(father_of_element->right_depth, + father_of_element->left_depth); + if (father_of_element->up != NULL) { + /* Bug fixed in March 94 - AS */ + BOOL first_time; + + father_of_forefather = father_of_element->up; + forefather_of_element = added_element; + first_time = YES; + do { + if (father_of_forefather->left + == forefather_of_element->up) { + depth = father_of_forefather->left_depth; + if (first_time) { + father_of_forefather->left_depth = 1 + + MAXIMUM(forefather_of_element->left_depth, + forefather_of_element->right_depth); + first_time = NO; + } else + father_of_forefather->left_depth = 1 + + MAXIMUM(forefather_of_element->up->left_depth, + forefather_of_element->up->right_depth); + + depth2 = father_of_forefather->left_depth; + } else { + depth = father_of_forefather->right_depth; + if (first_time) { + father_of_forefather->right_depth = 1 + + MAXIMUM(forefather_of_element->left_depth, + forefather_of_element->right_depth); + first_time = NO; + } else + father_of_forefather->right_depth = 1 + + MAXIMUM(forefather_of_element->up->left_depth, + forefather_of_element->up->right_depth); + depth2 = father_of_forefather->right_depth; + } + forefather_of_element = forefather_of_element->up; + father_of_forefather = father_of_forefather->up; + } while ((depth != depth2) && + (father_of_forefather != NULL)); + father_of_forefather = father_of_element->up; + if (father_of_forefather->left == father_of_element) { + /* + * 3 3 + * 4 5 + * When tree 5 6 becomes 7 4 + * 7 8 8 6 + * + * 3 is used to show that it may not be the top of the + * tree. + */ + father_of_forefather->left = added_element; + father_of_element->left = added_element->right; + added_element->right = father_of_element; + } + if (father_of_forefather->right == father_of_element) { + /* + * 3 3 + * 4 5 + * When tree 5 6 becomes 7 4 + * 7 8 8 6 + * + * 3 is used to show that it may not be the top of the + * tree + */ + father_of_forefather->right = added_element; + father_of_element->left = added_element->right; + added_element->right = father_of_element; + } + added_element->up = father_of_forefather; + } else { + /* + + * 1 2 + * When tree 2 3 becomes 4 1 + * 4 5 5 3 + * + * 1 is used to show that it is the top of the tree + */ + added_element->up = NULL; + father_of_element->left = added_element->right; + added_element->right = father_of_element; + } + father_of_element->up = added_element; + if (father_of_element->left != NULL) + father_of_element->left->up = father_of_element; + } else if (father_of_element->right != NULL) { + added_element = father_of_element->right; + father_of_element->right_depth = added_element->left_depth; + added_element->left_depth = 1 + + MAXIMUM(father_of_element->right_depth, + father_of_element->left_depth); + if (father_of_element->up != NULL) + /* Bug fixed in March 94 - AS */ + { + BOOL first_time; + + father_of_forefather = father_of_element->up; + forefather_of_element = added_element; + first_time = YES; + do { + if (father_of_forefather->left + == forefather_of_element->up) { + depth = father_of_forefather->left_depth; + if (first_time) { + father_of_forefather->left_depth = 1 + + MAXIMUM(forefather_of_element->left_depth, + forefather_of_element->right_depth); + first_time = NO; + } else + father_of_forefather->left_depth = 1 + + MAXIMUM(forefather_of_element->up->left_depth, + forefather_of_element->up->right_depth); + depth2 = father_of_forefather->left_depth; + } else { + depth = father_of_forefather->right_depth; + if (first_time) { + father_of_forefather->right_depth = 1 + + MAXIMUM(forefather_of_element->left_depth, + forefather_of_element->right_depth); + first_time = NO; + } else + father_of_forefather->right_depth = 1 + + MAXIMUM(forefather_of_element->up->left_depth, + forefather_of_element->up->right_depth); + depth2 = father_of_forefather->right_depth; + } + father_of_forefather = father_of_forefather->up; + forefather_of_element = forefather_of_element->up; + } while ((depth != depth2) && + (father_of_forefather != NULL)); + father_of_forefather = father_of_element->up; + if (father_of_forefather->left == father_of_element) { + /* + * 3 3 + * 4 6 + * When tree 5 6 becomes 4 8 + * 7 8 5 7 + * + * 3 is used to show that it may not be the top of the + * tree. + */ + father_of_forefather->left = added_element; + father_of_element->right = added_element->left; + added_element->left = father_of_element; + } + if (father_of_forefather->right == father_of_element) { + /* + * 3 3 + * 4 6 + * When tree 5 6 becomes 4 8 + * 7 8 5 7 + * + * 3 is used to show that it may not be the top of the + * tree + */ + father_of_forefather->right = added_element; + father_of_element->right = added_element->left; + added_element->left = father_of_element; + } + added_element->up = father_of_forefather; + } else { + /* + + * 1 3 + * When tree 2 3 becomes 1 5 + * 4 5 2 4 + * + * 1 is used to show that it is the top of the tree. + */ + added_element->up = NULL; + father_of_element->right = added_element->left; + added_element->left = father_of_element; + } + father_of_element->up = added_element; + if (father_of_element->right != NULL) + father_of_element->right->up = father_of_element; + } + } + } + while (father_of_element->up != NULL) { + father_of_element = father_of_element->up; + } + tree->top = father_of_element; + } +} + +/************************************************************************* + * this function returns a pointer to the leftmost element if ele is NULL, + * and to the next object to the right otherwise. + * If no elements left, returns a pointer to NULL. + */ +HTBTElement *HTBTree_next(HTBTree *tree, + HTBTElement *ele) +{ + HTBTElement *father_of_element; + HTBTElement *father_of_forefather; + + if (ele == NULL) { + father_of_element = tree->top; + if (father_of_element != NULL) + while (father_of_element->left != NULL) + father_of_element = father_of_element->left; + } else { + father_of_element = ele; + if (father_of_element->right != NULL) { + father_of_element = father_of_element->right; + while (father_of_element->left != NULL) + father_of_element = father_of_element->left; + } else { + father_of_forefather = father_of_element->up; + while (father_of_forefather && + (father_of_forefather->right == father_of_element)) { + father_of_element = father_of_forefather; + father_of_forefather = father_of_element->up; + } + father_of_element = father_of_forefather; + } + } +#ifdef BTREE_TRACE + /* The option -DBTREE_TRACE will give much more information + * about the way the process is running, for debugging matters + */ + if (father_of_element != NULL) { + printf("\nObject = %s\t", (char *) father_of_element->object); + if (father_of_element->up != NULL) + printf("Objet du pere = %s\n", + (char *) father_of_element->up->object); + else + printf("Pas de Pere\n"); + if (father_of_element->left != NULL) + printf("Objet du fils gauche = %s\t", + (char *) father_of_element->left->object); + else + printf("Pas de fils gauche\t"); + if (father_of_element->right != NULL) + printf("Objet du fils droit = %s\n", + (char *) father_of_element->right->object); + else + printf("Pas de fils droit\n"); + printf("Profondeur gauche = %d\t", father_of_element->left_depth); + printf("Profondeur droite = %d\n", father_of_element->right_depth); + printf(" **************\n"); + } +#endif + return father_of_element; +} + +#ifdef TEST +/***************************************************** + * This is just a test to show how to handle HTBTree.c + */ +main() +{ + HTBTree *tree; + HTBTElement *next_element; + + tree = HTBTree_new((HTComparer) strcasecomp); + HTBTree_add(tree, "hypertext"); + HTBTree_add(tree, "Addressing"); + HTBTree_add(tree, "X11"); + HTBTree_add(tree, "Tools"); + HTBTree_add(tree, "Proposal.wn"); + HTBTree_add(tree, "Protocols"); + HTBTree_add(tree, "NeXT"); + HTBTree_add(tree, "Daemon"); + HTBTree_add(tree, "Test"); + HTBTree_add(tree, "Administration"); + HTBTree_add(tree, "LineMode"); + HTBTree_add(tree, "DesignIssues"); + HTBTree_add(tree, "MarkUp"); + HTBTree_add(tree, "Macintosh"); + HTBTree_add(tree, "Proposal.rtf.wn"); + HTBTree_add(tree, "FIND"); + HTBTree_add(tree, "Paper"); + HTBTree_add(tree, "Tcl"); + HTBTree_add(tree, "Talks"); + HTBTree_add(tree, "Architecture"); + HTBTree_add(tree, "VMSHelp"); + HTBTree_add(tree, "Provider"); + HTBTree_add(tree, "Archive"); + HTBTree_add(tree, "SLAC"); + HTBTree_add(tree, "Project"); + HTBTree_add(tree, "News"); + HTBTree_add(tree, "Viola"); + HTBTree_add(tree, "Users"); + HTBTree_add(tree, "FAQ"); + HTBTree_add(tree, "WorkingNotes"); + HTBTree_add(tree, "Windows"); + HTBTree_add(tree, "FineWWW"); + HTBTree_add(tree, "Frame"); + HTBTree_add(tree, "XMosaic"); + HTBTree_add(tree, "People"); + HTBTree_add(tree, "All"); + HTBTree_add(tree, "Curses"); + HTBTree_add(tree, "Erwise"); + HTBTree_add(tree, "Carl"); + HTBTree_add(tree, "MidasWWW"); + HTBTree_add(tree, "XPM"); + HTBTree_add(tree, "MailRobot"); + HTBTree_add(tree, "Illustrations"); + HTBTree_add(tree, "VMClient"); + HTBTree_add(tree, "XPA"); + HTBTree_add(tree, "Clients.html"); + HTBTree_add(tree, "Library"); + HTBTree_add(tree, "CERNLIB_Distribution"); + HTBTree_add(tree, "libHTML"); + HTBTree_add(tree, "WindowsPC"); + HTBTree_add(tree, "tkWWW"); + HTBTree_add(tree, "tk2.3"); + HTBTree_add(tree, "CVS-RCS"); + HTBTree_add(tree, "DecnetSockets"); + HTBTree_add(tree, "SGMLStream"); + HTBTree_add(tree, "NextStep"); + HTBTree_add(tree, "CVSRepository_old"); + HTBTree_add(tree, "ArthurSecret"); + HTBTree_add(tree, "CVSROOT"); + HTBTree_add(tree, "HytelnetGate"); + HTBTree_add(tree, "cern.www.new.src"); + HTBTree_add(tree, "Conditions"); + HTBTree_add(tree, "HTMLGate"); + HTBTree_add(tree, "Makefile"); + HTBTree_add(tree, "Newsgroups.html"); + HTBTree_add(tree, "People.html"); + HTBTree_add(tree, "Bugs.html"); + HTBTree_add(tree, "Summary.html"); + HTBTree_add(tree, "zDesignIssues.wn"); + HTBTree_add(tree, "HT.draw"); + HTBTree_add(tree, "HTandCERN.wn"); + HTBTree_add(tree, "Ideas.wn"); + HTBTree_add(tree, "MarkUp.wn"); + HTBTree_add(tree, "Proposal.html"); + HTBTree_add(tree, "SearchPanel.draw"); + HTBTree_add(tree, "Comments.wn"); + HTBTree_add(tree, "Xanadu.html"); + HTBTree_add(tree, "Storinglinks.html"); + HTBTree_add(tree, "TheW3Book.html"); + HTBTree_add(tree, "Talk_Feb-91.html"); + HTBTree_add(tree, "JFosterEntry.txt"); + HTBTree_add(tree, "Summary.txt"); + HTBTree_add(tree, "Bibliography.html"); + HTBTree_add(tree, "HTandCern.txt"); + HTBTree_add(tree, "Talk.draw"); + HTBTree_add(tree, "zDesignNotes.html"); + HTBTree_add(tree, "Link.html"); + HTBTree_add(tree, "Status.html"); + HTBTree_add(tree, "http.txt"); + HTBTree_add(tree, "People.html~"); + HTBTree_add(tree, "TAGS"); + HTBTree_add(tree, "summary.txt"); + HTBTree_add(tree, "Technical.html"); + HTBTree_add(tree, "Terms.html"); + HTBTree_add(tree, "JANETAccess.html"); + HTBTree_add(tree, "People.txt"); + HTBTree_add(tree, "README.txt"); + HTBTree_add(tree, "CodingStandards.html"); + HTBTree_add(tree, "Copyright.txt"); + HTBTree_add(tree, "Status_old.html"); + HTBTree_add(tree, "patches~"); + HTBTree_add(tree, "RelatedProducts.html"); + HTBTree_add(tree, "Implementation"); + HTBTree_add(tree, "History.html"); + HTBTree_add(tree, "Makefile.bak"); + HTBTree_add(tree, "Makefile.old"); + HTBTree_add(tree, "Policy.html"); + HTBTree_add(tree, "WhatIs.html"); + HTBTree_add(tree, "TheProject.html"); + HTBTree_add(tree, "Notation.html"); + HTBTree_add(tree, "Helping.html"); + HTBTree_add(tree, "Cyber-WWW.sit.Hqx"); + HTBTree_add(tree, "Glossary.html"); + HTBTree_add(tree, "maketags.html"); + HTBTree_add(tree, "IntroCS.html"); + HTBTree_add(tree, "Contrib"); + HTBTree_add(tree, "Help.html"); + HTBTree_add(tree, "CodeManagExec"); + HTBTree_add(tree, "HT-0.1draz"); + HTBTree_add(tree, "Cello"); + HTBTree_add(tree, "TOPUB"); + HTBTree_add(tree, "BUILD"); + HTBTree_add(tree, "BUILDALL"); + HTBTree_add(tree, "Lynx"); + HTBTree_add(tree, "ArthurLibrary"); + HTBTree_add(tree, "RashtyClient"); + HTBTree_add(tree, "#History.html#"); + HTBTree_add(tree, "PerlServers"); + HTBTree_add(tree, "modules"); + HTBTree_add(tree, "NCSA_httpd"); + HTBTree_add(tree, "MAIL2HTML"); + HTBTree_add(tree, "core"); + HTBTree_add(tree, "EmacsWWW"); +#ifdef BTREE_TRACE + printf("\nTreeTopObject=%s\n\n", tree->top->object); +#endif + next_element = HTBTree_next(tree, NULL); + while (next_element != NULL) { +#ifndef BTREE_TRACE + printf("The next element is %s\n", next_element->object); +#endif + next_element = HTBTree_next(tree, next_element); + } + HTBTree_free(tree); +} + +#endif diff --git a/WWW/Library/Implementation/HTBTree.h b/WWW/Library/Implementation/HTBTree.h new file mode 100644 index 0000000..a4f78f9 --- /dev/null +++ b/WWW/Library/Implementation/HTBTree.h @@ -0,0 +1,104 @@ +/* /Net/dxcern/userd/timbl/hypertext/WWW/Library/Implementation/HTBTree.html + BALANCED BINARY TREE FOR SORTING THINGS + + Tree creation, traversal and freeing. User-supplied comparison routine. + + Author: Arthur Secret, CERN. Public domain. Please mail bugs and changes to + www-request@info.cern.ch + + part of libWWW + + */ +#ifndef HTBTREE_H +#define HTBTREE_H 1 + +#ifndef HTUTILS_H +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif +/* + +Data structures + + */ typedef struct _HTBTree_element { + void *object; /* User object */ + struct _HTBTree_element *up; + struct _HTBTree_element *left; + int left_depth; + struct _HTBTree_element *right; + int right_depth; + } HTBTElement; + + typedef int (*HTComparer) (void *a, void *b); + + typedef struct _HTBTree_top { + HTComparer compare; + struct _HTBTree_element *top; + } HTBTree; + +/* + +Create a binary tree given its discrimination routine + + */ + extern HTBTree *HTBTree_new(HTComparer comp); + +/* + +Free storage of the tree but not of the objects + + */ + extern void HTBTree_free(HTBTree *tree); + +/* + +Free storage of the tree and of the objects + + */ + extern void HTBTreeAndObject_free(HTBTree *tree); + +/* + +Add an object to a binary tree + + */ + + extern void HTBTree_add(HTBTree *tree, void *object); + +/* + +Search an object in a binary tree + + returns Pointer to equivalent object in a tree or NULL if none. + */ + + extern void *HTBTree_search(HTBTree *tree, void *object); + +/* + +Find user object for element + + */ +#define HTBTree_object(element) ((element)->object) + +/* + +Find next element in depth-first order + + ON ENTRY, + + ele if NULL, start with leftmost element. if != 0 give next object to + the right. + + returns Pointer to element or NULL if none left. + + */ + extern HTBTElement *HTBTree_next(HTBTree *tree, HTBTElement *ele); + +#ifdef __cplusplus +} +#endif +#endif /* HTBTREE_H */ diff --git a/WWW/Library/Implementation/HTCJK.h b/WWW/Library/Implementation/HTCJK.h new file mode 100644 index 0000000..7edf50b --- /dev/null +++ b/WWW/Library/Implementation/HTCJK.h @@ -0,0 +1,121 @@ +/* + * $LynxId: HTCJK.h,v 1.22 2021/07/01 23:51:38 tom Exp $ + * + * CJK character converter HTCJK.h + * ======================= + * + * Added 11-Jun-96 by FM, based on jiscode.h for + * Yutaka Sato's (ysato@etl.go.jp) SJIS.c, and + * Takuya ASADA's (asada@three-a.co.jp) CJK patches. + * (see SGML.c). + * + */ + +#ifndef HTCJK_H +#define HTCJK_H + +#ifndef HTUTILS_H +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif +/* + * STATUS CHANGE CODES + */ +#define TO_2BCODE '$' +#define TO_1BCODE '(' +#define TO_KANA '\016' +#define TO_KANAOUT '\017' +#define TO_KANJI "\033$B" +#define TO_HANJI "\033$A" +#define TO_HANGUL "\033$(C" +#define TO_ASCII "\033(B" + +#define IS_GBK_LO(lo) ((0xA1 <= (lo)) && ((lo) <= 0xFE)) +#define IS_GBK_HI(hi) ((0xA1 <= (hi)) && ((hi) <= 0xF7)) + +#define IS_SJIS_LO(lo) ((0x40 <= (lo)) && ((lo) != 0x7F) && ((lo) <= 0xFC)) +#define IS_SJIS_HI1(hi) ((0x81 <= (hi)) && ((hi) <= 0x9F)) /* 1st lev. */ +#define IS_SJIS_HI2(hi) ((0xE0 <= (hi)) && ((hi) <= 0xEF)) /* 2nd lev. */ +#define IS_SJIS(hi,lo,in_sjis) (!IS_SJIS_LO(lo) ? 0 : IS_SJIS_HI1(hi) ? (in_sjis=1) : in_sjis && IS_SJIS_HI2(hi)) +#define IS_SJIS_2BYTE(hi,lo) (IS_SJIS_LO(lo) && (IS_SJIS_HI1(hi) || IS_SJIS_HI2(hi))) +#define IS_SJIS_X0201KANA(lo) ((0xA1 <= (lo)) && ((lo) <= 0xDF)) + +#define IS_EUC_LOS(lo) ((0x21 <= (lo)) && ((lo) <= 0x7E)) /* standard */ +#define IS_EUC_LOX(lo) ((0xA1 <= (lo)) && ((lo) <= 0xFE)) /* extended */ +#define IS_EUC_HI(hi) ((0xA1 <= (hi)) && ((hi) <= 0xFE)) +#define IS_EUC_X0201KANA(hi,lo) (((hi) == 0x8E) && (0xA1 <= (lo)) && ((lo) <= 0xDF)) +#define IS_EUC(hi,lo) ((IS_EUC_HI(hi) && IS_EUC_LOX(lo)) || IS_EUC_X0201KANA(hi,lo)) + +#define IS_JAPANESE_2BYTE(hi,lo) (IS_SJIS_2BYTE(hi,lo) || IS_EUC(hi,lo)) + +#define IS_BIG5_LOS(lo) ((0x40 <= (lo)) && ((lo) <= 0x7E)) /* standard */ +#define IS_BIG5_LOX(lo) ((0xA1 <= (lo)) && ((lo) <= 0xFE)) /* extended */ +#define IS_BIG5_HI(hi) ((0xA1 <= (hi)) && ((hi) <= 0xFE)) +#define IS_BIG5(hi,lo) (IS_BIG5_HI(hi) && (IS_BIG5_LOS(lo) || IS_BIG5_LOX(lo))) + + typedef enum { + NOKANJI = 0, EUC, SJIS, JIS + } HTkcode; + typedef enum { + NOCJK = 0, JAPANESE, CHINESE, KOREAN, TAIPEI + } HTCJKlang; + + extern HTCJKlang HTCJK; + +/* + * Function prototypes. + */ + extern void JISx0201TO0208_EUC(unsigned IHI, + unsigned ILO, + unsigned char *OHI, + unsigned char *OLO); + + extern unsigned char *SJIS_TO_JIS1(unsigned HI, + unsigned LO, + unsigned char *JCODE); + + extern unsigned char *JIS_TO_SJIS1(unsigned HI, + unsigned LO, + unsigned char *SJCODE); + + extern unsigned char *EUC_TO_SJIS1(unsigned HI, + unsigned LO, + register unsigned char *SJCODE); + + extern void JISx0201TO0208_SJIS(unsigned I, + unsigned char *OHI, + unsigned char *OLO); + + extern unsigned char *SJIS_TO_EUC1(unsigned HI, + unsigned LO, + unsigned char *EUCp); + + extern unsigned char *SJIS_TO_EUC(unsigned char *src, + unsigned char *dst); + + extern unsigned char *EUC_TO_SJIS(unsigned char *src, + unsigned char *dst); + + extern unsigned char *EUC_TO_JIS(unsigned char *src, + unsigned char *dst, + const char *toK, + const char *toA); + + extern unsigned char *TO_EUC(const unsigned char *jis, + unsigned char *euc); + + extern void TO_SJIS(const unsigned char *any, + unsigned char *sjis); + + extern void TO_JIS(const unsigned char *any, + unsigned char *jis); + + extern char *str_kcode(HTkcode code); + +#ifdef __cplusplus +} +#endif +#endif /* HTCJK_H */ diff --git a/WWW/Library/Implementation/HTChunk.c b/WWW/Library/Implementation/HTChunk.c new file mode 100644 index 0000000..b9490fd --- /dev/null +++ b/WWW/Library/Implementation/HTChunk.c @@ -0,0 +1,335 @@ +/* + * $LynxId: HTChunk.c,v 1.29 2023/04/10 22:58:51 tom Exp $ + * + * Chunk handling: Flexible arrays + * =============================== + * + */ + +#include +#include + +#include + +/* + * Initialize a chunk with a certain allocation unit + */ +void HTChunkInit(HTChunk *ch, int grow) +{ + ch->data = 0; + ch->growby = grow; + ch->size = 0; + ch->allocated = 0; +} + +/* Create a chunk with a certain allocation unit + * -------------- + */ +HTChunk *HTChunkCreate(int grow) +{ + HTChunk *ch = typecalloc(HTChunk); + + if (ch == NULL) + outofmem(__FILE__, "creation of chunk"); + + HTChunkInit(ch, grow); + return ch; +} + +HTChunk *HTChunkCreateMayFail(int grow, int failok) +{ + HTChunk *ch = typecalloc(HTChunk); + + if (ch == NULL) { + if (!failok) { + outofmem(__FILE__, "creation of chunk"); + } else { + return ch; + } + } + + HTChunkInit(ch, grow); + ch->failok = failok; + return ch; +} + +/* Create a chunk with a certain allocation unit and ensured size + * -------------- + */ +HTChunk *HTChunkCreate2(int grow, size_t needed) +{ + HTChunk *ch = typecalloc(HTChunk); + + if (ch == NULL) + outofmem(__FILE__, "HTChunkCreate2"); + + HTChunkInit(ch, grow); + if (needed-- > 0) { + /* Round up */ + ch->allocated = (int) (needed - (needed % (size_t) ch->growby) + + (unsigned) ch->growby); + CTRACE((tfp, "HTChunkCreate2: requested %d, allocate %u\n", + (int) needed, (unsigned) ch->allocated)); + ch->data = typecallocn(char, (unsigned) ch->allocated); + + if (!ch->data) + outofmem(__FILE__, "HTChunkCreate2 data"); + } + return ch; +} + +/* Clear a chunk of all data + * -------------------------- + */ +void HTChunkClear(HTChunk *ch) +{ + FREE(ch->data); + ch->size = 0; + ch->allocated = 0; +} + +/* Free a chunk (and it's chain, if any) + * ------------------------------------- + */ +void HTChunkFree(HTChunk *ch) +{ + HTChunk *next; + + do { + next = ch->next; + FREE(ch->data); + FREE(ch); + ch = next; + } while (ch != NULL); +} + +/* Realloc the chunk + * ----------------- + */ +BOOL HTChunkRealloc(HTChunk *ch, int growby) +{ + char *data; + + ch->allocated = ch->allocated + growby; + + data = (ch->data + ? typeRealloc(char, ch->data, ch->allocated) + : typecallocn(char, ch->allocated)); + + if (data) { + ch->data = data; + } else if (ch->failok) { + HTChunkClear(ch); /* allocation failed, clear all data - kw */ + return FALSE; /* caller should check ch->allocated - kw */ + } else { + outofmem(__FILE__, "HTChunkRealloc"); + } + return TRUE; +} + +/* Append a character + * ------------------ + */ +void HTChunkPutc(HTChunk *ch, unsigned c) +{ + if (ch->size >= ch->allocated) { + if (!HTChunkRealloc(ch, ch->growby)) + return; + } + ch->data[ch->size++] = (char) c; +} + +/* like above but no realloc: extend to another chunk if necessary */ +HTChunk *HTChunkPutc2(HTChunk *ch, int c) +{ + if (ch->size >= ch->allocated) { + HTChunk *chunk = HTChunkCreateMayFail(ch->growby, ch->failok); + + ch->next = chunk; + ch = chunk; + HTChunkPutc(ch, UCH(c)); + } else { + ch->data[ch->size++] = (char) c; + } + return ch; +} + +/* Ensure a certain size + * --------------------- + */ +void HTChunkEnsure(HTChunk *ch, int needed) +{ + if (needed <= ch->allocated) + return; + ch->allocated = needed - 1 - ((needed - 1) % ch->growby) + + ch->growby; /* Round up */ + ch->data = (ch->data + ? typeRealloc(char, ch->data, ch->allocated) + : typecallocn(char, ch->allocated)); + + if (ch->data == NULL) + outofmem(__FILE__, "HTChunkEnsure"); +} + +/* + * Append a block of characters. + */ +void HTChunkPutb(HTChunk *ch, const char *b, int l) +{ + if (l <= 0) + return; + if (ch->size + l > ch->allocated) { + int growby = l - (l % ch->growby) + ch->growby; /* Round up */ + + if (!HTChunkRealloc(ch, growby)) + return; + } + MemCpy(ch->data + ch->size, b, l); + ch->size += l; +} + +/* like above but no realloc: extend to another chunk if necessary */ +HTChunk *HTChunkPutb2(HTChunk *ch, const char *b, int l) +{ + if (l <= 0) + return ch; + if (ch->size + l > ch->allocated) { + HTChunk *chunk; + int m = ch->allocated - ch->size; + + if (m != 0 && b != 0) { + MemCpy(ch->data + ch->size, b, (unsigned) m); + ch->size += m; + } + + chunk = HTChunkCreateMayFail(ch->growby, ch->failok); + ch->next = chunk; + ch = chunk; + if (b != 0) + HTChunkPutb(ch, b + m, l - m); + } else { + MemCpy(ch->data + ch->size, b, (unsigned) l); + ch->size += l; + } + return ch; +} + +#define PUTC(code) ch->data[ch->size++] = (char)(code) +#define PUTC2(code) ch->data[ch->size++] = (char)(0x80|(0x3f &(code))) + +/* + * Append a character encoded as UTF-8. + */ +void HTChunkPutUtf8Char(HTChunk *ch, UCode_t code) +{ + int utflen; + + if (TOASCII(code) < 128) + utflen = 1; + else if (code < 0x800L) { + utflen = 2; + } else if (code < 0x10000L) { + utflen = 3; + } else if (code < 0x200000L) { + utflen = 4; + } else if (code < 0x4000000L) { + utflen = 5; + } else if (code <= 0x7fffffffL) { + utflen = 6; + } else + utflen = 0; + + if (ch->size + utflen > ch->allocated) { + int growby = (ch->growby >= utflen) ? ch->growby : utflen; + + if (!HTChunkRealloc(ch, growby)) + return; + } + + switch (utflen) { + case 0: + return; + case 1: + ch->data[ch->size++] = (char) code; + return; + case 2: + PUTC(0xc0 | (code >> 6)); + break; + case 3: + PUTC(0xe0 | (code >> 12)); + break; + case 4: + PUTC(0xf0 | (code >> 18)); + break; + case 5: + PUTC(0xf8 | (code >> 24)); + break; + case 6: + PUTC(0xfc | (code >> 30)); + break; + } + switch (utflen) { + case 6: + PUTC2(code >> 24); + /* FALLTHRU */ + case 5: + PUTC2(code >> 18); + /* FALLTHRU */ + case 4: + PUTC2(code >> 12); + /* FALLTHRU */ + case 3: + PUTC2(code >> 6); + /* FALLTHRU */ + case 2: + PUTC2(code); + break; + } +} + +/* Terminate a chunk + * ----------------- + */ +void HTChunkTerminate(HTChunk *ch) +{ + HTChunkPutc(ch, (char) 0); +} + +/* Append a string + * --------------- + */ +void HTChunkPuts(HTChunk *ch, const char *s) +{ + const char *p; + + if (s != NULL) { + for (p = s; *p; p++) { + if (ch->size >= ch->allocated) { + if (!HTChunkRealloc(ch, ch->growby)) + break; + } + ch->data[ch->size++] = *p; + } + } +} + +/* like above but no realloc: extend to another chunk if necessary */ +HTChunk *HTChunkPuts2(HTChunk *ch, const char *s) +{ + const char *p; + + if (s != NULL) { + for (p = s; *p; p++) { + if (ch->size >= ch->allocated) { + HTChunk *chunk = HTChunkCreateMayFail(ch->growby, ch->failok); + + ch->next = chunk; + ch = chunk; + HTChunkPuts(ch, p); + break; + } + ch->data[ch->size++] = *p; + } + } + return ch; +} diff --git a/WWW/Library/Implementation/HTChunk.h b/WWW/Library/Implementation/HTChunk.h new file mode 100644 index 0000000..fa51c99 --- /dev/null +++ b/WWW/Library/Implementation/HTChunk.h @@ -0,0 +1,228 @@ +/* + * $LynxId: HTChunk.h,v 1.21 2020/01/21 22:02:43 tom Exp $ + * + * HTChunk: Flexible array handling for libwww + * CHUNK HANDLING: + * FLEXIBLE ARRAYS + * + * This module implements a flexible array. It is a general utility module. A + * chunk is a structure which may be extended. These routines create and + * append data to chunks, automatically reallocating them as necessary. + * + */ +#ifndef HTCHUNK_H +#define HTCHUNK_H 1 + +#ifndef HTUTILS_H +#include +#endif + +#include + +#ifdef __cplusplus +extern "C" { +#endif + typedef struct _HTChunk HTChunk; + + struct _HTChunk { + int size; /* In bytes */ + int growby; /* Allocation unit in bytes */ + int allocated; /* Current size of *data */ + char *data; /* Pointer to malloc'd area or 0 */ + int failok; /* allowed to fail without exiting program? */ + HTChunk *next; /* pointer to the next chunk */ + }; + +/* + * Initialize a chunk's allocation data and allocation-increment. + */ + extern void HTChunkInit(HTChunk *ch, int grow); + +/* + * + * Create new chunk + * + * ON ENTRY, + * + * growby The number of bytes to allocate at a time when the chunk + * is later extended. Arbitrary but normally a trade-off + * of time vs memory. + * + * ON EXIT, + * + * returns A chunk pointer to the new chunk, + * + */ + + extern HTChunk *HTChunkCreate(int growby); + +/* + * Create a chunk for which an allocation error is not a fatal application + * error if failok != 0, but merely resets the chunk. When using a chunk + * created this way, the caller should always check whether the contents + * are ok each time after data have been appended. + * The create call may also fail and will return NULL in that case. - kw + */ + extern HTChunk *HTChunkCreateMayFail(int growby, int failok); + +/* + * Like HTChunkCreate but with initial allocation - kw + * + */ + extern HTChunk *HTChunkCreate2(int growby, size_t needed); + +/* + * + * Free a chunk + * + * ON ENTRY, + * + * ch A valid chunk pointer made by HTChunkCreate() + * + * ON EXIT, + * + * ch is invalid and may not be used. + * + */ + + extern void HTChunkFree(HTChunk *ch); + +/* + * + * Clear a chunk + * + * ON ENTRY, + * + * ch A valid chunk pointer made by HTChunkCreate() + * + * ON EXIT, + * + * *ch The size of the chunk is zero. + * + */ + + extern void HTChunkClear(HTChunk *ch); + +/* + * + * Realloc a chunk + * + * ON ENTRY, + * + * ch A valid chunk pointer made by HTChunkCreate() + * + * growby growby + * + * ON EXIT, + * + * *ch Expanded by growby + * + */ + + extern BOOL HTChunkRealloc(HTChunk *ch, int growby); + +/* + * + * Ensure a chunk has a certain space in + * + * ON ENTRY, + * + * ch A valid chunk pointer made by HTChunkCreate() + * + * s The size required + * + * ON EXIT, + * + * *ch Has size at least s + * + */ + + extern void HTChunkEnsure(HTChunk *ch, int s); + +/* + * + * Append a character to a chunk + * + * ON ENTRY, + * + * ch A valid chunk pointer made by HTChunkCreate() + * + * c The character to be appended + * + * ON EXIT, + * + * *ch Is one character bigger + * + */ + extern void HTChunkPutc(HTChunk *ch, unsigned c); + + extern void HTChunkPutb(HTChunk *ch, const char *b, int l); + + extern void HTChunkPutUtf8Char(HTChunk *ch, UCode_t code); + +/* + * Append a string to a chunk + * + * ON ENTRY, + * + * ch A valid chunk pointer made by HTChunkCreate() + * + * str Points to a zero-terminated string to be appended + * + * ON EXIT, + * + * *ch Is bigger by strlen(str) + * + */ + + extern void HTChunkPuts(HTChunk *ch, const char *str); + +/* + * + * Append a zero character to a chunk + * + */ + +/* + * + * ON ENTRY, + * + * ch A valid chunk pointer made by HTChunkCreate() + * + * ON EXIT, + * + * *ch Is one character bigger + * + */ + + extern void HTChunkTerminate(HTChunk *ch); + +/* like the above but no realloc: extend to another chunk if necessary */ +/* + * + * Append a character (string, data) to a chunk + * + * ON ENTRY, + * + * ch A valid chunk pointer made by HTChunkCreate() + * + * c The character to be appended + * + * ON EXIT, + * + * returns original chunk or a pointer to the new chunk + * (original chunk is referenced to the new one + * by the field 'next') + * + */ + extern HTChunk *HTChunkPutc2(HTChunk *ch, int c); + extern HTChunk *HTChunkPuts2(HTChunk *ch, const char *str); + extern HTChunk *HTChunkPutb2(HTChunk *ch, const char *b, int l); + +/* New pool infrastructure: UNlike the above, store data using alignment */ + extern HTChunk *HTChunkPutb0(HTChunk *ch, const char *b, int l); + +#ifdef __cplusplus +} +#endif +#endif /* HTCHUNK_H */ diff --git a/WWW/Library/Implementation/HTDOS.c b/WWW/Library/Implementation/HTDOS.c new file mode 100644 index 0000000..84bff79 --- /dev/null +++ b/WWW/Library/Implementation/HTDOS.c @@ -0,0 +1,241 @@ +/* + * $LynxId: HTDOS.c,v 1.40 2013/11/28 11:11:05 tom Exp $ + * DOS specific routines + */ + +#include +#include +#include +#include + +#include + +#ifdef _WINDOWS +#include +#include +#endif + +/* + * Make a copy of the source argument in the result, allowing some extra + * space so we can append directly onto the result without reallocating. + */ +static char *copy_plus(char **result, const char *source) +{ + int length = (int) strlen(source); + int extra = 10; + int n; + + for (n = 0; n < length; ++n) { + if (source[n] == ' ') + ++extra; + } + + HTSprintf0(result, "%-*s", length + extra, source); + (*result)[length] = 0; + return (*result); +} + +/* PUBLIC HTDOS_wwwName() + * CONVERTS DOS Name into WWW Name + * ON ENTRY: + * dosname DOS file specification (NO NODE) + * + * ON EXIT: + * returns WWW file specification + * + */ +const char *HTDOS_wwwName(const char *dosname) +{ + static char *wwwname = NULL; + char *cp_url = copy_plus(&wwwname, dosname); + int wwwname_len; + char ch; + + while ((ch = *dosname) != '\0') { + switch (ch) { + case '\\': + /* convert dos backslash to unix-style */ + *cp_url++ = '/'; + break; + case ' ': + *cp_url++ = '%'; + *cp_url++ = '2'; + *cp_url++ = '0'; + break; + default: + *cp_url++ = ch; + break; + } + dosname++; + } + *cp_url = '\0'; + + wwwname_len = (int) strlen(wwwname); + if (wwwname_len > 1) + cp_url--; /* point last char */ + + if (wwwname_len > 3 && *cp_url == '/') { + cp_url++; + *cp_url = '\0'; + } + return (wwwname); +} + +/* + * Convert slashes from Unix to DOS + */ +char *HTDOS_slashes(char *path) +{ + char *s; + + for (s = path; *s != '\0'; ++s) { + if (*s == '/') { + *s = '\\'; + } + } + return path; +} + +/* PUBLIC HTDOS_name() + * CONVERTS WWW name into a DOS name + * ON ENTRY: + * wwwname WWW file name + * + * ON EXIT: + * returns DOS file specification + */ +char *HTDOS_name(const char *wwwname) +{ + static char *result = NULL; + int joe; + +#if defined(SH_EX) /* 2000/03/07 (Tue) 18:32:42 */ + if (unsafe_filename(wwwname)) { + HTUserMsg2("unsafe filename : %s", wwwname); + copy_plus(&result, "BAD_LOCAL_FILE_NAME"); + } else { + copy_plus(&result, wwwname); + } +#else + copy_plus(&result, wwwname); +#endif +#ifdef __DJGPP__ + if (result[0] == '/' + && result[1] == 'd' + && result[2] == 'e' + && result[3] == 'v' + && result[4] == '/' + && isalpha(result[5])) { + return (result); + } +#endif /* __DJGPP__ */ + + (void) HTDOS_slashes(result); + + /* pesky leading slash, rudiment from file://localhost/ */ + /* the rest of path may be with or without drive letter */ + if ((result[1] != '\\') && (result[0] == '\\')) { + for (joe = 0; (result[joe] = result[joe + 1]) != 0; joe++) ; + } + /* convert '|' after the drive letter to ':' */ + if (isalpha(UCH(result[0])) && result[1] == '|') { + result[1] = ':'; + } +#ifdef _WINDOWS /* 1998/04/02 (Thu) 08:59:48 */ + if (LYLastPathSep(result) != NULL + && !LYIsDosDrive(result)) { + char temp_buff[LY_MAXPATH]; + + sprintf(temp_buff, "%.3s\\%.*s", windows_drive, + (int) (sizeof(temp_buff) - 5), result); + StrAllocCopy(result, temp_buff); + } +#endif + /* + * If we have only a device, add a trailing slash. Otherwise it just + * refers to the current directory on the given device. + */ + if (LYLastPathSep(result) == NULL + && LYIsDosDrive(result)) + LYAddPathSep0(result); + + CTRACE((tfp, "HTDOS_name changed `%s' to `%s'\n", wwwname, result)); + return (result); +} + +#ifdef WIN_EX +char *HTDOS_short_name(const char *path) +{ + static char sbuf[LY_MAXPATH]; + char *ret; + DWORD r; + + if (StrChr(path, '/')) + path = HTDOS_name(path); + r = GetShortPathName(path, sbuf, sizeof sbuf); + if (r >= sizeof(sbuf) || r == 0) { + ret = LYStrNCpy(sbuf, path, sizeof(sbuf)); + } else { + ret = sbuf; + } + return ret; +} +#endif + +#if defined(DJGPP) +/* + * Poll tcp/ip lib and yield to DPMI-host while nothing in + * keyboard buffer (head = tail) (simpler than kbhit). + * This is required to be able to finish off dead sockets, + * answer pings etc. + */ +#include +#include +#include +#include + +void djgpp_idle_loop(void) +{ + while (_farpeekw(_dos_ds, 0x41a) == _farpeekw(_dos_ds, 0x41c)) { + tcp_tick(NULL); + __dpmi_yield(); +#if defined(USE_SLANG) + if (SLang_input_pending(1)) + break; +#endif + } +} + +/* PUBLIC getxkey() + * Replaces libc's getxkey() with polling of tcp/ip + * library (WatTcp or Watt-32). * + * ON EXIT: + * returns extended keypress. + */ + +/* Copyright (C) 1995 DJ Delorie, see COPYING.DJ for details */ + +int getxkey(void) +{ +#if defined(DJGPP_KEYHANDLER) + __dpmi_regs r; + + djgpp_idle_loop(); + + r.h.ah = 0x10; + __dpmi_int(0x16, &r); + + if (r.h.al == 0x00) + return 0x0100 | r.h.ah; + if (r.h.al == 0xe0) + return 0x0200 | r.h.ah; + return r.h.al; + +#elif defined(USE_SLANG) + djgpp_idle_loop(); + return SLkp_getkey(); +#else + /* PDcurses uses myGetChar() in LYString.c */ +#endif +} +#endif /* DJGPP */ diff --git a/WWW/Library/Implementation/HTDOS.h b/WWW/Library/Implementation/HTDOS.h new file mode 100644 index 0000000..e1613cb --- /dev/null +++ b/WWW/Library/Implementation/HTDOS.h @@ -0,0 +1,56 @@ +/* + * $LynxId: HTDOS.h,v 1.14 2009/09/09 00:16:06 tom Exp $ + * + * DOS specific routines + */ + +#ifndef HTDOS_H +#define HTDOS_H + +#ifndef HTUTILS_H +#include +#endif /* HTUTILS_H */ + +/* PUBLIC HTDOS_wwwName() + * CONVERTS DOS Name into WWW Name + * ON ENTRY: + * dosname DOS file specification (NO NODE) + * + * ON EXIT: + * returns WWW file specification + * + */ +const char *HTDOS_wwwName(const char *dosname); + +/* + * Converts Unix slashes to DOS + */ +char *HTDOS_slashes(char *path); + +/* PUBLIC HTDOS_name() + * CONVERTS WWW name into a DOS name + * ON ENTRY: + * wwwname WWW file name + * + * ON EXIT: + * returns DOS file specification + * + * Bug: Returns pointer to static -- non-reentrant + */ +char *HTDOS_name(const char *wwwname); + +#ifdef WIN_EX +char *HTDOS_short_name(const char *fn); + +#else +#define HTDOS_short_name(fn) fn +#endif + +#ifdef DJGPP +/* + * Poll tcp/ip lib and yield to DPMI-host while nothing in + * keyboard buffer (head = tail) (simpler than kbhit). + */ +void djgpp_idle_loop(void); +#endif +#endif /* HTDOS_H */ diff --git a/WWW/Library/Implementation/HTFTP.c b/WWW/Library/Implementation/HTFTP.c new file mode 100644 index 0000000..a08701c --- /dev/null +++ b/WWW/Library/Implementation/HTFTP.c @@ -0,0 +1,4180 @@ +/* + * $LynxId: HTFTP.c,v 1.149 2024/03/21 07:51:10 tom Exp $ + * + * File Transfer Protocol (FTP) Client + * for a WorldWideWeb browser + * =================================== + * + * A cache of control connections is kept. + * + * Note: Port allocation + * + * It is essential that the port is allocated by the system, rather + * than chosen in rotation by us (POLL_PORTS), or the following + * problem occurs. + * + * It seems that an attempt by the server to connect to a port which has + * been used recently by a listen on the same socket, or by another + * socket this or another process causes a hangup of (almost exactly) + * one minute. Therefore, we have to use a rotating port number. + * The problem remains that if the application is run twice in quick + * succession, it will hang for what remains of a minute. + * + * Authors + * TBL Tim Berners-lee + * DD Denis DeLaRoca 310 825-4580 + * LM Lou Montulli + * FM Foteos Macrides + * History: + * 2 May 91 Written TBL, as a part of the WorldWideWeb project. + * 15 Jan 92 Bug fix: close() was used for NETCLOSE for control soc + * 10 Feb 92 Retry if cached connection times out or breaks + * 8 Dec 92 Bug fix 921208 TBL after DD + * 17 Dec 92 Anon FTP password now just WWWuser@ suggested by DD + * fails on princeton.edu! + * 27 Dec 93 (FM) Fixed up so FTP now works with VMS hosts. Path + * must be Unix-style and cannot include the device + * or top directory. + * ?? ??? ?? (LM) Added code to prompt and send passwords for non + * anonymous FTP + * 25 Mar 94 (LM) Added code to recognize different ftp server types + * and code to parse dates and sizes on most hosts. + * 27 Mar 93 (FM) Added code for getting dates and sizes on VMS hosts. + * + * Notes: + * Portions Copyright 1994 Trustees of Dartmouth College + * Code for recognizing different FTP servers and + * parsing "ls -l" output taken from Macintosh Fetch + * program with permission from Jim Matthews, + * Dartmouth Software Development Team. + */ + +/* + * BUGS: @@@ Limit connection cache size! + * Error reporting to user. + * 400 & 500 errors are ack'ed by user with windows. + * Use configuration file for user names + * + * Note for portability this version does not use select() and + * so does not watch the control and data channels at the + * same time. + */ + +#include + +#include + +#include /* Implemented here */ +#include +#include +#include + +#define REPEAT_PORT /* Give the port number for each file */ +#define REPEAT_LISTEN /* Close each listen socket and open a new one */ + +/* define POLL_PORTS If allocation does not work, poll ourselves.*/ +#define LISTEN_BACKLOG 2 /* Number of pending connect requests (TCP) */ + +#define FIRST_TCP_PORT 1024 /* Region to try for a listening port */ +#define LAST_TCP_PORT 5999 + +#define LINE_LENGTH 256 + +#include +#include +#include /* For HTFileFormat() */ +#include +#include +#ifndef IPPORT_FTP +#define IPPORT_FTP 21 +#endif /* !IPORT_FTP */ + +#include +#include +#include +#include + +typedef struct _connection { + struct _connection *next; /* Link on list */ + int socket; /* Socket number for communication */ + BOOL is_binary; /* Binary mode? */ +} connection; + +/* Hypertext object building machinery +*/ +#include + +/* + * socklen_t is the standard, but there are many pre-standard variants. + * This ifdef works around a few of those cases. + * + * Information was obtained from header files on these platforms: + * AIX 4.3.2, 5.1 + * HPUX 10.20, 11.00, 11.11 + * IRIX64 6.5 + * Tru64 4.0G, 4.0D, 5.1 + */ +#if defined(SYS_IRIX64) + /* IRIX64 6.5 socket.h may use socklen_t if SGI_SOURCE is not defined */ +# if _NO_XOPEN4 && _NO_XOPEN5 +# define LY_SOCKLEN socklen_t +# elif _ABIAPI +# define LY_SOCKLEN int +# elif _XOPEN5 +# if (_MIPS_SIM != _ABIO32) +# define LY_SOCKLEN socklen_t +# else +# define LY_SOCKLEN int +# endif +# else +# define LY_SOCKLEN size_t +# endif +#elif defined(SYS_HPUX) +# if defined(_XOPEN_SOURCE_EXTENDED) && defined(SO_PROTOTYPE) +# define LY_SOCKLEN socklen_t +# else /* HPUX 10.20, etc. */ +# define LY_SOCKLEN int +# endif +#elif defined(SYS_TRU64) +# if defined(_POSIX_PII_SOCKET) +# define LY_SOCKLEN socklen_t +# elif defined(_XOPEN_SOURCE_EXTENDED) +# define LY_SOCKLEN size_t +# else +# define LY_SOCKLEN int +# endif +#else +# define LY_SOCKLEN socklen_t +#endif + +#define PUTC(c) (*target->isa->put_character) (target, c) +#define PUTS(s) (*target->isa->put_string) (target, s) +#define START(e) (*target->isa->start_element) (target, e, 0, 0, -1, 0) +#define END(e) (*target->isa->end_element) (target, e, 0) +#define FREE_TARGET (*target->isa->_free) (target) +#define ABORT_TARGET (*target->isa->_free) (target) + +#define TRACE_ENTRY(tag, entry_info) \ + CTRACE((tfp, "HTFTP: %s filename: %s date: %s size: %" PRI_off_t "\n", \ + tag, \ + entry_info->filename, \ + NonNull(entry_info->date), \ + CAST_off_t(entry_info->size))) + +struct _HTStructured { + const HTStructuredClass *isa; + /* ... */ +}; + +/* Global Variables + * --------------------- + */ +int HTfileSortMethod = FILE_BY_NAME; + +#ifndef DISABLE_FTP /*This disables everything to end-of-file */ +static char ThisYear[8]; +static char LastYear[8]; +static int TheDate; +static BOOLEAN HaveYears = FALSE; + +/* Module-Wide Variables + * --------------------- + */ +static connection *connections = NULL; /* Linked list of connections */ +static char response_text[LINE_LENGTH + 1]; /* Last response from ftp host */ +static connection *control = NULL; /* Current connection */ +static int data_soc = -1; /* Socket for data transfer =invalid */ +static char *user_entered_password = NULL; +static char *last_username_and_host = NULL; + +/* + * Some ftp servers are known to have a broken implementation of RETR. If + * asked to retrieve a directory, they get confused and fail subsequent + * commands such as CWD and LIST. + */ +static int Broken_RETR = FALSE; + +/* + * Some ftp servers are known to have a broken implementation of EPSV. The + * server will hang for a long time when we attempt to connect after issuing + * this command. + */ +#ifdef INET6 +static int Broken_EPSV = FALSE; +#endif + +typedef enum { + GENERIC_SERVER + ,MACHTEN_SERVER + ,UNIX_SERVER + ,VMS_SERVER + ,CMS_SERVER + ,DCTS_SERVER + ,TCPC_SERVER + ,PETER_LEWIS_SERVER + ,NCSA_SERVER + ,WINDOWS_NT_SERVER + ,WINDOWS_2K_SERVER + ,MS_WINDOWS_SERVER + ,MSDOS_SERVER + ,APPLESHARE_SERVER + ,NETPRESENZ_SERVER + ,DLS_SERVER +} eServerType; + +static eServerType server_type = GENERIC_SERVER; /* the type of ftp host */ +static int unsure_type = FALSE; /* sure about the type? */ +static BOOLEAN use_list = FALSE; /* use the LIST command? */ + +static int interrupted_in_next_data_char = FALSE; + +#ifdef POLL_PORTS +static PortNumber port_number = FIRST_TCP_PORT; +#endif /* POLL_PORTS */ + +static BOOL have_socket = FALSE; /* true if master_socket is valid */ +static LYNX_FD master_socket; /* Listening socket = invalid */ + +static char *port_command; /* Command for setting the port */ +static fd_set open_sockets; /* Mask of active channels */ +static LYNX_FD num_sockets; /* Number of sockets to scan */ +static PortNumber passive_port; /* Port server specified for data */ + +#define NEXT_CHAR HTGetCharacter() /* Use function in HTFormat.c */ + +#define DATA_BUFFER_SIZE 2048 +static char data_buffer[DATA_BUFFER_SIZE]; /* Input data buffer */ +static char *data_read_pointer; +static char *data_write_pointer; + +#define NEXT_DATA_CHAR next_data_char() +static int close_connection(connection * con); + +#ifndef HAVE_ATOLL +off_t LYatoll(const char *value) +{ + off_t result = 0; + + while (*value != '\0') { + result = (result * 10) + (off_t) (*value++ - '0'); + } + return result; +} +#endif + +#ifdef LY_FIND_LEAKS +/* + * This function frees module globals. - FM + */ +static void free_FTPGlobals(void) +{ + FREE(user_entered_password); + FREE(last_username_and_host); + if (control) { + if (control->socket != -1) + close_connection(control); + FREE(control); + } +} +#endif /* LY_FIND_LEAKS */ + +/* PUBLIC HTVMS_name() + * CONVERTS WWW name into a VMS name + * ON ENTRY: + * nn Node Name (optional) + * fn WWW file name + * + * ON EXIT: + * returns vms file specification + * + * Bug: Returns pointer to static -- non-reentrant + */ +char *HTVMS_name(const char *nn, + const char *fn) +{ + /* We try converting the filename into Files-11 syntax. That is, we assume + * first that the file is, like us, on a VMS node. We try remote (or + * local) DECnet access. Files-11, VMS, VAX and DECnet are trademarks of + * Digital Equipment Corporation. The node is assumed to be local if the + * hostname WITHOUT DOMAIN matches the local one. @@@ + */ + static char *vmsname; + char *filename = (char *) malloc(strlen(fn) + 1); + char *nodename = (char *) malloc(strlen(nn) + 2 + 1); /* Copies to hack */ + char *second; /* 2nd slash */ + char *last; /* last slash */ + + const char *hostname = HTHostName(); + + if (!filename || !nodename) + outofmem(__FILE__, "HTVMSname"); + + strcpy(filename, fn); + strcpy(nodename, ""); /* On same node? Yes if node names match */ + if (StrNCmp(nn, "localhost", 9)) { + const char *p; + const char *q; + + for (p = hostname, q = nn; + *p && *p != '.' && *q && *q != '.'; p++, q++) { + if (TOUPPER(*p) != TOUPPER(*q)) { + char *r; + + strcpy(nodename, nn); + r = StrChr(nodename, '.'); /* Mismatch */ + if (r) + *r = '\0'; /* Chop domain */ + strcat(nodename, "::"); /* Try decnet anyway */ + break; + } + } + } + + second = StrChr(filename + 1, '/'); /* 2nd slash */ + last = strrchr(filename, '/'); /* last slash */ + + if (!second) { /* Only one slash */ + HTSprintf0(&vmsname, "%s%s", nodename, filename + 1); + } else if (second == last) { /* Exactly two slashes */ + *second = '\0'; /* Split filename from disk */ + HTSprintf0(&vmsname, "%s%s:%s", nodename, filename + 1, second + 1); + *second = '/'; /* restore */ + } else { /* More than two slashes */ + char *p; + + *second = '\0'; /* Split disk from directories */ + *last = '\0'; /* Split dir from filename */ + HTSprintf0(&vmsname, "%s%s:[%s]%s", + nodename, filename + 1, second + 1, last + 1); + *second = *last = '/'; /* restore filename */ + if ((p = StrChr(vmsname, '[')) != 0) { + while (*p != '\0' && *p != ']') { + if (*p == '/') + *p = '.'; /* Convert dir sep. to dots */ + ++p; + } + } + } + FREE(nodename); + FREE(filename); + return vmsname; +} + +/* Procedure: Read a character from the data connection + * ---------------------------------------------------- + */ +static int next_data_char(void) +{ + int status; + + if (data_read_pointer >= data_write_pointer) { + status = NETREAD(data_soc, data_buffer, DATA_BUFFER_SIZE); + if (status == HT_INTERRUPTED) + interrupted_in_next_data_char = 1; + if (status <= 0) + return EOF; + data_write_pointer = data_buffer + status; + data_read_pointer = data_buffer; + } +#ifdef NOT_ASCII + { + char c = *data_read_pointer++; + + return FROMASCII(c); + } +#else + return UCH(*data_read_pointer++); +#endif /* NOT_ASCII */ +} + +/* Close an individual connection + * + */ +static int close_connection(connection * con) +{ + connection *scan; + int status; + + CTRACE((tfp, "HTFTP: Closing control socket %d\n", con->socket)); + status = NETCLOSE(con->socket); + if (TRACE && status != 0) { +#ifdef UNIX + CTRACE((tfp, "HTFTP:close_connection: %s", LYStrerror(errno))); +#else + if (con->socket != INVSOC) + HTInetStatus("HTFTP:close_connection"); +#endif + } + con->socket = -1; + if (connections == con) { + connections = con->next; + return status; + } + for (scan = connections; scan; scan = scan->next) { + if (scan->next == con) { + scan->next = con->next; /* Unlink */ + if (control == con) + control = (connection *) 0; + return status; + } /*if */ + } /* for */ + return -1; /* very strange -- was not on list. */ +} + +static char *help_message_buffer = NULL; /* global :( */ + +static void init_help_message_cache(void) +{ + FREE(help_message_buffer); +} + +static void help_message_cache_add(char *string) +{ + if (help_message_buffer) + StrAllocCat(help_message_buffer, string); + else + StrAllocCopy(help_message_buffer, string); + + CTRACE((tfp, "Adding message to help cache: %s\n", string)); +} + +static char *help_message_cache_non_empty(void) +{ + return (help_message_buffer); +} + +static char *help_message_cache_contents(void) +{ + return (help_message_buffer); +} + +/* Send One Command + * ---------------- + * + * This function checks whether we have a control connection, and sends + * one command if given. + * + * On entry, + * control points to the connection which is established. + * cmd points to a command, or is zero to just get the response. + * + * The command should already be terminated with the CRLF pair. + * + * On exit, + * returns: 1 for success, + * or negative for communication failure (in which case + * the control connection will be closed). + */ +static int write_cmd(const char *cmd) +{ + int status; + + if (!control) { + CTRACE((tfp, "HTFTP: No control connection set up!!\n")); + return HT_NO_CONNECTION; + } + + if (cmd) { + CTRACE((tfp, " Tx: %s", cmd)); +#ifdef NOT_ASCII + { + char *p; + + for (p = cmd; *p; p++) { + *p = TOASCII(*p); + } + } +#endif /* NOT_ASCII */ + status = (int) NETWRITE(control->socket, cmd, (unsigned) strlen(cmd)); + if (status < 0) { + CTRACE((tfp, + "HTFTP: Error %d sending command: closing socket %d\n", + status, control->socket)); + close_connection(control); + return status; + } + } + return 1; +} + +/* + * For each string in the list, check if it is found in the response text. + * If so, return TRUE. + */ +static BOOL find_response(HTList *list) +{ + BOOL result = FALSE; + HTList *p = list; + char *value; + + while ((value = (char *) HTList_nextObject(p)) != NULL) { + if (LYstrstr(response_text, value)) { + result = TRUE; + break; + } + } + return result; +} + +/* Execute Command and get Response + * -------------------------------- + * + * See the state machine illustrated in RFC959, p57. This implements + * one command/reply sequence. It also interprets lines which are to + * be continued, which are marked with a "-" immediately after the + * status code. + * + * Continuation then goes on until a line with a matching reply code + * an a space after it. + * + * On entry, + * control points to the connection which is established. + * cmd points to a command, or is zero to just get the response. + * + * The command must already be terminated with the CRLF pair. + * + * On exit, + * returns: The first digit of the reply type, + * or negative for communication failure. + */ +static int response(const char *cmd) +{ + int result; /* Three-digit decimal code */ + int continuation_response = -1; + int status; + + if ((status = write_cmd(cmd)) < 0) + return status; + + do { + char *p = response_text; + + for (;;) { + int ich = NEXT_CHAR; + + if (((*p++ = (char) ich) == LF) + || (p == &response_text[LINE_LENGTH])) { + + char continuation; + + if (interrupted_in_htgetcharacter) { + CTRACE((tfp, + "HTFTP: Interrupted in HTGetCharacter, apparently.\n")); + NETCLOSE(control->socket); + control->socket = -1; + return HT_INTERRUPTED; + } + + *p = '\0'; /* Terminate the string */ + CTRACE((tfp, " Rx: %s", response_text)); + + /* Check for login or help messages */ + if (!StrNCmp(response_text, "230-", 4) || + !StrNCmp(response_text, "250-", 4) || + !StrNCmp(response_text, "220-", 4)) + help_message_cache_add(response_text + 4); + + sscanf(response_text, "%d%c", &result, &continuation); + if (continuation_response == -1) { + if (continuation == '-') /* start continuation */ + continuation_response = result; + } else { /* continuing */ + if (continuation_response == result && + continuation == ' ') + continuation_response = -1; /* ended */ + } + if (result == 220 && find_response(broken_ftp_retr)) { + Broken_RETR = TRUE; + CTRACE((tfp, "This server is broken (RETR)\n")); + } +#ifdef INET6 + if (result == 220 && find_response(broken_ftp_epsv)) { + Broken_EPSV = TRUE; + CTRACE((tfp, "This server is broken (EPSV)\n")); + } +#endif + break; + } + /* if end of line */ + if (interrupted_in_htgetcharacter) { + CTRACE((tfp, + "HTFTP: Interrupted in HTGetCharacter, apparently.\n")); + NETCLOSE(control->socket); + control->socket = -1; + return HT_INTERRUPTED; + } + + if (ich == EOF) { + CTRACE((tfp, "Error on rx: closing socket %d\n", + control->socket)); + strcpy(response_text, "000 *** TCP read error on response\n"); + close_connection(control); + return -1; /* End of file on response */ + } + } /* Loop over characters */ + + } while (continuation_response != -1); + + if (result == 421) { + CTRACE((tfp, "HTFTP: They close so we close socket %d\n", + control->socket)); + close_connection(control); + return -1; + } + if ((result == 255 && server_type == CMS_SERVER) && + (0 == strncasecomp(cmd, "CWD", 3) || + 0 == strcasecomp(cmd, "CDUP"))) { + /* + * Alas, CMS returns 255 on failure to CWD to parent of root. - PG + */ + result = 555; + } + return result / 100; +} + +static int send_cmd_1(const char *verb) +{ + char command[80]; + + sprintf(command, "%.*s%c%c", (int) sizeof(command) - 4, verb, CR, LF); + return response(command); +} + +static int send_cmd_2(const char *verb, const char *param) +{ + char *command = 0; + int status; + + HTSprintf0(&command, "%s %s%c%c", verb, param, CR, LF); + status = response(command); + FREE(command); + + return status; +} + +#define send_cwd(path) send_cmd_2("CWD", path) + +/* + * This function should try to set the macintosh server into binary mode. Some + * servers need an additional letter after the MACB command. + */ +static int set_mac_binary(eServerType ServerType) +{ + /* try to set mac binary mode */ + if (ServerType == APPLESHARE_SERVER || + ServerType == NETPRESENZ_SERVER) { + /* + * Presumably E means "Enable". - KW + */ + return (2 == response("MACB E\r\n")); + } else { + return (2 == response("MACB\r\n")); + } +} + +/* This function gets the current working directory to help + * determine what kind of host it is + */ + +static void get_ftp_pwd(eServerType *ServerType, BOOLEAN *UseList) +{ + char *cp; + + /* get the working directory (to see what it looks like) */ + int status = response("PWD\r\n"); + + if (status < 0) { + return; + } else { + cp = StrChr(response_text + 5, '"'); + if (cp) + *cp = '\0'; + if (*ServerType == TCPC_SERVER) { + *ServerType = ((response_text[5] == '/') ? + NCSA_SERVER : TCPC_SERVER); + CTRACE((tfp, "HTFTP: Treating as %s server.\n", + ((*ServerType == NCSA_SERVER) ? + "NCSA" : "TCPC"))); + } else if (response_text[5] == '/') { + /* path names beginning with / imply Unix, + * right? + */ + if (set_mac_binary(*ServerType)) { + *ServerType = NCSA_SERVER; + CTRACE((tfp, "HTFTP: Treating as NCSA server.\n")); + } else { + *ServerType = UNIX_SERVER; + *UseList = TRUE; + CTRACE((tfp, "HTFTP: Treating as Unix server.\n")); + } + return; + } else if (response_text[strlen(response_text) - 1] == ']') { + /* path names ending with ] imply VMS, right? */ + *ServerType = VMS_SERVER; + *UseList = TRUE; + CTRACE((tfp, "HTFTP: Treating as VMS server.\n")); + } else { + *ServerType = GENERIC_SERVER; + CTRACE((tfp, "HTFTP: Treating as Generic server.\n")); + } + + if ((*ServerType == NCSA_SERVER) || + (*ServerType == TCPC_SERVER) || + (*ServerType == PETER_LEWIS_SERVER) || + (*ServerType == NETPRESENZ_SERVER)) + set_mac_binary(*ServerType); + } +} + +/* This function turns MSDOS-like directory output off for + * Windows NT servers. + */ + +static void set_unix_dirstyle(eServerType *ServerType, BOOLEAN *UseList) +{ + char *cp; + + /* This is a toggle. It seems we have to toggle in order to see + * the current state (after toggling), so we may end up toggling + * twice. - kw + */ + int status = response("SITE DIRSTYLE\r\n"); + + if (status != 2) { + *ServerType = GENERIC_SERVER; + CTRACE((tfp, "HTFTP: DIRSTYLE failed, treating as Generic server.\n")); + return; + } else { + *UseList = TRUE; + /* Expecting one of: + * 200 MSDOS-like directory output is off + * 200 MSDOS-like directory output is on + * The following code doesn't look for the full exact string - + * who knows how the wording may change in some future version. + * If the first response isn't recognized, we toggle again + * anyway, under the assumption that it's more likely that + * the MSDOS setting was "off" originally. - kw + */ + cp = strstr(response_text + 4, "MSDOS"); + if (cp && strstr(cp, " off")) { + return; /* already off now. */ + } else { + response("SITE DIRSTYLE\r\n"); + } + } +} + +#define CheckForInterrupt(msg) \ + if (status == HT_INTERRUPTED) { \ + CTRACE((tfp, "HTFTP: Interrupted %s.\n", msg)); \ + _HTProgress(CONNECTION_INTERRUPTED); \ + NETCLOSE(control->socket); \ + control->socket = -1; \ + return HT_INTERRUPTED; \ + } + +/* Get a valid connection to the host + * ---------------------------------- + * + * On entry, + * arg points to the name of the host in a hypertext address + * On exit, + * returns <0 if error + * socket number if success + * + * This routine takes care of managing timed-out connections, and + * limiting the number of connections in use at any one time. + * + * It ensures that all connections are logged in if they exist. + * It ensures they have the port number transferred. + */ +static int get_connection(const char *arg, + HTParentAnchor *anchor) +{ + int status; + char *command = 0; + connection *con; + char *username = NULL; + char *password = NULL; + static BOOLEAN firstuse = TRUE; + + if (firstuse) { + /* + * Set up freeing at exit. - FM + */ +#ifdef LY_FIND_LEAKS + atexit(free_FTPGlobals); +#endif + firstuse = FALSE; + } + + if (control != 0) { + connection *next = control->next; + + if (control->socket != -1) { + NETCLOSE(control->socket); + } + memset(con = control, 0, sizeof(*con)); + con->next = next; + } else { + con = typecalloc(connection); + if (con == NULL) + outofmem(__FILE__, "get_connection"); + } + con->socket = -1; + + if (isEmpty(arg)) { + free(con); + return -1; /* Bad if no name specified */ + } + + /* Get node name: + */ + CTRACE((tfp, "get_connection(%s)\n", arg)); + { + char *p1 = HTParse(arg, "", PARSE_HOST); + char *p2 = strrchr(p1, '@'); /* user? */ + char *pw = NULL; + + if (p2 != NULL) { + username = p1; + *p2 = '\0'; /* terminate */ + p1 = p2 + 1; /* point to host */ + pw = StrChr(username, ':'); + if (pw != NULL) { + *pw++ = '\0'; + password = HTUnEscape(pw); + } + if (*username) + HTUnEscape(username); + + /* + * If the password doesn't exist then we are going to have to ask + * the user for it. The only problem is that we don't want to ask + * for it every time, so we will store away in a primitive fashion. + */ + if (!password) { + char *tmp = NULL; + + HTSprintf0(&tmp, "%s@%s", username, p1); + /* + * If the user@host is not equal to the last time through or + * user_entered_password has no data then we need to ask the + * user for the password. + */ + if (!last_username_and_host || + strcmp(tmp, last_username_and_host) || + !user_entered_password) { + + StrAllocCopy(last_username_and_host, tmp); + HTSprintf0(&tmp, gettext("Enter password for user %s@%s:"), + username, p1); + FREE(user_entered_password); + user_entered_password = HTPromptPassword(tmp, NULL); + + } /* else we already know the password */ + password = user_entered_password; + FREE(tmp); + } + } + + if (!username) + FREE(p1); + } /* scope of p1 */ + + status = HTDoConnect(arg, "FTP", IPPORT_FTP, (int *) &con->socket); + + if (status < 0) { + if (status == HT_INTERRUPTED) { + CTRACE((tfp, "HTFTP: Interrupted on connect\n")); + } else { + CTRACE((tfp, "HTFTP: Unable to connect to remote host for `%s'.\n", + arg)); + } + if (status == HT_INTERRUPTED) { + _HTProgress(CONNECTION_INTERRUPTED); + status = HT_NOT_LOADED; + } else { + HTAlert(gettext("Unable to connect to FTP host.")); + } + if (con->socket != -1) { + NETCLOSE(con->socket); + } + + FREE(username); + if (control == con) + control = NULL; + FREE(con); + return status; /* Bad return */ + } + + CTRACE((tfp, "FTP connected, socket %d control %p\n", + con->socket, (void *) con)); + control = con; /* Current control connection */ + + /* Initialise buffering for control connection */ + HTInitInput(control->socket); + init_help_message_cache(); /* Clear the login message buffer. */ + + /* Now we log in Look up username, prompt for pw. + */ + status = response(NULL); /* Get greeting */ + CheckForInterrupt("at beginning of login"); + + server_type = GENERIC_SERVER; /* reset */ + if (status == 2) { /* Send username */ + char *cp; /* look at greeting text */ + + /* don't gettext() this -- incoming text: */ + if (strlen(response_text) > 4) { + if ((cp = strstr(response_text, " awaits your command")) || + (cp = strstr(response_text, " ready."))) { + *cp = '\0'; + } + cp = response_text + 4; + if (!strncasecomp(cp, "NetPresenz", 10)) + server_type = NETPRESENZ_SERVER; + } else { + cp = response_text; + } + StrAllocCopy(anchor->server, cp); + + status = send_cmd_2("USER", (username && *username) + ? username + : "anonymous"); + + CheckForInterrupt("while sending username"); + } + if (status == 3) { /* Send password */ + if (non_empty(password)) { + HTSprintf0(&command, "PASS %s%c%c", password, CR, LF); + } else { + /* + * No password was given; use mail-address. + */ + const char *the_address; + char *user = NULL; + const char *host = NULL; + char *cp; + + the_address = anonftp_password; + if (isEmpty(the_address)) + the_address = personal_mail_address; + if (isEmpty(the_address)) + the_address = LYGetEnv("USER"); + if (isEmpty(the_address)) + the_address = "WWWuser"; + + StrAllocCopy(user, the_address); + if ((cp = StrChr(user, '@')) != NULL) { + *cp++ = '\0'; + if (*cp == '\0') + host = HTHostName(); + else + host = cp; + } else { + host = HTHostName(); + } + + /* + * If host is not fully qualified, suppress it + * as ftp.uu.net prefers a blank to a bad name + */ + if (!(host) || StrChr(host, '.') == NULL) + host = ""; + + HTSprintf0(&command, "PASS %s@%s%c%c", user, host, CR, LF); + FREE(user); + } + status = response(command); + FREE(command); + CheckForInterrupt("while sending password"); + } + FREE(username); + + if (status == 3) { + status = send_cmd_1("ACCT noaccount"); + CheckForInterrupt("while sending password"); + } + if (status != 2) { + CTRACE((tfp, "HTFTP: Login fail: %s", response_text)); + /* if (control->socket > 0) close_connection(control->socket); */ + return -1; /* Bad return */ + } + CTRACE((tfp, "HTFTP: Logged in.\n")); + + /* Check for host type */ + if (server_type != NETPRESENZ_SERVER) + server_type = GENERIC_SERVER; /* reset */ + use_list = FALSE; /* reset */ + if (response("SYST\r\n") == 2) { + /* we got a line -- what kind of server are we talking to? */ + if (StrNCmp(response_text + 4, + "UNIX Type: L8 MAC-OS MachTen", 28) == 0) { + server_type = MACHTEN_SERVER; + use_list = TRUE; + CTRACE((tfp, "HTFTP: Treating as MachTen server.\n")); + + } else if (strstr(response_text + 4, "UNIX") != NULL || + strstr(response_text + 4, "Unix") != NULL) { + server_type = UNIX_SERVER; + unsure_type = FALSE; /* to the best of out knowledge... */ + use_list = TRUE; + CTRACE((tfp, "HTFTP: Treating as Unix server.\n")); + + } else if (strstr(response_text + 4, "MSDOS") != NULL) { + server_type = MSDOS_SERVER; + use_list = TRUE; + CTRACE((tfp, "HTFTP: Treating as MSDOS (Unix emulation) server.\n")); + + } else if (StrNCmp(response_text + 4, "VMS", 3) == 0) { + char *tilde = strstr(arg, "/~"); + + use_list = TRUE; + if (tilde != 0 + && tilde[2] != 0 + && strstr(response_text + 4, "MadGoat") != 0) { + server_type = UNIX_SERVER; + CTRACE((tfp, "HTFTP: Treating VMS as UNIX server.\n")); + } else { + server_type = VMS_SERVER; + CTRACE((tfp, "HTFTP: Treating as VMS server.\n")); + } + + } else if ((StrNCmp(response_text + 4, "VM/CMS", 6) == 0) || + (StrNCmp(response_text + 4, "VM ", 3) == 0)) { + server_type = CMS_SERVER; + use_list = TRUE; + CTRACE((tfp, "HTFTP: Treating as CMS server.\n")); + + } else if (StrNCmp(response_text + 4, "DCTS", 4) == 0) { + server_type = DCTS_SERVER; + CTRACE((tfp, "HTFTP: Treating as DCTS server.\n")); + + } else if (strstr(response_text + 4, "MAC-OS TCP/Connect II") != NULL) { + server_type = TCPC_SERVER; + CTRACE((tfp, "HTFTP: Looks like a TCPC server.\n")); + get_ftp_pwd(&server_type, &use_list); + unsure_type = TRUE; + + } else if (server_type == NETPRESENZ_SERVER) { /* already set above */ + use_list = TRUE; + set_mac_binary(server_type); + CTRACE((tfp, "HTFTP: Treating as NetPresenz (MACOS) server.\n")); + + } else if (StrNCmp(response_text + 4, "MACOS Peter's Server", 20) == 0) { + server_type = PETER_LEWIS_SERVER; + use_list = TRUE; + set_mac_binary(server_type); + CTRACE((tfp, "HTFTP: Treating as Peter Lewis (MACOS) server.\n")); + + } else if (StrNCmp(response_text + 4, "Windows_NT", 10) == 0) { + server_type = WINDOWS_NT_SERVER; + CTRACE((tfp, "HTFTP: Treating as Window_NT server.\n")); + set_unix_dirstyle(&server_type, &use_list); + + } else if (StrNCmp(response_text + 4, "Windows2000", 11) == 0) { + server_type = WINDOWS_2K_SERVER; + CTRACE((tfp, "HTFTP: Treating as Window_2K server.\n")); + set_unix_dirstyle(&server_type, &use_list); + + } else if (StrNCmp(response_text + 4, "MS Windows", 10) == 0) { + server_type = MS_WINDOWS_SERVER; + use_list = TRUE; + CTRACE((tfp, "HTFTP: Treating as MS Windows server.\n")); + + } else if (StrNCmp(response_text + 4, + "MACOS AppleShare IP FTP Server", 30) == 0) { + server_type = APPLESHARE_SERVER; + use_list = TRUE; + set_mac_binary(server_type); + CTRACE((tfp, "HTFTP: Treating as AppleShare server.\n")); + + } else { + server_type = GENERIC_SERVER; + CTRACE((tfp, "HTFTP: Ugh! A Generic server.\n")); + get_ftp_pwd(&server_type, &use_list); + unsure_type = TRUE; + } + } else { + /* SYST fails :( try to get the type from the PWD command */ + get_ftp_pwd(&server_type, &use_list); + } + + return con->socket; /* Good return */ +} + +static void reset_master_socket(void) +{ + have_socket = FALSE; +} + +static void set_master_socket(int value) +{ + have_socket = (BOOLEAN) (value >= 0); + if (have_socket) + master_socket = (LYNX_FD) value; +} + +/* Close Master (listening) socket + * ------------------------------- + * + * + */ +static int close_master_socket(void) +{ + int status; + + if (have_socket) + FD_CLR(master_socket, &open_sockets); + + status = NETCLOSE((int) master_socket); + CTRACE((tfp, "HTFTP: Closed master socket %u\n", (unsigned) master_socket)); + + reset_master_socket(); + + if (status < 0) + return HTInetStatus(gettext("close master socket")); + else + return status; +} + +/* Open a master socket for listening on + * ------------------------------------- + * + * When data is transferred, we open a port, and wait for the server to + * connect with the data. + * + * On entry, + * have_socket Must be false, if master_socket is not setup already + * master_socket Must be negative if not set up already. + * On exit, + * Returns socket number if good + * less than zero if error. + * master_socket is socket number if good, else negative. + * port_number is valid if good. + */ +static int get_listen_socket(void) +{ + LY_SOCKADDR soc_A; + +#ifdef INET6 + unsigned short af; + LY_SOCKLEN slen; +#endif /* INET6 */ + int new_socket; /* Will be master_socket */ + + FD_ZERO(&open_sockets); /* Clear our record of open sockets */ + num_sockets = 0; + + FREE(port_command); +#ifndef REPEAT_LISTEN + if (have_socket) + return master_socket; /* Done already */ +#endif /* !REPEAT_LISTEN */ + +#ifdef INET6 + /* query address family of control connection */ + memset(&soc_A, 0, sizeof(soc_A)); + slen = (LY_SOCKLEN) sizeof(soc_A); + if (getsockname(control->socket, SOCKADDR_OF(soc_A), &slen) < 0) { + return HTInetStatus("getsockname failed"); + } + af = SOCKADDR_OF(soc_A)->sa_family; +#endif /* INET6 */ + +/* Create internet socket +*/ +#ifdef INET6 + new_socket = socket(af, SOCK_STREAM, IPPROTO_TCP); +#else + new_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); +#endif /* INET6 */ + + if (new_socket < 0) + return HTInetStatus(gettext("socket for master socket")); + + CTRACE((tfp, "HTFTP: Opened master socket number %d\n", new_socket)); + +/* Search for a free port. +*/ +#ifdef INET6 + memset(&soc_A, 0, sizeof(soc_A)); + SOCKADDR_OF(soc_A)->sa_family = (unsigned short) af; + switch (af) { + case AF_INET: +#ifdef SIN6_LEN + SOCKADDR_OF(soc_A)->sa_len = sizeof(struct sockaddr_in); +#endif /* SIN6_LEN */ + break; + case AF_INET6: +#ifdef SIN6_LEN + SOCKADDR_OF(soc_A)->sa_len = sizeof(struct sockaddr_in6); +#endif /* SIN6_LEN */ + break; + default: + HTInetStatus("AF"); + } +#else + soc_A.soc_in.sin_family = AF_INET; /* Family = internet, host order */ + soc_A.soc_in.sin_addr.s_addr = INADDR_ANY; /* Any peer address */ +#endif /* INET6 */ +#ifdef POLL_PORTS + { + PortNumber old_port_number = port_number; + + for (port_number = (old_port_number + 1);; port_number++) { + int status; + + if (port_number > LAST_TCP_PORT) + port_number = FIRST_TCP_PORT; + if (port_number == old_port_number) { + return HTInetStatus("bind"); + } +#ifdef INET6 + soc_A.soc_in.sin_port = htons(port_number); +#else + soc_A.sin_port = htons(port_number); +#endif /* INET6 */ +#ifdef SOCKS + if (socks_flag) + if ((status = Rbind(new_socket, + SOCKADDR_OF(soc_A), + SOCKADDR_LEN(soc_A))) == 0) { + break; + } else +#endif /* SOCKS */ + if ((status = bind(new_socket, + SOCKADDR_OF(soc_A), + SOCKADDR_LEN(soc_A) + )) == 0) { + break; + } + CTRACE((tfp, "TCP bind attempt to port %d yields %d, errno=%d\n", + port_number, status, SOCKET_ERRNO)); + } /* for */ + } +#else + { + int status; + LY_SOCKLEN address_length = (LY_SOCKLEN) sizeof(soc_A); + +#ifdef SOCKS + if (socks_flag) + status = Rgetsockname(control->socket, + SOCKADDR_OF(soc_A), + &address_length); + else +#endif /* SOCKS */ + status = getsockname(control->socket, + SOCKADDR_OF(soc_A), + &address_length); + if (status < 0) { + close(new_socket); + return HTInetStatus("getsockname"); + } + CTRACE((tfp, "HTFTP: This host is %s\n", + HTInetString((void *) &soc_A.soc_in))); + + soc_A.soc_in.sin_port = 0; /* Unspecified: please allocate */ +#ifdef SOCKS + if (socks_flag) + status = Rbind(new_socket, + SOCKADDR_OF(soc_A), + sizeof(soc_A)); + else +#endif /* SOCKS */ + status = bind(new_socket, + SOCKADDR_OF(soc_A), + SOCKADDR_LEN(soc_A)); + if (status < 0) { + close(new_socket); + return HTInetStatus("bind"); + } + + address_length = sizeof(soc_A); +#ifdef SOCKS + if (socks_flag) + status = Rgetsockname(new_socket, + SOCKADDR_OF(soc_A), + &address_length); + else +#endif /* SOCKS */ + status = getsockname(new_socket, + SOCKADDR_OF(soc_A), + &address_length); + if (status < 0) { + close(new_socket); + return HTInetStatus("getsockname"); + } + } +#endif /* POLL_PORTS */ + + CTRACE((tfp, "HTFTP: bound to port %d on %s\n", + (int) ntohs(soc_A.soc_in.sin_port), + HTInetString((void *) &soc_A.soc_in))); + +#ifdef REPEAT_LISTEN + if (have_socket) + (void) close_master_socket(); +#endif /* REPEAT_LISTEN */ + + set_master_socket(new_socket); + +/* Now we must find out who we are to tell the other guy +*/ + (void) HTHostName(); /* Make address valid - doesn't work */ +#ifdef INET6 + switch (SOCKADDR_OF(soc_A)->sa_family) { + case AF_INET: +#endif /* INET6 */ + HTSprintf0(&port_command, "PORT %d,%d,%d,%d,%d,%d%c%c", + (int) *((unsigned char *) (&soc_A.soc_in.sin_addr) + 0), + (int) *((unsigned char *) (&soc_A.soc_in.sin_addr) + 1), + (int) *((unsigned char *) (&soc_A.soc_in.sin_addr) + 2), + (int) *((unsigned char *) (&soc_A.soc_in.sin_addr) + 3), + (int) *((unsigned char *) (&soc_A.soc_in.sin_port) + 0), + (int) *((unsigned char *) (&soc_A.soc_in.sin_port) + 1), + CR, LF); + +#ifdef INET6 + break; + + case AF_INET6: + { + char hostbuf[MAXHOSTNAMELEN]; + char portbuf[MAXHOSTNAMELEN]; + + getnameinfo(SOCKADDR_OF(soc_A), + SOCKADDR_LEN(soc_A), + hostbuf, + (socklen_t) sizeof(hostbuf), + portbuf, + (socklen_t) sizeof(portbuf), + NI_NUMERICHOST | NI_NUMERICSERV); + HTSprintf0(&port_command, "EPRT |%d|%s|%s|%c%c", 2, hostbuf, portbuf, + CR, LF); + break; + } + default: + HTSprintf0(&port_command, "JUNK%c%c", CR, LF); + break; + } +#endif /* INET6 */ + if (port_command == NULL) + return -1; + + /* Inform TCP that we will accept connections + */ + { + int status; + +#ifdef SOCKS + if (socks_flag) + status = Rlisten((int) master_socket, 1); + else +#endif /* SOCKS */ + status = listen((int) master_socket, 1); + if (status < 0) { + reset_master_socket(); + return HTInetStatus("listen"); + } + } + CTRACE((tfp, "TCP: Master socket(), bind() and listen() all OK\n")); + FD_SET(master_socket, &open_sockets); + if ((master_socket + 1) > num_sockets) + num_sockets = master_socket + 1; + + return (int) master_socket; /* Good */ + +} /* get_listen_socket */ + +static const char *months[12] = +{ + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" +}; + +/* Procedure: Set the current and last year strings and date integer + * ----------------------------------------------------------------- + * + * Bug: + * This code is for sorting listings by date, if that option + * is selected in Lynx, and doesn't take into account time + * zones or ensure resetting at midnight, so the sort may not + * be perfect, but the actual date isn't changed in the display, + * i.e., the date is still correct. - FM + */ +static void set_years_and_date(void) +{ + char day[8], month[8], date[12]; + time_t NowTime; + int i; + char *printable; + + NowTime = time(NULL); + printable = ctime(&NowTime); + LYStrNCpy(day, printable + 8, 2); + if (day[0] == ' ') { + day[0] = '0'; + } + LYStrNCpy(month, printable + 4, 3); + for (i = 0; i < 12; i++) { + if (!strcasecomp(month, months[i])) { + break; + } + } + i++; + sprintf(date, "9999%02d%.2s", i % 100, day); + TheDate = atoi(date); + LYStrNCpy(ThisYear, printable + 20, 4); + sprintf(LastYear, "%d", (atoi(ThisYear) - 1) % 10000); + HaveYears = TRUE; +} + +typedef struct _EntryInfo { + char *filename; + char *linkname; /* symbolic link, if any */ + char *type; + char *date; + off_t size; + BOOLEAN display; /* show this entry? */ +#ifdef LONG_LIST + unsigned long file_links; + char *file_mode; + char *file_user; + char *file_group; +#endif +} EntryInfo; + +static void free_entryinfo_struct_contents(EntryInfo *entry_info) +{ + if (entry_info) { +#ifdef LONG_LIST + FREE(entry_info->file_mode); + FREE(entry_info->file_user); + FREE(entry_info->file_group); +#endif + FREE(entry_info->filename); + FREE(entry_info->linkname); + FREE(entry_info->type); + FREE(entry_info->date); + } + /* don't free the struct */ +} + +/* + * is_ls_date() -- + * Return TRUE if s points to a string of the form: + * "Sep 1 1990 " or + * "Sep 11 11:59 " or + * "Dec 12 1989 " or + * "FCv 23 1990 " ... + */ +static BOOLEAN is_ls_date(char *s) +{ + /* must start with three alpha characters */ + if (!isalpha(UCH(*s++)) || !isalpha(UCH(*s++)) || !isalpha(UCH(*s++))) + return FALSE; + + /* space or HT_NON_BREAK_SPACE */ + if (!(*s == ' ' || *s == HT_NON_BREAK_SPACE)) { + return FALSE; + } + s++; + + /* space or digit */ + if (!(*s == ' ' || isdigit(UCH(*s)))) { + return FALSE; + } + s++; + + /* digit */ + if (!isdigit(UCH(*s++))) + return FALSE; + + /* space */ + if (*s++ != ' ') + return FALSE; + + /* space or digit */ + if (!(*s == ' ' || isdigit(UCH(*s)))) { + return FALSE; + } + s++; + + /* digit */ + if (!isdigit(UCH(*s++))) + return FALSE; + + /* colon or digit */ + if (!(*s == ':' || isdigit(UCH(*s)))) { + return FALSE; + } + s++; + + /* digit */ + if (!isdigit(UCH(*s++))) + return FALSE; + + /* space or digit */ + if (!(*s == ' ' || isdigit(UCH(*s)))) { + return FALSE; + } + s++; + + /* space */ + if (*s != ' ') + return FALSE; + + return TRUE; +} /* is_ls_date() */ + +/* + * Extract the name, size, and date from an EPLF line. - 08-06-96 DJB + */ +static void parse_eplf_line(char *line, + EntryInfo *info) +{ + char *cp = line; + char ct[26]; + off_t size; + time_t secs; + static time_t base; /* time() value on this OS in 1970 */ + static int flagbase = 0; + + if (!flagbase) { + struct tm t; + + t.tm_year = 70; + t.tm_mon = 0; + t.tm_mday = 0; + t.tm_hour = 0; + t.tm_min = 0; + t.tm_sec = 0; + t.tm_isdst = -1; + base = mktime(&t); /* could return -1 */ + flagbase = 1; + } + + while (*cp) { + switch (*cp) { + case '\t': + StrAllocCopy(info->filename, cp + 1); + return; + case 's': + size = 0; + while (*(++cp) && (*cp != ',')) + size = (size * 10) + (off_t) (*cp - '0'); + info->size = size; + break; + case 'm': + secs = 0; + while (*(++cp) && (*cp != ',')) + secs = (secs * 10) + (*cp - '0'); + secs += base; /* assumes that time_t is #seconds */ + LYStrNCpy(ct, ctime(&secs), 24); + StrAllocCopy(info->date, ct); + break; + case '/': + StrAllocCopy(info->type, ENTRY_IS_DIRECTORY); + /* FALLTHRU */ + default: + while (*cp) { + if (*cp++ == ',') + break; + } + break; + } + } +} /* parse_eplf_line */ + +/* + * Extract the name, size, and date from an ls -l line. + */ +static void parse_ls_line(char *line, + EntryInfo *entry) +{ +#ifdef LONG_LIST + char *next; + char *cp; +#endif + int i, j; + off_t base = 1; + off_t size_num = 0; + + for (i = (int) strlen(line) - 1; + (i > 13) && (!isspace(UCH(line[i])) || !is_ls_date(&line[i - 12])); + i--) { + ; /* null body */ + } + line[i] = '\0'; + if (i > 13) { + StrAllocCopy(entry->date, &line[i - 12]); + /* replace the 4th location with nbsp if it is a space or zero */ + if (entry->date[4] == ' ' || entry->date[4] == '0') + entry->date[4] = HT_NON_BREAK_SPACE; + /* make sure year or time is flush right */ + if (entry->date[11] == ' ') { + for (j = 11; j > 6; j--) { + entry->date[j] = entry->date[j - 1]; + } + } + } + j = i - 14; + while (isdigit(UCH(line[j]))) { + size_num += ((off_t) (line[j] - '0') * base); + base *= 10; + j--; + } + entry->size = size_num; + StrAllocCopy(entry->filename, &line[i + 1]); + +#ifdef LONG_LIST + line[j] = '\0'; + + /* + * Extract the file-permissions, as a string. + */ + if ((cp = StrChr(line, ' ')) != 0) { + if ((cp - line) == 10) { + *cp = '\0'; + StrAllocCopy(entry->file_mode, line); + *cp = ' '; + } + + /* + * Next is the link-count. + */ + next = 0; + entry->file_links = (unsigned long) strtol(cp, &next, 10); + if (next == 0 || *next != ' ') { + entry->file_links = 0; + next = cp; + } else { + cp = next; + } + /* + * Next is the user-name. + */ + while (isspace(UCH(*cp))) + ++cp; + if ((next = StrChr(cp, ' ')) != 0) + *next = '\0'; + if (*cp != '\0') + StrAllocCopy(entry->file_user, cp); + /* + * Next is the group-name (perhaps). + */ + if (next != NULL) { + cp = (next + 1); + while (isspace(UCH(*cp))) + ++cp; + if ((next = StrChr(cp, ' ')) != 0) + *next = '\0'; + if (*cp != '\0') + StrAllocCopy(entry->file_group, cp); + } + } +#endif +} + +/* + * Extract the name and size info and whether it refers to a directory from a + * LIST line in "dls" format. + */ +static void parse_dls_line(char *line, + EntryInfo *entry_info, + char **pspilledname) +{ + short j; + int base = 1; + off_t size_num = 0; + int len; + char *cps = NULL; + + /* README 763 Information about this server\0 + bin/ - \0 + etc/ = \0 + ls-lR 0 \0 + ls-lR.Z 3 \0 + pub/ = Public area\0 + usr/ - \0 + morgan 14 -> ../real/morgan\0 + TIMIT.mostlikely.Z\0 + 79215 \0 + */ + + len = (int) strlen(line); + if (len == 0) { + FREE(*pspilledname); + entry_info->display = FALSE; + return; + } + cps = LYSkipNonBlanks(line); + if (*cps == '\0') { /* only a filename, save it and return. */ + StrAllocCopy(*pspilledname, line); + entry_info->display = FALSE; + return; + } + if (len < 24 || line[23] != ' ' || + (isspace(UCH(line[0])) && !*pspilledname)) { + /* this isn't the expected "dls" format! */ + if (!isspace(UCH(line[0]))) + *cps = '\0'; + if (*pspilledname && !*line) { + entry_info->filename = *pspilledname; + *pspilledname = NULL; + if (entry_info->filename[strlen(entry_info->filename) - 1] == '/') + StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY); + else + StrAllocCopy(entry_info->type, ""); + } else { + StrAllocCopy(entry_info->filename, line); + if (cps != line && *(cps - 1) == '/') + StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY); + else + StrAllocCopy(entry_info->type, ""); + FREE(*pspilledname); + } + return; + } + + j = 22; + if (line[j] == '=' || line[j] == '-') { + StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY); + } else { + while (isdigit(UCH(line[j]))) { + size_num += (line[j] - '0') * base; + base *= 10; + j--; + } + } + entry_info->size = size_num; + + cps = LYSkipBlanks(&line[23]); + if (!StrNCmp(cps, "-> ", 3) && cps[3] != '\0' && cps[3] != ' ') { + StrAllocCopy(entry_info->type, ENTRY_IS_SYMBOLIC_LINK); + StrAllocCopy(entry_info->linkname, LYSkipBlanks(cps + 3)); + entry_info->size = 0; /* don't display size */ + } + + if (j > 0) + line[j] = '\0'; + + LYTrimTrailing(line); + + len = (int) strlen(line); + if (len == 0 && *pspilledname && **pspilledname) { + line = *pspilledname; + len = (int) strlen(*pspilledname); + } + if (len > 0 && line[len - 1] == '/') { + /* + * It's a dir, remove / and mark it as such. + */ + if (len > 1) + line[len - 1] = '\0'; + if (!entry_info->type) + StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY); + } + + StrAllocCopy(entry_info->filename, line); + FREE(*pspilledname); +} /* parse_dls_line() */ + +/* + * parse_vms_dir_entry() + * Format the name, date, and size from a VMS LIST line + * into the EntryInfo structure - FM + */ +static void parse_vms_dir_entry(char *line, + EntryInfo *entry_info) +{ + int i, j; + off_t ialloc; + char *cp, *cpd, *cps, date[16]; + const char *sp = " "; + + /* Get rid of blank lines, and information lines. Valid lines have the ';' + * version number token. + */ + if (!strlen(line) || (cp = StrChr(line, ';')) == NULL) { + entry_info->display = FALSE; + return; + } + + /* Cut out file or directory name at VMS version number. */ + *cp++ = '\0'; + StrAllocCopy(entry_info->filename, line); + + /* Cast VMS non-README file and directory names to lowercase. */ + if (strstr(entry_info->filename, "READ") == NULL) { + LYLowerCase(entry_info->filename); + i = (int) strlen(entry_info->filename); + } else { + i = (int) ((strstr(entry_info->filename, "READ") + - entry_info->filename) + + 4); + if (!StrNCmp(&entry_info->filename[i], "ME", 2)) { + i += 2; + while (entry_info->filename[i] && entry_info->filename[i] != '.') { + i++; + } + } else if (!StrNCmp(&entry_info->filename[i], ".ME", 3)) { + i = (int) strlen(entry_info->filename); + } else { + i = 0; + } + LYLowerCase(entry_info->filename + i); + } + + /* Uppercase terminal .z's or _z's. */ + if ((--i > 2) && + entry_info->filename[i] == 'z' && + (entry_info->filename[i - 1] == '.' || + entry_info->filename[i - 1] == '_')) + entry_info->filename[i] = 'Z'; + + /* Convert any tabs in rest of line to spaces. */ + cps = cp - 1; + while ((cps = StrChr(cps + 1, '\t')) != NULL) + *cps = ' '; + + /* Collapse serial spaces. */ + i = 0; + j = 1; + cps = cp; + while (cps[j] != '\0') { + if (cps[i] == ' ' && cps[j] == ' ') + j++; + else + cps[++i] = cps[j++]; + } + cps[++i] = '\0'; + + /* Set the years and date, if we don't have them yet. * */ + if (!HaveYears) { + set_years_and_date(); + } + + /* Track down the date. */ + if ((cpd = StrChr(cp, '-')) != NULL && + strlen(cpd) > 9 && isdigit(UCH(*(cpd - 1))) && + isalpha(UCH(*(cpd + 1))) && *(cpd + 4) == '-') { + + /* Month */ + *(cpd + 2) = (char) TOLOWER(*(cpd + 2)); + *(cpd + 3) = (char) TOLOWER(*(cpd + 3)); + sprintf(date, "%.3s ", cpd + 1); + + /* Day */ + if (isdigit(UCH(*(cpd - 2)))) + sprintf(date + 4, "%.2s ", cpd - 2); + else + sprintf(date + 4, "%c%.1s ", HT_NON_BREAK_SPACE, cpd - 1); + + /* Time or Year */ + if (!StrNCmp(ThisYear, cpd + 5, 4) && + strlen(cpd) > 15 && *(cpd + 12) == ':') { + sprintf(date + 7, "%.5s", cpd + 10); + } else { + sprintf(date + 7, " %.4s", cpd + 5); + } + + StrAllocCopy(entry_info->date, date); + } + + /* Track down the size */ + if ((cpd = StrChr(cp, '/')) != NULL) { + /* Appears be in used/allocated format */ + cps = cpd; + while (isdigit(UCH(*(cps - 1)))) + cps--; + if (cps < cpd) + *cpd = '\0'; + entry_info->size = LYatoll(cps); + cps = cpd + 1; + while (isdigit(UCH(*cps))) + cps++; + *cps = '\0'; + ialloc = LYatoll(cpd + 1); + /* Check if used is in blocks or bytes */ + if (entry_info->size <= ialloc) + entry_info->size *= 512; + + } else if (strtok(cp, sp) != NULL) { + /* We just initialized on the version number */ + /* Now let's hunt for a lone, size number */ + while ((cps = strtok(NULL, sp)) != NULL) { + cpd = cps; + while (isdigit(UCH(*cpd))) + cpd++; + if (*cpd == '\0') { + /* Assume it's blocks */ + entry_info->size = (LYatoll(cps) * 512); + break; + } + } + } + + TRACE_ENTRY("VMS", entry_info); + return; +} /* parse_vms_dir_entry() */ + +/* + * parse_ms_windows_dir_entry() -- + * Format the name, date, and size from an MS_WINDOWS LIST line into + * the EntryInfo structure (assumes Chameleon NEWT format). - FM + */ +static void parse_ms_windows_dir_entry(char *line, + EntryInfo *entry_info) +{ + char *cp = line; + char *cps, *cpd, date[16]; + char *end = line + strlen(line); + + /* Get rid of blank or junk lines. */ + cp = LYSkipBlanks(cp); + if (!(*cp)) { + entry_info->display = FALSE; + return; + } + + /* Cut out file or directory name. */ + cps = LYSkipNonBlanks(cp); + *cps++ = '\0'; + cpd = cps; + StrAllocCopy(entry_info->filename, cp); + + /* Track down the size */ + if (cps < end) { + cps = LYSkipBlanks(cps); + cpd = LYSkipNonBlanks(cps); + *cpd++ = '\0'; + if (isdigit(UCH(*cps))) { + entry_info->size = LYatoll(cps); + } else { + StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY); + } + } else { + StrAllocCopy(entry_info->type, ""); + } + + /* Set the years and date, if we don't have them yet. * */ + if (!HaveYears) { + set_years_and_date(); + } + + /* Track down the date. */ + if (cpd < end) { + cpd = LYSkipBlanks(cpd); + if (strlen(cpd) > 17) { + *(cpd + 6) = '\0'; /* Month and Day */ + *(cpd + 11) = '\0'; /* Year */ + *(cpd + 17) = '\0'; /* Time */ + if (strcmp(ThisYear, cpd + 7)) + /* Not this year, so show the year */ + sprintf(date, "%.6s %.4s", cpd, (cpd + 7)); + else + /* Is this year, so show the time */ + sprintf(date, "%.6s %.5s", cpd, (cpd + 12)); + StrAllocCopy(entry_info->date, date); + if (entry_info->date[4] == ' ' || entry_info->date[4] == '0') { + entry_info->date[4] = HT_NON_BREAK_SPACE; + } + } + } + + TRACE_ENTRY("MS Windows", entry_info); + return; +} /* parse_ms_windows_dir_entry */ + +/* + * parse_windows_nt_dir_entry() -- + * Format the name, date, and size from a WINDOWS_NT LIST line into + * the EntryInfo structure (assumes Chameleon NEWT format). - FM + */ +#ifdef NOTDEFINED +static void parse_windows_nt_dir_entry(char *line, + EntryInfo *entry_info) +{ + char *cp = line; + char *cps, *cpd, date[16]; + char *end = line + strlen(line); + int i; + + /* Get rid of blank or junk lines. */ + cp = LYSkipBlanks(cp); + if (!(*cp)) { + entry_info->display = FALSE; + return; + } + + /* Cut out file or directory name. */ + cpd = cp; + cps = LYSkipNonBlanks(end - 1); + cp = (cps + 1); + if (!strcmp(cp, ".") || !strcmp(cp, "..")) { + entry_info->display = FALSE; + return; + } + StrAllocCopy(entry_info->filename, cp); + if (cps < cpd) + return; + *cp = '\0'; + end = cp; + + /* Set the years and date, if we don't have them yet. * */ + if (!HaveYears) { + set_years_and_date(); + } + + /* Cut out the date. */ + cp = cps = cpd; + cps = LYSkipNonBlanks(cps); + *cps++ = '\0'; + if (cps > end) { + entry_info->display = FALSE; + return; + } + cps = LYSkipBlanks(cps); + cpd = LYSkipNonBlanks(cps); + *cps++ = '\0'; + if (cps > end || cpd == cps || strlen(cpd) < 7) { + entry_info->display = FALSE; + return; + } + if (strlen(cp) == 8 && + isdigit(*cp) && isdigit(*(cp + 1)) && *(cp + 2) == '-' && + isdigit(*(cp + 3)) && isdigit(*(cp + 4)) && *(cp + 5) == '-') { + *(cp + 2) = '\0'; /* Month */ + i = atoi(cp) - 1; + *(cp + 5) = '\0'; /* Day */ + sprintf(date, "%.3s %.2s", months[i], (cp + 3)); + if (date[4] == '0') + date[4] = ' '; + cp += 6; /* Year */ + if (strcmp((ThisYear + 2), cp)) { + /* Not this year, so show the year */ + if (atoi(cp) < 70) { + sprintf(&date[6], " 20%.2s", cp); + } else { + sprintf(&date[6], " 19%.2s", cp); + } + } else { + /* Is this year, so show the time */ + *(cpd + 2) = '\0'; /* Hour */ + i = atoi(cpd); + if (*(cpd + 5) == 'P' || *(cpd + 5) == 'p') + i += 12; + sprintf(&date[6], " %02d:%.2s", i, (cpd + 3)); + } + StrAllocCopy(entry_info->date, date); + if (entry_info->date[4] == ' ' || entry_info->date[4] == '0') { + entry_info->date[4] = HT_NON_BREAK_SPACE; + } + } + + /* Track down the size */ + if (cps < end) { + cps = LYSkipBlanks(cps); + cpd = LYSkipNonBlanks(cps); + *cpd = '\0'; + if (isdigit(*cps)) { + entry_info->size = LYatoll(cps); + } else { + StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY); + } + } else { + StrAllocCopy(entry_info->type, ""); + } + + /* Wrap it up */ + CTRACE((tfp, "HTFTP: Windows NT filename: %s date: %s size: %d\n", + entry_info->filename, + NonNull(entry_info->date), + entry_info->size)); + return; +} /* parse_windows_nt_dir_entry */ +#endif /* NOTDEFINED */ + +/* + * parse_cms_dir_entry() -- + * Format the name, date, and size from a VM/CMS line into + * the EntryInfo structure. - FM + */ +static void parse_cms_dir_entry(char *line, + EntryInfo *entry_info) +{ + char *cp = line; + char *cps, *cpd, date[16]; + char *end = line + strlen(line); + int RecordLength = 0; + int Records = 0; + int i; + + /* Get rid of blank or junk lines. */ + cp = LYSkipBlanks(cp); + if (!(*cp)) { + entry_info->display = FALSE; + return; + } + + /* Cut out file or directory name. */ + cps = LYSkipNonBlanks(cp); + *cps++ = '\0'; + StrAllocCopy(entry_info->filename, cp); + if (StrChr(entry_info->filename, '.') != NULL) + /* If we already have a dot, we did an NLST. */ + return; + cp = LYSkipBlanks(cps); + if (!(*cp)) { + /* If we don't have more, we've misparsed. */ + FREE(entry_info->filename); + FREE(entry_info->type); + entry_info->display = FALSE; + return; + } + cps = LYSkipNonBlanks(cp); + *cps++ = '\0'; + if ((0 == strcasecomp(cp, "DIR")) && (cp - line) > 17) { + /* It's an SFS directory. */ + StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY); + entry_info->size = 0; + } else { + /* It's a file. */ + cp--; + *cp = '.'; + StrAllocCat(entry_info->filename, cp); + + /* Track down the VM/CMS RECFM or type. */ + cp = cps; + if (cp < end) { + cp = LYSkipBlanks(cp); + cps = LYSkipNonBlanks(cp); + *cps++ = '\0'; + /* Check cp here, if it's relevant someday. */ + } + } + + /* Track down the record length or dash. */ + cp = cps; + if (cp < end) { + cp = LYSkipBlanks(cp); + cps = LYSkipNonBlanks(cp); + *cps++ = '\0'; + if (isdigit(UCH(*cp))) { + RecordLength = atoi(cp); + } + } + + /* Track down the number of records or the dash. */ + cp = cps; + if (cps < end) { + cp = LYSkipBlanks(cp); + cps = LYSkipNonBlanks(cp); + *cps++ = '\0'; + if (isdigit(UCH(*cp))) { + Records = atoi(cp); + } + if (Records > 0 && RecordLength > 0) { + /* Compute an approximate size. */ + entry_info->size = ((off_t) Records * (off_t) RecordLength); + } + } + + /* Set the years and date, if we don't have them yet. */ + if (!HaveYears) { + set_years_and_date(); + } + + /* Track down the date using the ":" separating hours/minutes: + * mm/dd/yy hh:mm + * 01234567890123 + */ + cpd = cps; + if (((cps < end) && + (cps = StrChr(cpd, ':')) != NULL) && + (cps < (end - 3) && + isdigit(UCH(*(cps + 1))) && isdigit(UCH(*(cps + 2))) && *(cps + 3) == ':')) { + cps += 3; + *cps = '\0'; + if ((cps - cpd) >= 14) { + cpd = (cps - 14); + *(cpd + 2) = '\0'; /* Month */ + *(cpd + 5) = '\0'; /* Day */ + *(cpd + 8) = '\0'; /* Year */ + cps -= 5; /* Time */ + if (*cpd == ' ') + *cpd = '0'; + i = atoi(cpd) - 1; + sprintf(date, "%.3s %.2s", months[i], (cpd + 3)); + if (date[4] == '0') + date[4] = ' '; + cpd += 6; /* Year */ + if (strcmp((ThisYear + 2), cpd)) { + /* Not this year, so show the year. */ + if (atoi(cpd) < 70) { + sprintf(&date[6], " 20%.2s", cpd); + } else { + sprintf(&date[6], " 19%.2s", cpd); + } + } else { + /* Is this year, so show the time. */ + *(cps + 2) = '\0'; /* Hour */ + i = atoi(cps); + sprintf(&date[6], " %02d:%.2s", i, (cps + 3)); + } + StrAllocCopy(entry_info->date, date); + if (entry_info->date[4] == ' ' || entry_info->date[4] == '0') { + entry_info->date[4] = HT_NON_BREAK_SPACE; + } + } + } + + TRACE_ENTRY("VM/CMS", entry_info); + return; +} /* parse_cms_dir_entry */ + +/* + * Given a line of LIST/NLST output in entry, return results and a file/dir + * name in entry_info struct + * + * If first is true, this is the first name in a directory. + */ +static EntryInfo *parse_dir_entry(char *entry, + BOOLEAN *first, + char **pspilledname) +{ + EntryInfo *entry_info; + int i; + int len; + BOOLEAN remove_size = FALSE; + char *cp; + + entry_info = typecalloc(EntryInfo); + + if (entry_info == NULL) + outofmem(__FILE__, "parse_dir_entry"); + + entry_info->display = TRUE; + + switch (server_type) { + case DLS_SERVER: + + /* + * Interpret and edit LIST output from a Unix server in "dls" format. + * This one must have claimed to be Unix in order to get here; if the + * first line looks fishy, we revert to Unix and hope that fits better + * (this recovery is untested). - kw + */ + + if (*first) { + len = (int) strlen(entry); + if (!len || entry[0] == ' ' || + (len >= 24 && entry[23] != ' ') || + (len < 24 && StrChr(entry, ' '))) { + server_type = UNIX_SERVER; + CTRACE((tfp, + "HTFTP: Falling back to treating as Unix server.\n")); + } else { + *first = FALSE; + } + } + + if (server_type == DLS_SERVER) { + /* if still unchanged... */ + parse_dls_line(entry, entry_info, pspilledname); + + if (isEmpty(entry_info->filename)) { + entry_info->display = FALSE; + return (entry_info); + } + if (!strcmp(entry_info->filename, "..") || + !strcmp(entry_info->filename, ".")) + entry_info->display = FALSE; + if (entry_info->type && *entry_info->type == '\0') { + FREE(entry_info->type); + return (entry_info); + } + /* + * Goto the bottom and get real type. + */ + break; + } + /* fall through if server_type changed for *first == TRUE ! */ + /* FALLTHRU */ + case UNIX_SERVER: + case PETER_LEWIS_SERVER: + case MACHTEN_SERVER: + case MSDOS_SERVER: + case WINDOWS_NT_SERVER: + case WINDOWS_2K_SERVER: + case APPLESHARE_SERVER: + case NETPRESENZ_SERVER: + /* + * Check for EPLF output (local times). + */ + if (*entry == '+') { + parse_eplf_line(entry, entry_info); + break; + } + + /* + * Interpret and edit LIST output from Unix server. + */ + len = (int) strlen(entry); + if (*first) { + /* don't gettext() this -- incoming text: */ + if (!strcmp(entry, "can not access directory .")) { + /* + * Don't reset *first, nothing real will follow. - KW + */ + entry_info->display = FALSE; + return (entry_info); + } + *first = FALSE; + if (!StrNCmp(entry, "total ", 6) || + strstr(entry, "not available") != NULL) { + entry_info->display = FALSE; + return (entry_info); + } else if (unsure_type) { + /* this isn't really a unix server! */ + server_type = GENERIC_SERVER; + entry_info->display = FALSE; + return (entry_info); + } + } + + /* + * Check first character of ls -l output. + */ + if (TOUPPER(entry[0]) == 'D') { + /* + * It's a directory. + */ + StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY); + remove_size = TRUE; /* size is not useful */ + } else if (entry[0] == 'l') { + /* + * It's a symbolic link, does the user care about knowing if it is + * symbolic? I think so since it might be a directory. + */ + StrAllocCopy(entry_info->type, ENTRY_IS_SYMBOLIC_LINK); + remove_size = TRUE; /* size is not useful */ + + /* + * Strip off " -> pathname". + */ + for (i = len - 1; (i > 3) && + (!isspace(UCH(entry[i])) || + (entry[i - 1] != '>') || + (entry[i - 2] != '-') || + (entry[i - 3] != ' ')); i--) ; /* null body */ + if (i > 3) { + entry[i - 3] = '\0'; + StrAllocCopy(entry_info->linkname, LYSkipBlanks(entry + i)); + } + } + /* link */ + parse_ls_line(entry, entry_info); + + if (!strcmp(entry_info->filename, "..") || + !strcmp(entry_info->filename, ".")) + entry_info->display = FALSE; + /* + * Goto the bottom and get real type. + */ + break; + + case VMS_SERVER: + /* + * Interpret and edit LIST output from VMS server and convert + * information lines to zero length. + */ + parse_vms_dir_entry(entry, entry_info); + + /* + * Get rid of any junk lines. + */ + if (!entry_info->display) + return (entry_info); + + /* + * Trim off VMS directory extensions. + */ + len = (int) strlen(entry_info->filename); + if ((len > 4) && !strcmp(&entry_info->filename[len - 4], ".dir")) { + entry_info->filename[len - 4] = '\0'; + StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY); + remove_size = TRUE; /* size is not useful */ + } + /* + * Goto the bottom and get real type. + */ + break; + + case MS_WINDOWS_SERVER: + /* + * Interpret and edit LIST output from MS_WINDOWS server and convert + * information lines to zero length. + */ + parse_ms_windows_dir_entry(entry, entry_info); + + /* + * Get rid of any junk lines. + */ + if (!entry_info->display) + return (entry_info); + if (entry_info->type && *entry_info->type == '\0') { + FREE(entry_info->type); + return (entry_info); + } + /* + * Goto the bottom and get real type. + */ + break; + +#ifdef NOTDEFINED + case WINDOWS_NT_SERVER: + /* + * Interpret and edit LIST output from MS_WINDOWS server and convert + * information lines to zero length. + */ + parse_windows_nt_dir_entry(entry, entry_info); + + /* + * Get rid of any junk lines. + */ + if (!entry_info->display) + return (entry_info); + if (entry_info->type && *entry_info->type == '\0') { + FREE(entry_info->type); + return (entry_info); + } + /* + * Goto the bottom and get real type. + */ + break; +#endif /* NOTDEFINED */ + + case CMS_SERVER: + { + /* + * Interpret and edit LIST output from VM/CMS server and convert + * any information lines to zero length. + */ + parse_cms_dir_entry(entry, entry_info); + + /* + * Get rid of any junk lines. + */ + if (!entry_info->display) + return (entry_info); + if (entry_info->type && *entry_info->type == '\0') { + FREE(entry_info->type); + return (entry_info); + } + /* + * Goto the bottom and get real type. + */ + break; + } + + case NCSA_SERVER: + case TCPC_SERVER: + /* + * Directories identified by trailing "/" characters. + */ + StrAllocCopy(entry_info->filename, entry); + len = (int) strlen(entry); + if (entry[len - 1] == '/') { + /* + * It's a dir, remove / and mark it as such. + */ + entry[len - 1] = '\0'; + StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY); + remove_size = TRUE; /* size is not useful */ + } + /* + * Goto the bottom and get real type. + */ + break; + + default: + /* + * We can't tell if it is a directory since we only did an NLST :( List + * bad file types anyways? NOT! + */ + StrAllocCopy(entry_info->filename, entry); + return (entry_info); /* mostly empty info */ + + } /* switch (server_type) */ + +#ifdef LONG_LIST + (void) remove_size; +#else + if (remove_size && entry_info->size) { + entry_info->size = 0; + } +#endif + + if (isEmpty(entry_info->filename)) { + entry_info->display = FALSE; + return (entry_info); + } + if (strlen(entry_info->filename) > 3) { + if (((cp = strrchr(entry_info->filename, '.')) != NULL && + 0 == strncasecomp(cp, ".me", 3)) && + (cp[3] == '\0' || cp[3] == ';')) { + /* + * Don't treat this as application/x-Troff-me if it's a Unix server + * but has the string "read.me", or if it's not a Unix server. - + * FM + */ + if ((server_type != UNIX_SERVER) || + (cp > (entry_info->filename + 3) && + 0 == strncasecomp((cp - 4), "read.me", 7))) { + StrAllocCopy(entry_info->type, STR_PLAINTEXT); + } + } + } + + /* + * Get real types eventually. + */ + if (!entry_info->type) { + const char *cp2; + HTFormat format; + HTAtom *encoding; /* @@ not used at all */ + + format = HTFileFormat(entry_info->filename, &encoding, &cp2); + + if (cp2 == NULL) { + if (!StrNCmp(HTAtom_name(format), "application", 11)) { + cp2 = HTAtom_name(format) + 12; + if (!StrNCmp(cp2, "x-", 2)) + cp2 += 2; + } else { + cp2 = HTAtom_name(format); + } + } + + StrAllocCopy(entry_info->type, cp2); + } + + return (entry_info); +} + +static void formatDate(char target[16], EntryInfo *entry) +{ + char temp[8], month[4]; + int i; + + /* + * Set up for sorting in reverse chronological order. - FM + */ + if (entry->date[9] == ':') { + strcpy(target, "9999"); + LYStrNCpy(temp, &entry->date[7], 5); + if (temp[0] == ' ') { + temp[0] = '0'; + } + } else { + LYStrNCpy(target, &entry->date[8], 4); + strcpy(temp, "00:00"); + } + LYStrNCpy(month, entry->date, 3); + for (i = 0; i < 12; i++) { + if (!strcasecomp(month, months[i])) { + break; + } + } + i++; + sprintf(month, "%02d", i % 100); + strcat(target, month); + StrNCat(target, &entry->date[4], 2); + if (target[6] == ' ' || target[6] == HT_NON_BREAK_SPACE) { + target[6] = '0'; + } + + /* If no year given, assume last year if it would otherwise be in the + * future by more than one day. The one day tolerance is to account for a + * possible timezone difference. - kw + */ + if (target[0] == '9' && atoi(target) > TheDate + 1) { + for (i = 0; i < 4; i++) { + target[i] = LastYear[i]; + } + } + strcat(target, temp); +} + +static int compare_EntryInfo_structs(EntryInfo *entry1, EntryInfo *entry2) +{ + int status; + char date1[16], date2[16]; + int result = strcmp(entry1->filename, entry2->filename); + + switch (HTfileSortMethod) { + case FILE_BY_SIZE: + /* both equal or both 0 */ + if (entry1->size > entry2->size) + result = 1; + else if (entry1->size < entry2->size) + result = -1; + break; + + case FILE_BY_TYPE: + if (entry1->type && entry2->type) { + status = strcasecomp(entry1->type, entry2->type); + if (status) + result = status; + } + break; + + case FILE_BY_DATE: + if (entry1->date && entry2->date && + strlen(entry1->date) == 12 && + strlen(entry2->date) == 12) { + /* + * Set the years and date, if we don't have them yet. + */ + if (!HaveYears) { + set_years_and_date(); + } + formatDate(date1, entry1); + formatDate(date2, entry2); + /* + * Do the comparison. - FM + */ + status = strcasecomp(date2, date1); + if (status) + result = status; + } + break; + + case FILE_BY_NAME: + default: + break; + } + return result; +} + +#ifdef LONG_LIST +static char *FormatStr(char **bufp, + char *start, + const char *value) +{ + char fmt[512]; + + if (*start) { + sprintf(fmt, "%%%.*ss", (int) sizeof(fmt) - 3, start); + HTSprintf(bufp, fmt, value); + } else if (*bufp && !(value && *value)) { + ; + } else if (value) { + StrAllocCat(*bufp, value); + } + return *bufp; +} + +static char *FormatSize(char **bufp, + char *start, + off_t value) +{ + char fmt[512]; + + if (*start) { + sprintf(fmt, "%%%.*s" PRI_off_t, + (int) sizeof(fmt) - DigitsOf(start) - 3, start); + + HTSprintf(bufp, fmt, value); + } else { + sprintf(fmt, "%" PRI_off_t, CAST_off_t (value)); + + StrAllocCat(*bufp, fmt); + } + return *bufp; +} + +static char *FormatNum(char **bufp, + char *start, + unsigned long value) +{ + char fmt[512]; + + if (*start) { + sprintf(fmt, "%%%.*sld", + (int) sizeof(fmt) - DigitsOf(start) - 3, start); + HTSprintf(bufp, fmt, value); + } else { + sprintf(fmt, "%lu", value); + StrAllocCat(*bufp, fmt); + } + return *bufp; +} + +static void FlushParse(HTStructured * target, char **buf) +{ + if (*buf && **buf) { + PUTS(*buf); + **buf = '\0'; + } +} + +static void LYListFmtParse(const char *fmtstr, + EntryInfo *data, + HTStructured * target, + char *tail) +{ + char c; + char *s; + char *end; + char *start; + char *str = NULL; + char *buf = NULL; + BOOL is_directory = (BOOL) (data->file_mode != 0 && + (TOUPPER(data->file_mode[0]) == 'D')); + BOOL is_symlinked = (BOOL) (data->file_mode != 0 && + (TOUPPER(data->file_mode[0]) == 'L')); + BOOL remove_size = (BOOL) (is_directory || is_symlinked); + + StrAllocCopy(str, fmtstr); + s = str; + end = str + strlen(str); + while (*s) { + start = s; + while (*s) { + if (*s == '%') { + if (*(s + 1) == '%') /* literal % */ + s++; + else + break; + } + s++; + } + /* s is positioned either at a % or at \0 */ + *s = '\0'; + if (s > start) { /* some literal chars. */ + StrAllocCat(buf, start); + } + if (s == end) + break; + start = ++s; + while (isdigit(UCH(*s)) || *s == '.' || *s == '-' || *s == ' ' || + *s == '#' || *s == '+' || *s == '\'') + s++; + c = *s; /* the format char. or \0 */ + *s = '\0'; + + switch (c) { + case '\0': + StrAllocCat(buf, start); + continue; + + case 'A': + case 'a': /* anchor */ + FlushParse(target, &buf); + HTDirEntry(target, tail, data->filename); + FormatStr(&buf, start, data->filename); + PUTS(buf); + END(HTML_A); + if (buf != 0) + *buf = '\0'; + if (c != 'A' && data->linkname != 0) { + PUTS(" -> "); + PUTS(data->linkname); + } + break; + + case 'T': /* MIME type */ + case 't': /* MIME type description */ + if (is_directory) { + if (c != 'T') { + FormatStr(&buf, start, ENTRY_IS_DIRECTORY); + } else { + FormatStr(&buf, start, ""); + } + } else if (is_symlinked) { + if (c != 'T') { + FormatStr(&buf, start, ENTRY_IS_SYMBOLIC_LINK); + } else { + FormatStr(&buf, start, ""); + } + } else { + const char *cp2; + HTFormat format; + + format = HTFileFormat(data->filename, NULL, &cp2); + + if (c != 'T') { + if (cp2 == NULL) { + if (!StrNCmp(HTAtom_name(format), + "application", 11)) { + cp2 = HTAtom_name(format) + 12; + if (!StrNCmp(cp2, "x-", 2)) + cp2 += 2; + } else { + cp2 = HTAtom_name(format); + } + } + FormatStr(&buf, start, cp2); + } else { + FormatStr(&buf, start, HTAtom_name(format)); + } + } + break; + + case 'd': /* date */ + if (data->date) { + FormatStr(&buf, start, data->date); + } else { + FormatStr(&buf, start, " * "); + } + break; + + case 's': /* size in bytes */ + FormatSize(&buf, start, data->size); + break; + + case 'K': /* size in Kilobytes but not for directories */ + if (remove_size) { + FormatStr(&buf, start, ""); + StrAllocCat(buf, " "); + break; + } + /* FALL THROUGH */ + case 'k': /* size in Kilobytes */ + /* FIXME - this is inconsistent with HTFile.c, but historical */ + if (data->size < 1024) { + FormatSize(&buf, start, data->size); + StrAllocCat(buf, " bytes"); + } else { + FormatSize(&buf, start, data->size / 1024); + StrAllocCat(buf, "Kb"); + } + break; + +#ifdef LONG_LIST + case 'p': /* unix-style permission bits */ + FormatStr(&buf, start, NonNull(data->file_mode)); + break; + + case 'o': /* owner */ + FormatStr(&buf, start, NonNull(data->file_user)); + break; + + case 'g': /* group */ + FormatStr(&buf, start, NonNull(data->file_group)); + break; + + case 'l': /* link count */ + FormatNum(&buf, start, data->file_links); + break; +#endif + + case '%': /* literal % with flags/width */ + FormatStr(&buf, start, "%"); + break; + + default: + fprintf(stderr, + "Unknown format character `%c' in list format\n", c); + break; + } + + s++; + } + if (buf) { + LYTrimTrailing(buf); + FlushParse(target, &buf); + FREE(buf); + } + PUTC('\n'); + FREE(str); +} +#endif /* LONG_LIST */ + +/* Read a directory into an hypertext object from the data socket + * -------------------------------------------------------------- + * + * On entry, + * anchor Parent anchor to link the this node to + * address Address of the directory + * On exit, + * returns HT_LOADED if OK + * <0 if error. + */ +static int read_directory(HTParentAnchor *parent, + const char *address, + HTFormat format_out, + HTStream *sink) +{ + int status; + BOOLEAN WasInterrupted = FALSE; + HTStructured *target = HTML_new(parent, format_out, sink); + char *filename = HTParse(address, "", PARSE_PATH + PARSE_PUNCTUATION); + EntryInfo *entry_info; + BOOLEAN first = TRUE; + char *lastpath = NULL; /* prefix for link, either "" (for root) or xxx */ + BOOL tildeIsTop = FALSE; + +#ifndef LONG_LIST + char string_buffer[64]; +#endif + + _HTProgress(gettext("Receiving FTP directory.")); + + /* + * Force the current Date and Year (TheDate, ThisYear, and LastYear) to be + * recalculated for each directory request. Otherwise we have a problem + * with long-running sessions assuming the wrong date for today. - kw + */ + HaveYears = FALSE; + /* + * Check whether we always want the home directory treated as Welcome. - + * FM + */ + if (server_type == VMS_SERVER) + tildeIsTop = TRUE; + + /* + * This should always come back FALSE, since the flag is set only for local + * directory listings if LONG_LIST was defined on compilation, but we could + * someday set up an equivalent listing for Unix ftp servers. - FM + */ + (void) HTDirTitles(target, parent, format_out, tildeIsTop); + + data_read_pointer = data_write_pointer = data_buffer; + + if (*filename == '\0') { /* Empty filename: use root. */ + StrAllocCopy(lastpath, "/"); + } else if (!strcmp(filename, "/")) { /* Root path. */ + StrAllocCopy(lastpath, "/foo/.."); + } else { + char *p = strrchr(filename, '/'); /* Find the lastslash. */ + char *cp; + + if (server_type == CMS_SERVER) { + StrAllocCopy(lastpath, filename); /* Use absolute path for CMS. */ + } else { + StrAllocCopy(lastpath, p + 1); /* Take slash off the beginning. */ + } + if ((cp = strrchr(lastpath, ';')) != NULL) { /* Trim type= param. */ + if (!strncasecomp((cp + 1), "type=", 5)) { + if (TOUPPER(*(cp + 6)) == 'D' || + TOUPPER(*(cp + 6)) == 'A' || + TOUPPER(*(cp + 6)) == 'I') + *cp = '\0'; + } + } + } + FREE(filename); + + { + HTBTree *bt = HTBTree_new((HTComparer) compare_EntryInfo_structs); + int ic; + HTChunk *chunk = HTChunkCreate(128); + int BytesReceived = 0; + int BytesReported = 0; + char NumBytes[64]; + char *spilledname = NULL; + + PUTC('\n'); /* prettier LJM */ + for (ic = 0; ic != EOF;) { /* For each entry in the directory */ + HTChunkClear(chunk); + + if (HTCheckForInterrupt()) { + CTRACE((tfp, + "read_directory: interrupted after %d bytes\n", + BytesReceived)); + WasInterrupted = TRUE; + if (BytesReceived) { + goto unload_btree; /* unload btree */ + } else { + ABORT_TARGET; + HTBTreeAndObject_free(bt); + FREE(spilledname); + HTChunkFree(chunk); + return HT_INTERRUPTED; + } + } + + /* read directory entry + */ + interrupted_in_next_data_char = FALSE; + for (;;) { /* Read in one line as filename */ + ic = NEXT_DATA_CHAR; + AgainForMultiNet: + if (interrupted_in_next_data_char) { + CTRACE((tfp, + "read_directory: interrupted_in_next_data_char after %d bytes\n", + BytesReceived)); + WasInterrupted = TRUE; + if (BytesReceived) { + goto unload_btree; /* unload btree */ + } else { + ABORT_TARGET; + HTBTreeAndObject_free(bt); + FREE(spilledname); + HTChunkFree(chunk); + return HT_INTERRUPTED; + } + } else if ((char) ic == CR || (char) ic == LF) { /* Terminator? */ + if (chunk->size != 0) { /* got some text */ + /* Deal with MultiNet's wrapping of long lines */ + if (server_type == VMS_SERVER) { + /* Deal with MultiNet's wrapping of long lines - F.M. */ + if (data_read_pointer < data_write_pointer && + *(data_read_pointer + 1) == ' ') + data_read_pointer++; + else if (data_read_pointer >= data_write_pointer) { + status = NETREAD(data_soc, data_buffer, + DATA_BUFFER_SIZE); + if (status == HT_INTERRUPTED) { + interrupted_in_next_data_char = 1; + goto AgainForMultiNet; + } + if (status <= 0) { + ic = EOF; + break; + } + data_write_pointer = data_buffer + status; + data_read_pointer = data_buffer; + if (*data_read_pointer == ' ') + data_read_pointer++; + else + break; + } else + break; + } else + break; /* finish getting one entry */ + } + } else if (ic == EOF) { + break; /* End of file */ + } else { + HTChunkPutc(chunk, UCH(ic)); + } + } + HTChunkTerminate(chunk); + + BytesReceived += chunk->size; + if (BytesReceived > BytesReported + 1024) { +#ifdef _WINDOWS + sprintf(NumBytes, gettext("Transferred %d bytes (%5d)"), + BytesReceived, ws_read_per_sec); +#else + sprintf(NumBytes, TRANSFERRED_X_BYTES, BytesReceived); +#endif + HTProgress(NumBytes); + BytesReported = BytesReceived; + } + + if (ic == EOF && chunk->size == 1) + /* 1 means empty: includes terminating 0 */ + break; + CTRACE((tfp, "HTFTP: Line in %s is %s\n", + lastpath, chunk->data)); + + entry_info = parse_dir_entry(chunk->data, &first, &spilledname); + if (entry_info->display) { + FREE(spilledname); + CTRACE((tfp, "Adding file to BTree: %s\n", + entry_info->filename)); + HTBTree_add(bt, entry_info); + } else { + free_entryinfo_struct_contents(entry_info); + FREE(entry_info); + } + + } /* next entry */ + + unload_btree: + + HTChunkFree(chunk); + FREE(spilledname); + + /* print out the handy help message if it exists :) */ + if (help_message_cache_non_empty()) { + START(HTML_PRE); + START(HTML_HR); + PUTC('\n'); + PUTS(help_message_cache_contents()); + init_help_message_cache(); /* to free memory */ + START(HTML_HR); + PUTC('\n'); + } else { + START(HTML_PRE); + PUTC('\n'); + } + + /* Run through tree printing out in order + */ + { +#ifndef LONG_LIST +#ifdef SH_EX /* 1997/10/18 (Sat) 14:14:28 */ + char *p, name_buff[256]; + int name_len, dot_len; + +#define FNAME_WIDTH 30 +#define FILE_GAP 1 + +#endif + int i; +#endif + HTBTElement *ele; + + for (ele = HTBTree_next(bt, NULL); + ele != NULL; + ele = HTBTree_next(bt, ele)) { + entry_info = (EntryInfo *) HTBTree_object(ele); + +#ifdef LONG_LIST + LYListFmtParse(ftp_format, + entry_info, + target, + lastpath); +#else + if (entry_info->date) { + PUTS(entry_info->date); + PUTS(" "); + } else { + PUTS(" * "); + } + + if (entry_info->type) { + for (i = 0; entry_info->type[i] != '\0' && i < 16; i++) + PUTC(entry_info->type[i]); + for (; i < 17; i++) + PUTC(' '); + } + /* start the anchor */ + HTDirEntry(target, lastpath, entry_info->filename); +#ifdef SH_EX /* 1997/10/18 (Sat) 16:00 */ + name_len = strlen(entry_info->filename); + + sprintf(name_buff, "%-*s", FNAME_WIDTH, entry_info->filename); + + if (name_len < FNAME_WIDTH) { + dot_len = FNAME_WIDTH - FILE_GAP - name_len; + if (dot_len > 0) { + p = name_buff + name_len + 1; + while (dot_len-- > 0) + *p++ = '.'; + } + } else { + name_buff[FNAME_WIDTH] = '\0'; + } + + PUTS(name_buff); +#else + PUTS(entry_info->filename); +#endif + END(HTML_A); + + if (entry_info->size) { +#ifdef SH_EX /* 1998/02/02 (Mon) 16:34:52 */ + if (entry_info->size < 1024) + sprintf(string_buffer, "%6ld bytes", + entry_info->size); + else + sprintf(string_buffer, "%6ld Kb", + entry_info->size / 1024); +#else + if (entry_info->size < 1024) + sprintf(string_buffer, " %lu bytes", + (unsigned long) entry_info->size); + else + sprintf(string_buffer, " %luKb", + (unsigned long) entry_info->size / 1024); +#endif + PUTS(string_buffer); + } else if (entry_info->linkname != 0) { + PUTS(" -> "); + PUTS(entry_info->linkname); + } + + PUTC('\n'); /* end of this entry */ +#endif + + free_entryinfo_struct_contents(entry_info); + } + } + END(HTML_PRE); + END(HTML_BODY); + FREE_TARGET; + HTBTreeAndObject_free(bt); + } + + FREE(lastpath); + + if (WasInterrupted || data_soc != -1) { /* should always be true */ + /* + * Without closing the data socket first, the response(NULL) later may + * hang. Some servers expect the client to fin/ack the close of the + * data connection before proceeding with the conversation on the + * control connection. - kw + */ + CTRACE((tfp, "HTFTP: Closing data socket %d\n", data_soc)); + status = NETCLOSE(data_soc); + if (status == -1) + HTInetStatus("close"); /* Comment only */ + data_soc = -1; + } + + if (WasInterrupted || HTCheckForInterrupt()) { + _HTProgress(TRANSFER_INTERRUPTED); + } + return HT_LOADED; +} + +/* + * Setup an FTP connection. + */ +static int setup_connection(const char *name, + HTParentAnchor *anchor) +{ + int retry; /* How many times tried? */ + int status = HT_NO_CONNECTION; + + CTRACE((tfp, "setup_connection(%s)\n", name)); + + /* set use_list to NOT since we don't know what kind of server + * this is yet. And set the type to GENERIC + */ + use_list = FALSE; + server_type = GENERIC_SERVER; + Broken_RETR = FALSE; + +#ifdef INET6 + Broken_EPSV = FALSE; +#endif + + for (retry = 0; retry < 2; retry++) { /* For timed out/broken connections */ + status = get_connection(name, anchor); + if (status < 0) { + break; + } + + if (!ftp_local_passive) { + status = get_listen_socket(); + if (status < 0) { + NETCLOSE(control->socket); + control->socket = -1; +#ifdef INET6 + if (have_socket) + (void) close_master_socket(); +#else + close_master_socket(); +#endif /* INET6 */ + /* HT_INTERRUPTED would fall through, if we could interrupt + somehow in the middle of it, which we currently can't. */ + break; + } +#ifdef REPEAT_PORT + /* Inform the server of the port number we will listen on + */ + status = response(port_command); + FREE(port_command); + if (status == HT_INTERRUPTED) { + CTRACE((tfp, "HTFTP: Interrupted in response (port_command)\n")); + _HTProgress(CONNECTION_INTERRUPTED); + NETCLOSE(control->socket); + control->socket = -1; + close_master_socket(); + status = HT_INTERRUPTED; + break; + } + if (status != 2) { /* Could have timed out */ + if (status < 0) + continue; /* try again - net error */ + status = -status; /* bad reply */ + break; + } + CTRACE((tfp, "HTFTP: Port defined.\n")); +#endif /* REPEAT_PORT */ + } else { /* Tell the server to be passive */ + char *command = NULL; + const char *p = "?"; + int h0, h1, h2, h3, p0, p1; /* Parts of reply */ + +#ifdef INET6 + char dst[LINE_LENGTH + 1]; +#endif + + data_soc = status; + +#ifdef INET6 + /* see RFC 2428 */ + if (Broken_EPSV) + status = 1; + else + status = send_cmd_1(p = "EPSV"); + if (status < 0) /* retry or Bad return */ + continue; + else if (status != 2) { + status = send_cmd_1(p = "PASV"); + if (status < 0) { /* retry or Bad return */ + continue; + } else if (status != 2) { + status = -status; /* bad reply */ + break; + } + } + + if (strcmp(p, "PASV") == 0) { + for (p = response_text; *p && *p != ','; p++) { + ; /* null body */ + } + + while (--p > response_text && '0' <= *p && *p <= '9') { + ; /* null body */ + } + status = sscanf(p + 1, "%d,%d,%d,%d,%d,%d", + &h0, &h1, &h2, &h3, &p0, &p1); + if (status < 4) { + fprintf(tfp, "HTFTP: PASV reply has no inet address!\n"); + status = HT_NO_CONNECTION; + break; + } + passive_port = (PortNumber) ((p0 << 8) + p1); + sprintf(dst, "%d.%d.%d.%d", h0, h1, h2, h3); + } else if (strcmp(p, "EPSV") == 0) { + char c0, c1, c2, c3; + LY_SOCKADDR ss; + LY_SOCKLEN sslen; + + /* + * EPSV bla (|||port|) + */ + for (p = response_text; *p && !isspace(UCH(*p)); p++) { + ; /* null body */ + } + for ( /*nothing */ ; + *p && *p != '('; + p++) { /*) */ + ; /* null body */ + } + status = sscanf(p, "(%c%c%c%d%c)", &c0, &c1, &c2, &p0, &c3); + if (status != 5) { + fprintf(tfp, "HTFTP: EPSV reply has invalid format!\n"); + status = HT_NO_CONNECTION; + break; + } + passive_port = (PortNumber) p0; + + sslen = (LY_SOCKLEN) sizeof(ss); + if (getpeername(control->socket, SOCKADDR_OF(ss), &sslen) < 0) { + fprintf(tfp, "HTFTP: getpeername(control) failed\n"); + status = HT_NO_CONNECTION; + break; + } + if (getnameinfo(SOCKADDR_OF(ss), + sslen, + dst, + (socklen_t) sizeof(dst), + NULL, 0, NI_NUMERICHOST)) { + fprintf(tfp, "HTFTP: getnameinfo failed\n"); + status = HT_NO_CONNECTION; + break; + } + } +#else + status = send_cmd_1("PASV"); + if (status != 2) { + if (status < 0) + continue; /* retry or Bad return */ + status = -status; /* bad reply */ + break; + } + for (p = response_text; *p && *p != ','; p++) { + ; /* null body */ + } + + while (--p > response_text && '0' <= *p && *p <= '9') { + ; /* null body */ + } + + status = sscanf(p + 1, "%d,%d,%d,%d,%d,%d", + &h0, &h1, &h2, &h3, &p0, &p1); + if (status < 4) { + fprintf(tfp, "HTFTP: PASV reply has no inet address!\n"); + status = HT_NO_CONNECTION; + break; + } + passive_port = (PortNumber) ((p0 << 8) + p1); +#endif /* INET6 */ + CTRACE((tfp, "HTFTP: Server is listening on port %d\n", + passive_port)); + + /* Open connection for data: */ + +#ifdef INET6 + HTSprintf0(&command, "%s//%s:%d/", STR_FTP_URL, dst, passive_port); +#else + HTSprintf0(&command, "%s//%d.%d.%d.%d:%d/", + STR_FTP_URL, h0, h1, h2, h3, passive_port); +#endif + status = HTDoConnect(command, "FTP data", passive_port, &data_soc); + FREE(command); + + if (status < 0) { + (void) HTInetStatus(gettext("connect for data")); + NETCLOSE(data_soc); + break; + } + + CTRACE((tfp, "FTP data connected, socket %d\n", data_soc)); + } + status = 0; + break; /* No more retries */ + + } /* for retries */ + CTRACE((tfp, "setup_connection returns %d\n", status)); + return status; +} + +/* Retrieve File from Server + * ------------------------- + * + * On entry, + * name WWW address of a file: document, including hostname + * On exit, + * returns Socket number for file if good. + * <0 if bad. + */ +int HTFTPLoad(const char *name, + HTParentAnchor *anchor, + HTFormat format_out, + HTStream *sink) +{ + BOOL isDirectory = NO; + HTAtom *encoding = NULL; + int status, final_status; + int outstanding = 1; /* outstanding control connection responses + + that we are willing to wait for, if we + get to the point of reading data - kw */ + HTFormat format; + + CTRACE((tfp, "HTFTPLoad(%s) %s connection\n", + name, + (ftp_local_passive + ? "passive" + : "normal"))); + + HTReadProgress((off_t) 0, (off_t) 0); + + status = setup_connection(name, anchor); + if (status < 0) + return status; /* Failed with this code */ + + /* Ask for the file: + */ + { + char *filename = HTParse(name, "", PARSE_PATH + PARSE_PUNCTUATION); + char *fname = filename; /* Save for subsequent free() */ + char *vmsname = NULL; + BOOL binary; + const char *type = NULL; + char *types = NULL; + char *cp; + + if (server_type == CMS_SERVER) { + /* If the unescaped path has a %2f, reject it as illegal. - FM */ + if (((cp = strstr(filename, "%2")) != NULL) && + TOUPPER(cp[2]) == 'F') { + FREE(fname); + init_help_message_cache(); /* to free memory */ + NETCLOSE(control->socket); + control->socket = -1; + CTRACE((tfp, + "HTFTP: Rejecting path due to illegal escaped slash.\n")); + return -1; + } + } + + if (!*filename) { + StrAllocCopy(filename, "/"); + type = "D"; + } else if ((type = types = strrchr(filename, ';')) != NULL) { + /* + * Check and trim the type= parameter. - FM + */ + if (!strncasecomp((type + 1), "type=", 5)) { + switch (TOUPPER(*(type + 6))) { + case 'D': + *types = '\0'; + type = "D"; + break; + case 'A': + *types = '\0'; + type = "A"; + break; + case 'I': + *types = '\0'; + type = "I"; + break; + default: + type = ""; + break; + } + if (!*filename) { + *filename = '/'; + *(filename + 1) = '\0'; + } + } + if (*type != '\0') { + CTRACE((tfp, "HTFTP: type=%s\n", type)); + } + } + HTUnEscape(filename); + CTRACE((tfp, "HTFTP: UnEscaped %s\n", filename)); + if (filename[1] == '~') { + /* + * Check if translation of HOME as tilde is supported, + * and adjust filename if so. - FM + */ + char *cp2 = NULL; + char *fn = NULL; + + if ((cp2 = StrChr((filename + 1), '/')) != NULL) { + *cp2 = '\0'; + } + status = send_cmd_1("PWD"); + if (status == 2 && response_text[5] == '/') { + status = send_cwd(filename + 1); + if (status == 2) { + StrAllocCopy(fn, (filename + 1)); + if (cp2) { + *cp2 = '/'; + if (fn[strlen(fn) - 1] != '/') { + StrAllocCat(fn, cp2); + } else { + StrAllocCat(fn, (cp2 + 1)); + } + cp2 = NULL; + } + FREE(fname); + fname = filename = fn; + } + } + if (cp2) { + *cp2 = '/'; + } + } + if (strlen(filename) > 3) { + char *cp2; + + if (((cp2 = strrchr(filename, '.')) != NULL && + 0 == strncasecomp(cp2, ".me", 3)) && + (cp2[3] == '\0' || cp2[3] == ';')) { + /* + * Don't treat this as application/x-Troff-me if it's a Unix + * server but has the string "read.me", or if it's not a Unix + * server. - FM + */ + if ((server_type != UNIX_SERVER) || + (cp2 > (filename + 3) && + 0 == strncasecomp((cp2 - 4), "read.me", 7))) { + *cp2 = '\0'; + format = HTFileFormat(filename, &encoding, NULL); + *cp2 = '.'; + } else { + format = HTFileFormat(filename, &encoding, NULL); + } + } else { + format = HTFileFormat(filename, &encoding, NULL); + } + } else { + format = HTFileFormat(filename, &encoding, NULL); + } + format = HTCharsetFormat(format, anchor, -1); + binary = (BOOL) (encoding != WWW_ENC_8BIT && + encoding != WWW_ENC_7BIT); + if (!binary && + /* + * Force binary if we're in source, download or dump mode and this is + * not a VM/CMS server, so we don't get CRLF instead of LF (or CR) for + * newlines in text files. Can't do this for VM/CMS or we'll get raw + * EBCDIC. - FM + */ + (format_out == WWW_SOURCE || + format_out == WWW_DOWNLOAD || + format_out == WWW_DUMP) && + (server_type != CMS_SERVER)) + binary = TRUE; + if (!binary && type && *type == 'I') { + /* + * Force binary if we had ;type=I - FM + */ + binary = TRUE; + } else if (binary && type && *type == 'A') { + /* + * Force ASCII if we had ;type=A - FM + */ + binary = FALSE; + } + if (binary != control->is_binary) { + /* + * Act on our setting if not already set. - FM + */ + const char *mode = binary ? "I" : "A"; + + status = send_cmd_2("TYPE", mode); + if (status != 2) { + init_help_message_cache(); /* to free memory */ + return ((status < 0) ? status : -status); + } + control->is_binary = binary; + } + switch (server_type) { + /* + * Handle what for Lynx are special case servers, e.g., for which + * we respect RFC 1738, or which have known conflicts in suffix + * mappings. - FM + */ + case VMS_SERVER: + { + char *cp1, *cp2; + BOOL included_device = FALSE; + BOOL found_tilde = FALSE; + + /* Accept only Unix-style filename */ + if (StrChr(filename, ':') != NULL || + StrChr(filename, '[') != NULL) { + FREE(fname); + init_help_message_cache(); /* to free memory */ + NETCLOSE(control->socket); + control->socket = -1; + CTRACE((tfp, + "HTFTP: Rejecting path due to non-Unix-style syntax.\n")); + return -1; + } + /* Handle any unescaped "/%2F" path */ + if (!StrNCmp(filename, "//", 2)) { + int i; + + included_device = TRUE; + for (i = 0; filename[(i + 1)]; i++) + filename[i] = filename[(i + 1)]; + filename[i] = '\0'; + CTRACE((tfp, "HTFTP: Trimmed '%s'\n", filename)); + cp = HTVMS_name("", filename); + CTRACE((tfp, "HTFTP: VMSized '%s'\n", cp)); + if ((cp1 = strrchr(cp, ']')) != NULL) { + strcpy(filename, ++cp1); + CTRACE((tfp, "HTFTP: Filename '%s'\n", filename)); + *cp1 = '\0'; + status = send_cwd(cp); + if (status != 2) { + char *dotslash = 0; + + if ((cp1 = StrChr(cp, '[')) != NULL) { + *cp1++ = '\0'; + status = send_cwd(cp); + if (status != 2) { + FREE(fname); + init_help_message_cache(); /* to free memory */ + NETCLOSE(control->socket); + control->socket = -1; + return ((status < 0) ? status : -status); + } + HTSprintf0(&dotslash, "[.%s", cp1); + status = send_cwd(dotslash); + FREE(dotslash); + if (status != 2) { + FREE(fname); + init_help_message_cache(); /* to free memory */ + NETCLOSE(control->socket); + control->socket = -1; + return ((status < 0) ? status : -status); + } + } else { + FREE(fname); + init_help_message_cache(); /* to free memory */ + NETCLOSE(control->socket); + control->socket = -1; + return ((status < 0) ? status : -status); + } + } + } else if ((cp1 = StrChr(cp, ':')) != NULL && + StrChr(cp, '[') == NULL && + StrChr(cp, ']') == NULL) { + cp1++; + if (*cp1 != '\0') { + int cplen = (int) (cp1 - cp); + + strcpy(filename, cp1); + CTRACE((tfp, "HTFTP: Filename '%s'\n", filename)); + HTSprintf0(&vmsname, "%.*s[%s]", cplen, cp, filename); + status = send_cwd(vmsname); + if (status != 2) { + HTSprintf(&vmsname, "%.*s[000000]", cplen, cp); + status = send_cwd(vmsname); + if (status != 2) { + HTSprintf(&vmsname, "%.*s", cplen, cp); + status = send_cwd(vmsname); + if (status != 2) { + FREE(fname); + init_help_message_cache(); + NETCLOSE(control->socket); + control->socket = -1; + return ((status < 0) ? status : -status); + } + } + } else { + HTSprintf0(&vmsname, "000000"); + filename = vmsname; + } + } + } else if (0 == strcmp(cp, (filename + 1))) { + status = send_cwd(cp); + if (status != 2) { + HTSprintf0(&vmsname, "%s:", cp); + status = send_cwd(vmsname); + if (status != 2) { + FREE(fname); + init_help_message_cache(); /* to free memory */ + NETCLOSE(control->socket); + control->socket = -1; + return ((status < 0) ? status : -status); + } + } + HTSprintf0(&vmsname, "000000"); + filename = vmsname; + } + } + /* Trim trailing slash if filename is not the top directory */ + if (strlen(filename) > 1 && filename[strlen(filename) - 1] == '/') + filename[strlen(filename) - 1] = '\0'; + +#ifdef MAINTAIN_CONNECTION /* Don't need this if always new connection - F.M. */ + if (!included_device) { + /* Get the current default VMS device:[directory] */ + status = send_cmd_1("PWD"); + if (status != 2) { + FREE(fname); + init_help_message_cache(); /* to free memory */ + NETCLOSE(control->socket); + control->socket = -1; + return ((status < 0) ? status : -status); + } + /* Go to the VMS account's top directory */ + if ((cp = StrChr(response_text, '[')) != NULL && + (cp1 = strrchr(response_text, ']')) != NULL) { + char *tmp = 0; + unsigned len = 4; + + StrAllocCopy(tmp, cp); + if ((cp2 = StrChr(cp, '.')) != NULL && cp2 < cp1) { + len += (cp2 - cp); + } else { + len += (cp1 - cp); + } + tmp[len] = 0; + StrAllocCat(tmp, "]"); + + status = send_cwd(tmp); + FREE(tmp); + + if (status != 2) { + FREE(fname); + init_help_message_cache(); /* to free memory */ + NETCLOSE(control->socket); + control->socket = -1; + return ((status < 0) ? status : -status); + } + } + } +#endif /* MAINTAIN_CONNECTION */ + + /* If we want the VMS account's top directory, list it now */ + if (!(strcmp(filename, "/~")) || + (included_device && 0 == strcmp(filename, "000000")) || + (strlen(filename) == 1 && *filename == '/')) { + isDirectory = YES; + status = send_cmd_1("LIST"); + FREE(fname); + if (status != 1) { + /* Action not started */ + init_help_message_cache(); /* to free memory */ + NETCLOSE(control->socket); + control->socket = -1; + return ((status < 0) ? status : -status); + } + /* Big goto! */ + goto listen; + } + /* Otherwise, go to appropriate directory and doctor filename */ + if (!StrNCmp(filename, "/~", 2)) { + filename += 2; + found_tilde = TRUE; + } + CTRACE((tfp, "check '%s' to translate x/y/ to [.x.y]\n", filename)); + if (!included_device && + (cp = StrChr(filename, '/')) != NULL && + (cp1 = strrchr(cp, '/')) != NULL && + (cp1 - cp) > 1) { + char *tmp = 0; + + HTSprintf0(&tmp, "[.%.*s]", (int) (cp1 - cp - 1), cp + 1); + + CTRACE((tfp, "change path '%s'\n", tmp)); + while ((cp2 = strrchr(tmp, '/')) != NULL) + *cp2 = '.'; + CTRACE((tfp, "...to path '%s'\n", tmp)); + + status = send_cwd(tmp); + FREE(tmp); + + if (status != 2) { + FREE(fname); + init_help_message_cache(); /* to free memory */ + NETCLOSE(control->socket); + control->socket = -1; + return ((status < 0) ? status : -status); + } + filename = cp1 + 1; + } else { + if (!included_device && !found_tilde) { + filename += 1; + } + } + break; + } + case CMS_SERVER: + { + /* + * If we want the CMS account's top directory, or a base SFS or + * anonymous directory path (i.e., without a slash), list it + * now. FM + */ + if ((strlen(filename) == 1 && *filename == '/') || + ((0 == strncasecomp((filename + 1), "vmsysu:", 7)) && + (cp = StrChr((filename + 1), '.')) != NULL && + StrChr(cp, '/') == NULL) || + (0 == strncasecomp(filename + 1, "anonymou.", 9) && + StrChr(filename + 1, '/') == NULL)) { + if (filename[1] != '\0') { + status = send_cwd(filename + 1); + if (status != 2) { + /* Action not started */ + init_help_message_cache(); /* to free memory */ + NETCLOSE(control->socket); + control->socket = -1; + return ((status < 0) ? status : -status); + } + } + isDirectory = YES; + if (use_list) + status = send_cmd_1("LIST"); + else + status = send_cmd_1("NLST"); + FREE(fname); + if (status != 1) { + /* Action not started */ + init_help_message_cache(); /* to free memory */ + NETCLOSE(control->socket); + control->socket = -1; + return ((status < 0) ? status : -status); + } + /* Big goto! */ + goto listen; + } + filename++; + + /* Otherwise, go to appropriate directory and adjust filename */ + while ((cp = StrChr(filename, '/')) != NULL) { + *cp++ = '\0'; + status = send_cwd(filename); + if (status == 2) { + if (*cp == '\0') { + isDirectory = YES; + if (use_list) + status = send_cmd_1("LIST"); + else + status = send_cmd_1("NLST"); + FREE(fname); + if (status != 1) { + /* Action not started */ + init_help_message_cache(); /* to free memory */ + NETCLOSE(control->socket); + control->socket = -1; + return ((status < 0) ? status : -status); + } + /* Clear any messages from the login directory */ + init_help_message_cache(); + /* Big goto! */ + goto listen; + } + filename = cp; + } + } + break; + } + default: + /* Shift for any unescaped "/%2F" path */ + if (!StrNCmp(filename, "//", 2)) + filename++; + break; + } + /* + * Act on a file or listing request, or try to figure out which we're + * dealing with if we don't know yet. - FM + */ + if (!(type) || (type && *type != 'D')) { + /* + * If we are retrieving a file we will (except for CMS) use + * binary mode, which lets us use the size command supported by + * ftp servers which implement RFC 3659. Knowing the size lets + * us in turn display ETA in the progress message -TD + */ + if (control->is_binary) { + int code; + + status = send_cmd_2("SIZE", filename); + if (status == 2) { +#if !defined(HAVE_LONG_LONG) && defined(GUESS_PRI_off_t) + long size; + + if (sscanf(response_text, "%d %ld", &code, &size) == 2) { + anchor->content_length = (off_t) size; + } +#else + off_t size; + if (sscanf(response_text, "%d %" SCN_off_t, &code, &size) + == 2) { + anchor->content_length = size; + } +#endif + } + } + status = send_cmd_2("RETR", filename); + if (status >= 5) { + int check; + + if (Broken_RETR) { + CTRACE((tfp, "{{reconnecting...\n")); + close_connection(control); + check = setup_connection(name, anchor); + CTRACE((tfp, "...done }}reconnecting\n")); + if (check < 0) + return check; + } + } + } else { + status = 5; /* Failed status set as flag. - FM */ + } + if (status != 1) { /* Failed : try to CWD to it */ + /* Clear any login messages if this isn't the login directory */ + if (strcmp(filename, "/")) + init_help_message_cache(); + + status = send_cwd(filename); + if (status == 2) { /* Succeeded : let's NAME LIST it */ + isDirectory = YES; + if (use_list) + status = send_cmd_1("LIST"); + else + status = send_cmd_1("NLST"); + } + } + FREE(fname); + FREE(vmsname); + if (status != 1) { + init_help_message_cache(); /* to free memory */ + NETCLOSE(control->socket); + control->socket = -1; + if (status < 0) + return status; + else + return -status; + } + } + + listen: + if (!ftp_local_passive) { + /* Wait for the connection */ + LY_SOCKADDR soc_A; + LY_SOCKLEN soc_addrlen = (LY_SOCKLEN) sizeof(soc_A); + +#ifdef SOCKS + if (socks_flag) + status = Raccept((int) master_socket, + SOCKADDR_OF(soc_A), + &soc_addrlen); + else +#endif /* SOCKS */ + status = accept((int) master_socket, + SOCKADDR_OF(soc_A), + &soc_addrlen); + if (status < 0) { + init_help_message_cache(); /* to free memory */ + return HTInetStatus("accept"); + } + CTRACE((tfp, "TCP: Accepted new socket %d\n", status)); + data_soc = status; + } + + if (isDirectory) { + if (server_type == UNIX_SERVER && !unsure_type && + !strcmp(response_text, + "150 Opening ASCII mode data connection for /bin/dl.\n")) { + CTRACE((tfp, "HTFTP: Treating as \"dls\" server.\n")); + server_type = DLS_SERVER; + } + final_status = read_directory(anchor, name, format_out, sink); + if (final_status > 0) { + if (server_type != CMS_SERVER) + if (outstanding-- > 0) { + status = response(NULL); + if (status < 0 || + (status == 2 && !StrNCmp(response_text, "221", 3))) + outstanding = 0; + } + } else { /* HT_INTERRUPTED */ + /* User may have pressed 'z' to give up because no + packets got through, so let's not make them wait + any longer - kw */ + outstanding = 0; + } + + if (data_soc != -1) { /* normally done in read_directory */ + CTRACE((tfp, "HTFTP: Closing data socket %d\n", data_soc)); + status = NETCLOSE(data_soc); + if (status == -1) + HTInetStatus("close"); /* Comment only */ + } + status = final_status; + } else { + int rv; + char *FileName = HTParse(name, "", PARSE_PATH + PARSE_PUNCTUATION); + + /* Clear any login messages */ + init_help_message_cache(); + + /* Fake a Content-Encoding for compressed files. - FM */ + HTUnEscape(FileName); + if (!IsUnityEnc(encoding)) { + /* + * We already know from the call to HTFileFormat above that this is + * a compressed file, no need to look at the filename again. - kw + */ + StrAllocCopy(anchor->content_type, format->name); + StrAllocCopy(anchor->content_encoding, HTAtom_name(encoding)); + format = HTAtom_for("www/compressed"); + + } else { + int rootlen; + CompressFileType cft = HTCompressFileType(FileName, "._-", &rootlen); + + if (cft != cftNone) { + FileName[rootlen] = '\0'; + format = HTFileFormat(FileName, &encoding, NULL); + format = HTCharsetFormat(format, anchor, -1); + StrAllocCopy(anchor->content_type, format->name); + format = HTAtom_for("www/compressed"); + } + + switch (cft) { + case cftCompress: + StrAllocCopy(anchor->content_encoding, "x-compress"); + break; + case cftGzip: + StrAllocCopy(anchor->content_encoding, "x-gzip"); + break; + case cftDeflate: + StrAllocCopy(anchor->content_encoding, "x-deflate"); + break; + case cftBzip2: + StrAllocCopy(anchor->content_encoding, "x-bzip2"); + break; + case cftBrotli: + StrAllocCopy(anchor->content_encoding, "x-brotli"); + break; + case cftNone: + break; + } + } + FREE(FileName); + + _HTProgress(gettext("Receiving FTP file.")); + rv = HTParseSocket(format, format_out, anchor, data_soc, sink); + + HTInitInput(control->socket); + /* Reset buffering to control connection DD 921208 */ + + if (rv < 0) { + if (rv == -2) /* weird error, don't expect much response */ + outstanding--; + else if (rv == HT_INTERRUPTED || rv == -1) + /* User may have pressed 'z' to give up because no + packets got through, so let's not make them wait + longer - kw */ + outstanding = 0; + CTRACE((tfp, "HTFTP: Closing data socket %d\n", data_soc)); + status = NETCLOSE(data_soc); + } else { + status = 2; /* data_soc already closed in HTCopy - kw */ + } + + if (status < 0 && rv != HT_INTERRUPTED && rv != -1) { + (void) HTInetStatus("close"); /* Comment only */ + } else { + if (rv != HT_LOADED && outstanding--) { + status = response(NULL); /* Pick up final reply */ + if (status != 2 && rv != HT_INTERRUPTED && rv != -1) { + data_soc = -1; /* invalidate it */ + init_help_message_cache(); /* to free memory */ + return HTLoadError(sink, 500, response_text); + } else if (status == 2 && !StrNCmp(response_text, "221", 3)) { + outstanding = 0; + } + } + } + final_status = HT_LOADED; + } + while (outstanding-- > 0 && + (status > 0)) { + status = response(NULL); + if (status == 2 && !StrNCmp(response_text, "221", 3)) + break; + } + data_soc = -1; /* invalidate it */ + CTRACE((tfp, "HTFTPLoad: normal end; ")); + if (control->socket < 0) { + CTRACE((tfp, "control socket is %d\n", control->socket)); + } else { + CTRACE((tfp, "closing control socket %d\n", control->socket)); + status = NETCLOSE(control->socket); + if (status == -1) + HTInetStatus("control connection close"); /* Comment only */ + } + control->socket = -1; + init_help_message_cache(); /* to free memory */ + /* returns HT_LOADED (always for file if we get here) or error */ + return final_status; +} /* open_file_read */ + +/* + * This function frees any user entered password, so that + * it must be entered again for a future request. - FM + */ +void HTClearFTPPassword(void) +{ + /* + * Need code to check cached documents from non-anonymous ftp accounts and + * do something to ensure that they no longer can be accessed without a new + * retrieval. - FM + */ + + /* + * Now free the current user entered password, if any. - FM + */ + FREE(user_entered_password); +} + +#endif /* ifndef DISABLE_FTP */ diff --git a/WWW/Library/Implementation/HTFTP.h b/WWW/Library/Implementation/HTFTP.h new file mode 100644 index 0000000..a903bbb --- /dev/null +++ b/WWW/Library/Implementation/HTFTP.h @@ -0,0 +1,70 @@ +/* FTP access module for libwww + FTP ACCESS FUNCTIONS + + This isn't really a valid protocol module -- it is lumped together with HTFile . That + could be changed easily. + + Author: Tim Berners-Lee. Public Domain. Please mail changes to timbl@info.cern.ch + + */ +#ifndef HTFTP_H +#define HTFTP_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif +#define FILE_BY_NAME 0 +#define FILE_BY_TYPE 1 +#define FILE_BY_SIZE 2 +#define FILE_BY_DATE 3 + extern int HTfileSortMethod; /* specifies the method of sorting */ + +/* PUBLIC HTVMS_name() + * CONVERTS WWW name into a VMS name + * ON ENTRY: + * nn Node Name (optional) + * fn WWW file name + * + * ON EXIT: + * returns vms file specification + * + * Bug: Returns pointer to static -- non-reentrant + */ + extern char *HTVMS_name(const char *nn, + const char *fn); + +/* + +Retrieve File from Server + + ON EXIT, + + returns Socket number for file if good.<0 if bad. + + */ + extern int HTFTPLoad(const char *name, + HTParentAnchor *anchor, + HTFormat format_out, + HTStream *sink); + +/* + * This function frees any user entered password, so that + * it must be entered again for a future request. - FM + */ + extern void HTClearFTPPassword(void); + +/* + +Return Host Name + + */ + extern const char *HTHostName(void); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/WWW/Library/Implementation/HTFWriter.h b/WWW/Library/Implementation/HTFWriter.h new file mode 100644 index 0000000..015ea15 --- /dev/null +++ b/WWW/Library/Implementation/HTFWriter.h @@ -0,0 +1,30 @@ +/* File Writer for libwww + C FILE WRITER + + It is useful to have both FWriter and Writer for environments in which fdopen() doesn't + exist for example. + + */ +#ifndef HTFWRITE_H +#define HTFWRITE_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + extern HTStream *HTFWriter_new(FILE *fp); + + extern HTStream *HTSaveAndExecute(HTPresentation *pres, + HTParentAnchor *anchor, /* Not used */ + HTStream *sink); + + extern HTStream *HTSaveLocally(HTPresentation *pres, + HTParentAnchor *anchor, /* Not used */ + HTStream *sink); + +#ifdef __cplusplus +} +#endif +#endif /* HTFWRITE_H */ diff --git a/WWW/Library/Implementation/HTFile.c b/WWW/Library/Implementation/HTFile.c new file mode 100644 index 0000000..9b6c54f --- /dev/null +++ b/WWW/Library/Implementation/HTFile.c @@ -0,0 +1,3506 @@ +/* + * $LynxId: HTFile.c,v 1.165 2024/05/28 00:35:41 tom Exp $ + * + * File Access HTFile.c + * =========== + * + * This is unix-specific code in general, with some VMS bits. + * These are routines for file access used by browsers. + * Development of this module for Unix DIRED_SUPPORT in Lynx + * regrettably has has been conducted in a manner with now + * creates a major impediment for hopes of adapting Lynx to + * a newer version of the library. + * + * History: + * Feb 91 Written Tim Berners-Lee CERN/CN + * Apr 91 vms-vms access included using DECnet syntax + * 26 Jun 92 (JFG) When running over DECnet, suppressed FTP. + * Fixed access bug for relative names on VMS. + * Sep 93 (MD) Access to VMS files allows sharing. + * 15 Nov 93 (MD) Moved HTVMSname to HTVMSUTILS.C + * 27 Dec 93 (FM) FTP now works with VMS hosts. + * FTP path must be Unix-style and cannot include + * the device or top directory. + */ + +#include + +#ifndef VMS +#if defined(DOSPATH) +#undef LONG_LIST +#define LONG_LIST /* Define this for long style unix listings (ls -l), + the actual style is configurable from lynx.cfg */ +#endif +/* #define NO_PARENT_DIR_REFERENCE */ +/* Define this for no parent links */ +#endif /* !VMS */ + +#if defined(DOSPATH) +#define HAVE_READDIR 1 +#define USE_DIRENT +#endif + +#if defined(USE_DOS_DRIVES) +#include +#endif + +#include /* Implemented here */ + +#ifdef VMS +#include +#endif /* VMS */ + +#if defined (USE_ZLIB) || defined (USE_BZLIB) +#include +#endif + +#define MULTI_SUFFIX ".multi" /* Extension for scanning formats */ + +#include +#include +#ifndef DECNET +#include +#endif /* !DECNET */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifdef USE_PRETTYSRC +# include +#endif + +#include + +typedef struct _HTSuffix { + char *suffix; + HTAtom *rep; + HTAtom *encoding; + char *desc; + float quality; +} HTSuffix; + +typedef struct { + struct stat file_info; + char sort_tags; + char file_name[1]; /* on the end of the struct, since its length varies */ +} DIRED; + +#ifndef NGROUPS +#ifdef NGROUPS_MAX +#define NGROUPS NGROUPS_MAX +#else +#define NGROUPS 32 +#endif /* NGROUPS_MAX */ +#endif /* NGROUPS */ + +#ifndef GETGROUPS_T +#define GETGROUPS_T int +#endif + +#include /* For directory object building */ + +#define PUTC(c) (*target->isa->put_character)(target, c) +#define PUTS(s) (*target->isa->put_string)(target, s) +#define START(e) (*target->isa->start_element)(target, e, 0, 0, -1, 0) +#define END(e) (*target->isa->end_element)(target, e, 0) +#define MAYBE_END(e) if (HTML_dtd.tags[e].contents != SGML_EMPTY) \ + (*target->isa->end_element)(target, e, 0) +#define FREE_TARGET (*target->isa->_free)(target) +#define ABORT_TARGET (*targetClass._abort)(target, NULL); + +struct _HTStructured { + const HTStructuredClass *isa; + /* ... */ +}; + +/* + * Controlling globals. + */ +int HTDirAccess = HT_DIR_OK; + +#ifdef DIRED_SUPPORT +int HTDirReadme = HT_DIR_README_NONE; + +#else +int HTDirReadme = HT_DIR_README_TOP; +#endif /* DIRED_SUPPORT */ + +static const char *HTMountRoot = "/Net/"; /* Where to find mounts */ + +#ifdef VMS +static const char *HTCacheRoot = "/WWW$SCRATCH"; /* Where to cache things */ + +#else +static const char *HTCacheRoot = "/tmp/W3_Cache_"; /* Where to cache things */ +#endif /* VMS */ + +static char s_no_suffix[] = "*"; +static char s_unknown_suffix[] = "*.*"; + +/* + * Suffix registration. + */ +static HTList *HTSuffixes = 0; + +static HTSuffix no_suffix = +{ + s_no_suffix, NULL, NULL, NULL, 1.0 +}; + +static HTSuffix unknown_suffix = +{ + s_unknown_suffix, NULL, NULL, NULL, 1.0 +}; + +/* To free up the suffixes at program exit. + * ---------------------------------------- + */ +#ifdef LY_FIND_LEAKS +static void free_suffixes(void); +#endif + +#define FindSearch(filename) strchr(filename, '?') + +#ifdef LONG_LIST +static char *FormatStr(char **bufp, + char *start, + const char *entry) +{ + char fmt[512]; + + if (*start) { + sprintf(fmt, "%%%.*ss", (int) sizeof(fmt) - 3, start); + HTSprintf0(bufp, fmt, entry); + } else if (*bufp && !(entry && *entry)) { + **bufp = '\0'; + } else if (entry) { + StrAllocCopy(*bufp, entry); + } + return *bufp; +} + +static char *FormatSize(char **bufp, + char *start, + off_t entry) +{ + char fmt[512]; + + if (*start) { + sprintf(fmt, "%%%.*s" PRI_off_t, + (int) sizeof(fmt) - DigitsOf(start) - 3, start); + + HTSprintf0(bufp, fmt, entry); + } else { + sprintf(fmt, "%" PRI_off_t, CAST_off_t (entry)); + + StrAllocCopy(*bufp, fmt); + } + return *bufp; +} + +static char *FormatNum(char **bufp, + char *start, + int entry) +{ + char fmt[512]; + + if (*start) { + sprintf(fmt, "%%%.*sd", (int) sizeof(fmt) - 3, start); + HTSprintf0(bufp, fmt, entry); + } else { + sprintf(fmt, "%d", entry); + StrAllocCopy(*bufp, fmt); + } + return *bufp; +} + +static void LYListFmtParse(const char *fmtstr, + DIRED * data, + char *file, + HTStructured * target, + char *tail) +{ + char c; + char *s; + char *end; + char *start; + char *str = NULL; + char *buf = NULL; + char tmp[LY_MAXPATH]; + char type; + +#ifndef NOUSERS + const char *name; +#endif + time_t now; + char *datestr; + +#ifdef S_IFLNK + int len; +#endif +#define SEC_PER_YEAR (60 * 60 * 24 * 365) + +#ifdef _WINDOWS /* 1998/01/06 (Tue) 21:20:53 */ + static const char *pbits[] = + { + "---", "--x", "-w-", "-wx", + "r--", "r-x", "rw-", "rwx", + 0}; + +#define PBIT(a, n, s) pbits[((a) >> (n)) & 0x7] + +#else + static const char *pbits[] = + {"---", "--x", "-w-", "-wx", + "r--", "r-x", "rw-", "rwx", 0}; + static const char *psbits[] = + {"--S", "--s", "-wS", "-ws", + "r-S", "r-s", "rwS", "rws", 0}; + +#define PBIT(a, n, s) (s) ? psbits[((a) >> (n)) & 0x7] : \ + pbits[((a) >> (n)) & 0x7] +#endif +#if defined(S_ISVTX) && !defined(_WINDOWS) + static const char *ptbits[] = + {"--T", "--t", "-wT", "-wt", + "r-T", "r-t", "rwT", "rwt", 0}; + +#define PTBIT(a, s) (s) ? ptbits[(a) & 0x7] : pbits[(a) & 0x7] +#else +#define PTBIT(a, s) PBIT(a, 0, 0) +#endif + + if (data->file_info.st_mode == 0) + fmtstr = " %a"; /* can't stat so just do anchor */ + + StrAllocCopy(str, fmtstr); + s = str; + end = str + strlen(str); + while (*s) { + start = s; + while (*s) { + if (*s == '%') { + if (*(s + 1) == '%') /* literal % */ + s++; + else + break; + } + s++; + } + /* s is positioned either at a % or at \0 */ + *s = '\0'; + if (s > start) { /* some literal chars. */ + PUTS(start); + } + if (s == end) + break; + start = ++s; + while (isdigit(UCH(*s)) || *s == '.' || *s == '-' || *s == ' ' || + *s == '#' || *s == '+' || *s == '\'') + s++; + c = *s; /* the format char. or \0 */ + *s = '\0'; + + switch (c) { + case '\0': + PUTS(start); + continue; + + case 'A': + case 'a': /* anchor */ + HTDirEntry(target, tail, data->file_name); + FormatStr(&buf, start, data->file_name); + PUTS(buf); + END(HTML_A); + *buf = '\0'; +#ifdef S_IFLNK + if (c != 'A' && S_ISLNK(data->file_info.st_mode) && + (len = (int) readlink(file, tmp, sizeof(tmp) - 1)) >= 0) { + PUTS(" -> "); + tmp[len] = '\0'; + PUTS(tmp); + } +#endif + break; + + case 'T': /* MIME type */ + case 't': /* MIME type description */ + if (S_ISDIR(data->file_info.st_mode)) { + if (c != 'T') { + FormatStr(&buf, start, ENTRY_IS_DIRECTORY); + } else { + FormatStr(&buf, start, ""); + } + } else { + const char *cp2; + HTFormat format; + + format = HTFileFormat(file, NULL, &cp2); + + if (c != 'T') { + if (cp2 == NULL) { + if (!StrNCmp(HTAtom_name(format), + "application", 11)) { + cp2 = HTAtom_name(format) + 12; + if (!StrNCmp(cp2, "x-", 2)) + cp2 += 2; + } else { + cp2 = HTAtom_name(format); + } + } + FormatStr(&buf, start, cp2); + } else { + FormatStr(&buf, start, HTAtom_name(format)); + } + } + break; + + case 'd': /* date */ + now = time(0); + datestr = ctime(&data->file_info.st_mtime); + if ((now - data->file_info.st_mtime) < SEC_PER_YEAR / 2) + /* + * MMM DD HH:MM + */ + sprintf(tmp, "%.12s", datestr + 4); + else + /* + * MMM DD YYYY + */ + sprintf(tmp, "%.7s %.4s ", datestr + 4, + datestr + 20); + FormatStr(&buf, start, tmp); + break; + + case 's': /* size in bytes */ + FormatSize(&buf, start, data->file_info.st_size); + break; + + case 'K': /* size in Kilobytes but not for directories */ + if (S_ISDIR(data->file_info.st_mode)) { + FormatStr(&buf, start, ""); + StrAllocCat(buf, " "); + break; + } + /* FALL THROUGH */ + case 'k': /* size in Kilobytes */ + FormatSize(&buf, start, ((data->file_info.st_size + 1023) / 1024)); + StrAllocCat(buf, "K"); + break; + + case 'p': /* unix-style permission bits */ + switch (data->file_info.st_mode & S_IFMT) { +#if defined(_MSC_VER) && defined(_S_IFIFO) + case _S_IFIFO: + type = 'p'; + break; +#else + case S_IFIFO: + type = 'p'; + break; +#endif + case S_IFCHR: + type = 'c'; + break; + case S_IFDIR: + type = 'd'; + break; + case S_IFREG: + type = '-'; + break; +#ifdef S_IFBLK + case S_IFBLK: + type = 'b'; + break; +#endif +#ifdef S_IFLNK + case S_IFLNK: + type = 'l'; + break; +#endif +#ifdef S_IFSOCK +# ifdef S_IFIFO /* some older machines (e.g., apollo) have a conflict */ +# if S_IFIFO != S_IFSOCK + case S_IFSOCK: + type = 's'; + break; +# endif +# else + case S_IFSOCK: + type = 's'; + break; +# endif +#endif /* S_IFSOCK */ + default: + type = '?'; + break; + } +#ifdef _WINDOWS + sprintf(tmp, "%c%s", type, + PBIT(data->file_info.st_mode, 6, data->file_info.st_mode & S_IRWXU)); +#else + sprintf(tmp, "%c%s%s%s", type, + PBIT(data->file_info.st_mode, 6, data->file_info.st_mode & S_ISUID), + PBIT(data->file_info.st_mode, 3, data->file_info.st_mode & S_ISGID), + PTBIT(data->file_info.st_mode, data->file_info.st_mode & S_ISVTX)); +#endif + FormatStr(&buf, start, tmp); + break; + + case 'o': /* owner */ +#ifndef NOUSERS + name = HTAA_UidToName((int) data->file_info.st_uid); + if (*name) { + FormatStr(&buf, start, name); + } else { + FormatNum(&buf, start, (int) data->file_info.st_uid); + } +#endif + break; + + case 'g': /* group */ +#ifndef NOUSERS + name = HTAA_GidToName((int) data->file_info.st_gid); + if (*name) { + FormatStr(&buf, start, name); + } else { + FormatNum(&buf, start, (int) data->file_info.st_gid); + } +#endif + break; + + case 'l': /* link count */ + FormatNum(&buf, start, (int) data->file_info.st_nlink); + break; + + case '%': /* literal % with flags/width */ + FormatStr(&buf, start, "%"); + break; + + default: + fprintf(stderr, + "Unknown format character `%c' in list format\n", c); + break; + } + if (buf) + PUTS(buf); + + s++; + } + FREE(buf); + PUTC('\n'); + FREE(str); +} +#endif /* LONG_LIST */ + +/* Define the representation associated with a file suffix. + * -------------------------------------------------------- + * + * Calling this with suffix set to "*" will set the default + * representation. + * Calling this with suffix set to "*.*" will set the default + * representation for unknown suffix files which contain a ".". + * + * The encoding parameter can give a trivial (8bit, 7bit, binary) + * or real (gzip, compress) encoding. + * + * If filename suffix is already defined with the same encoding + * its previous definition is overridden. + */ +void HTSetSuffix5(const char *suffix, + const char *representation, + const char *encoding, + const char *desc, + double value) +{ + HTSuffix *suff; + BOOL trivial_enc = (BOOL) IsUnityEncStr(encoding); + + if (strcmp(suffix, s_no_suffix) == 0) + suff = &no_suffix; + else if (strcmp(suffix, s_unknown_suffix) == 0) + suff = &unknown_suffix; + else { + HTList *cur = HTSuffixes; + + while (NULL != (suff = (HTSuffix *) HTList_nextObject(cur))) { + if (suff->suffix && 0 == strcmp(suff->suffix, suffix) && + ((trivial_enc && IsUnityEnc(suff->encoding)) || + (!trivial_enc && !IsUnityEnc(suff->encoding) && + strcmp(encoding, HTAtom_name(suff->encoding)) == 0))) + break; + } + if (!suff) { /* Not found -- create a new node */ + suff = typecalloc(HTSuffix); + if (suff == NULL) + outofmem(__FILE__, "HTSetSuffix"); + + if (!HTSuffixes) { + HTSuffixes = HTList_new(); +#ifdef LY_FIND_LEAKS + atexit(free_suffixes); +#endif + } + + HTList_addObject(HTSuffixes, suff); + + StrAllocCopy(suff->suffix, suffix); + } + } + + if (representation) + suff->rep = HTAtom_for(representation); + + /* + * Memory leak fixed. + * 05-28-94 Lynx 2-3-1 Garrett Arch Blythe + * Invariant code removed. + */ + suff->encoding = HTAtom_for(encoding); + + StrAllocCopy(suff->desc, desc); + + suff->quality = (float) value; +} + +#ifdef LY_FIND_LEAKS +/* + * Purpose: Free all added suffixes. + * Arguments: void + * Return Value: void + * Remarks/Portability/Dependencies/Restrictions: + * To be used at program exit. + * Revision History: + * 05-28-94 created Lynx 2-3-1 Garrett Arch Blythe + */ +static void free_suffixes(void) +{ + HTSuffix *suff = NULL; + + /* + * Loop through all suffixes. + */ + while (!HTList_isEmpty(HTSuffixes)) { + /* + * Free off each item and its members if need be. + */ + suff = (HTSuffix *) HTList_removeLastObject(HTSuffixes); + FREE(suff->suffix); + FREE(suff->desc); + FREE(suff); + } + /* + * Free off the list itself. + */ + HTList_delete(HTSuffixes); + HTSuffixes = NULL; +} +#endif /* LY_FIND_LEAKS */ + +/* Make the cache file name for a W3 document. + * ------------------------------------------- + * Make up a suitable name for saving the node in + * + * E.g. /tmp/WWW_Cache_news/1234@cernvax.cern.ch + * /tmp/WWW_Cache_http/crnvmc/FIND/xx.xxx.xx + * + * On exit: + * Returns a malloc'ed string which must be freed by the caller. + */ +char *HTCacheFileName(const char *name) +{ + char *acc_method = HTParse(name, "", PARSE_ACCESS); + char *host = HTParse(name, "", PARSE_HOST); + char *path = HTParse(name, "", PARSE_PATH + PARSE_PUNCTUATION); + char *result = NULL; + + HTSprintf0(&result, "%s/WWW/%s/%s%s", HTCacheRoot, acc_method, host, path); + + FREE(path); + FREE(acc_method); + FREE(host); + return result; +} + +/* Open a file for write, creating the path. + * ----------------------------------------- + */ +#ifdef NOT_IMPLEMENTED +static int HTCreatePath(const char *path) +{ + return -1; +} +#endif /* NOT_IMPLEMENTED */ + +/* Convert filename from URL-path syntax to local path format + * ---------------------------------------------------------- + * Input name is assumed to be the URL-path of a local file + * URL, i.e. what comes after the "file://localhost". + * '#'-fragments to be treated as such must already be stripped. + * If expand_all is FALSE, unescape only escaped '/'. - kw + * + * On exit: + * Returns a malloc'ed string which must be freed by the caller. + */ +char *HTURLPath_toFile(const char *name, + int expand_all, + int is_remote GCC_UNUSED) +{ + char *path = NULL; + char *result = NULL; + + StrAllocCopy(path, name); + if (expand_all) + HTUnEscape(path); /* Interpret all % signs */ + else + HTUnEscapeSome(path, "/"); /* Interpret % signs for path delims */ + + CTRACE((tfp, "URLPath `%s' means path `%s'\n", name, path)); +#if defined(USE_DOS_DRIVES) + StrAllocCopy(result, is_remote ? path : HTDOS_name(path)); +#else + StrAllocCopy(result, path); +#endif + + FREE(path); + + return result; +} +/* Convert filenames between local and WWW formats. + * ------------------------------------------------ + * Make up a suitable name for saving the node in + * + * E.g. $(HOME)/WWW/news/1234@cernvax.cern.ch + * $(HOME)/WWW/http/crnvmc/FIND/xx.xxx.xx + * + * On exit: + * Returns a malloc'ed string which must be freed by the caller. + */ +/* NOTE: Don't use this function if you know that the input is a URL path + rather than a full URL, use HTURLPath_toFile instead. Otherwise + this function will return the wrong thing for some unusual + paths (like ones containing "//", possibly escaped). - kw +*/ +char *HTnameOfFile_WWW(const char *name, + int WWW_prefix, + int expand_all) +{ + char *acc_method = HTParse(name, "", PARSE_ACCESS); + char *host = HTParse(name, "", PARSE_HOST); + char *path = HTParse(name, "", PARSE_PATH + PARSE_PUNCTUATION); + const char *home; + char *result = NULL; + + if (expand_all) { + HTUnEscape(path); /* Interpret all % signs */ + } else + HTUnEscapeSome(path, "/"); /* Interpret % signs for path delims */ + + if (0 == strcmp(acc_method, "file") /* local file */ + ||!*acc_method) { /* implicitly local? */ + if ((0 == strcasecomp(host, HTHostName())) || + (0 == strcasecomp(host, "localhost")) || !*host) { + CTRACE((tfp, "Node `%s' means path `%s'\n", name, path)); + StrAllocCopy(result, HTSYS_name(path)); + } else if (WWW_prefix) { + HTSprintf0(&result, "%s%s%s", "/Net/", host, path); + CTRACE((tfp, "Node `%s' means file `%s'\n", name, result)); + } else { + StrAllocCopy(result, path); + } + } else if (WWW_prefix) { /* other access */ +#ifdef VMS + if ((home = LYGetEnv("HOME")) == NULL) + home = HTCacheRoot; + else + home = HTVMS_wwwName(home); +#else +#if defined(_WINDOWS) /* 1997/10/16 (Thu) 20:42:51 */ + home = Home_Dir(); +#else + home = LYGetEnv("HOME"); +#endif + if (home == NULL) + home = "/tmp"; +#endif /* VMS */ + HTSprintf0(&result, "%s/WWW/%s/%s%s", home, acc_method, host, path); + } else { + StrAllocCopy(result, path); + } + + FREE(host); + FREE(path); + FREE(acc_method); + + CTRACE((tfp, "HTnameOfFile_WWW(%s,%d,%d) = %s\n", + name, WWW_prefix, expand_all, result)); + + return result; +} + +/* Make a WWW name from a full local path name. + * -------------------------------------------- + * + * Bugs: + * At present, only the names of two network root nodes are hand-coded + * in and valid for the NeXT only. This should be configurable in + * the general case. + */ +char *WWW_nameOfFile(const char *name) +{ + char *result = NULL; + +#ifdef NeXT + if (0 == StrNCmp("/private/Net/", name, 13)) { + HTSprintf0(&result, "%s//%s", STR_FILE_URL, name + 13); + } else +#endif /* NeXT */ + if (0 == StrNCmp(HTMountRoot, name, 5)) { + HTSprintf0(&result, "%s//%s", STR_FILE_URL, name + 5); + } else { + HTSprintf0(&result, "%s//%s%s", STR_FILE_URL, HTHostName(), name); + } + CTRACE((tfp, "File `%s'\n\tmeans node `%s'\n", name, result)); + return result; +} + +/* Determine a suitable suffix, given the representation. + * ------------------------------------------------------ + * + * On entry, + * rep is the atomized MIME style representation + * enc is an encoding, trivial (8bit, binary, etc.) or gzip etc. + * + * On exit: + * Returns a pointer to a suitable suffix string if one has been + * found, else "". + */ +const char *HTFileSuffix(HTAtom *rep, + const char *enc) +{ + HTSuffix *suff; + +#ifdef FNAMES_8_3 + HTSuffix *first_found = NULL; +#endif + BOOL trivial_enc; + int n; + int i; + +#define NO_INIT /* don't init anymore since I do it in Lynx at startup */ +#ifndef NO_INIT + if (!HTSuffixes) + HTFileInit(); +#endif /* !NO_INIT */ + + trivial_enc = (BOOL) IsUnityEncStr(enc); + n = HTList_count(HTSuffixes); + for (i = 0; i < n; i++) { + suff = (HTSuffix *) HTList_objectAt(HTSuffixes, i); + if (suff->rep == rep && +#if defined(VMS) || defined(FNAMES_8_3) + /* Don't return a suffix whose first char is a dot, and which + has more dots or asterisks after that, for + these systems - kw */ + (!suff->suffix || !suff->suffix[0] || suff->suffix[0] != '.' || + (StrChr(suff->suffix + 1, '.') == NULL && + StrChr(suff->suffix + 1, '*') == NULL)) && +#endif + ((trivial_enc && IsUnityEnc(suff->encoding)) || + (!trivial_enc && !IsUnityEnc(suff->encoding) && + strcmp(enc, HTAtom_name(suff->encoding)) == 0))) { +#ifdef FNAMES_8_3 + if (suff->suffix && (strlen(suff->suffix) <= 4)) { + /* + * If length of suffix (including dot) is 4 or smaller, return + * this one even if we found a longer one earlier - kw + */ + return suff->suffix; + } else if (!first_found) { + first_found = suff; /* remember this one */ + } +#else + return suff->suffix; /* OK -- found */ +#endif + } + } +#ifdef FNAMES_8_3 + if (first_found) + return first_found->suffix; +#endif + return ""; /* Dunno */ +} + +/* + * Trim version from VMS filenames to avoid confusing comparisons. + */ +#ifdef VMS +static const char *VMS_trim_version(const char *filename) +{ + const char *result = filename; + const char *version = StrChr(filename, ';'); + + if (version != 0) { + static char *stripped; + + StrAllocCopy(stripped, filename); + stripped[version - filename] = '\0'; + result = (const char *) stripped; + } + return result; +} +#define VMS_DEL_VERSION(name) name = VMS_trim_version(name) +#else +#define VMS_DEL_VERSION(name) /* nothing */ +#endif + +/* Determine file format from file name. + * ------------------------------------- + * + * This version will return the representation and also set + * a variable for the encoding. + * + * Encoding may be a unity encoding (binary, 8bit, etc.) or + * a content-coding like gzip, compress. + * + * It will handle for example x.txt, x.txt,Z, x.Z + */ +HTFormat HTFileFormat(const char *filename, + HTAtom **pencoding, + const char **pdesc) +{ + HTSuffix *suff; + int n; + int i; + int lf; + char *search; + + VMS_DEL_VERSION(filename); + + if ((search = FindSearch(filename)) != 0) { + char *newname = NULL; + HTFormat result; + + StrAllocCopy(newname, filename); + newname[((const char *) search) - filename] = '\0'; + result = HTFileFormat(newname, pencoding, pdesc); + free(newname); + return result; + } + + if (pencoding) + *pencoding = NULL; + if (pdesc) + *pdesc = NULL; + if (LYforce_HTML_mode) { + if (pencoding) + *pencoding = WWW_ENC_8BIT; + return WWW_HTML; + } +#ifndef NO_INIT + if (!HTSuffixes) + HTFileInit(); +#endif /* !NO_INIT */ + lf = (int) strlen(filename); + n = HTList_count(HTSuffixes); + for (i = 0; i < n; i++) { + int ls; + + suff = (HTSuffix *) HTList_objectAt(HTSuffixes, i); + ls = (int) strlen(suff->suffix); + if ((ls <= lf) && 0 == strcasecomp(suff->suffix, filename + lf - ls)) { + int j; + + if (pencoding) + *pencoding = suff->encoding; + if (pdesc) + *pdesc = suff->desc; + if (suff->rep) { + return suff->rep; /* OK -- found */ + } + for (j = 0; j < n; j++) { /* Got encoding, need representation */ + int ls2; + + suff = (HTSuffix *) HTList_objectAt(HTSuffixes, j); + ls2 = (int) strlen(suff->suffix); + if ((ls + ls2 <= lf) && + !strncasecomp(suff->suffix, + filename + lf - ls - ls2, ls2)) { + if (suff->rep) { + if (pdesc && !(*pdesc)) + *pdesc = suff->desc; + if (pencoding && IsUnityEnc(*pencoding) && + *pencoding != WWW_ENC_7BIT && + !IsUnityEnc(suff->encoding)) + *pencoding = suff->encoding; + return suff->rep; + } + } + } + + } + } + + /* defaults tree */ + + suff = (StrChr(filename, '.') + ? (unknown_suffix.rep + ? &unknown_suffix + : &no_suffix) + : &no_suffix); + + /* + * Set default encoding unless found with suffix already. + */ + if (pencoding && !*pencoding) { + *pencoding = (suff->encoding + ? suff->encoding + : HTAtom_for("binary")); + } + return suff->rep ? suff->rep : WWW_BINARY; +} + +/* Revise the file format in relation to the Lynx charset. - FM + * ------------------------------------------------------- + * + * This checks the format associated with an anchor for + * an extended MIME Content-Type, and if a charset is + * indicated, sets Lynx up for proper handling in relation + * to the currently selected character set. - FM + */ +HTFormat HTCharsetFormat(HTFormat format, + HTParentAnchor *anchor, + int default_LYhndl) +{ + char *cp = NULL, *cp1, *cp2, *cp3 = NULL, *cp4; + BOOL chartrans_ok = FALSE; + int chndl = -1; + const char *format_name = format->name; + + FREE(anchor->charset); + if (format_name == 0) + format_name = ""; + StrAllocCopy(cp, format_name); + LYLowerCase(cp); + if (((cp1 = StrChr(cp, ';')) != NULL) && + (cp2 = strstr(cp1, "charset")) != NULL) { + CTRACE((tfp, "HTCharsetFormat: Extended MIME Content-Type is %s\n", + format_name)); + cp2 += 7; + while (*cp2 == ' ' || *cp2 == '=') + cp2++; + StrAllocCopy(cp3, cp2); /* copy to mutilate more */ + for (cp4 = cp3; (*cp4 != '\0' && *cp4 != '"' && + *cp4 != ';' && *cp4 != ':' && + !WHITE(*cp4)); cp4++) { + ; /* do nothing */ + } + *cp4 = '\0'; + cp4 = cp3; + chndl = UCGetLYhndl_byMIME(cp3); + if (UCCanTranslateFromTo(chndl, current_char_set)) { + chartrans_ok = YES; + *cp1 = '\0'; + format = HTAtom_for(cp); + StrAllocCopy(anchor->charset, cp4); + HTAnchor_setUCInfoStage(anchor, chndl, + UCT_STAGE_MIME, + UCT_SETBY_MIME); + } else if (chndl < 0) { + /* + * Got something but we don't recognize it. + */ + chndl = UCLYhndl_for_unrec; + if (chndl < 0) + /* + * UCLYhndl_for_unrec not defined :-( fallback to + * UCLYhndl_for_unspec which always valid. + */ + chndl = UCLYhndl_for_unspec; /* always >= 0 */ + if (UCCanTranslateFromTo(chndl, current_char_set)) { + chartrans_ok = YES; + HTAnchor_setUCInfoStage(anchor, chndl, + UCT_STAGE_MIME, + UCT_SETBY_DEFAULT); + } + } + if (chartrans_ok) { + LYUCcharset *p_in = HTAnchor_getUCInfoStage(anchor, + UCT_STAGE_MIME); + LYUCcharset *p_out = HTAnchor_setUCInfoStage(anchor, + current_char_set, + UCT_STAGE_HTEXT, + UCT_SETBY_DEFAULT); + + if (!p_out) { + /* + * Try again. + */ + p_out = HTAnchor_getUCInfoStage(anchor, UCT_STAGE_HTEXT); + } + if (!strcmp(p_in->MIMEname, "x-transparent")) { + HTPassEightBitRaw = TRUE; + HTAnchor_setUCInfoStage(anchor, + HTAnchor_getUCLYhndl(anchor, + UCT_STAGE_HTEXT), + UCT_STAGE_MIME, + UCT_SETBY_DEFAULT); + } + if (!strcmp(p_out->MIMEname, "x-transparent")) { + HTPassEightBitRaw = TRUE; + HTAnchor_setUCInfoStage(anchor, + HTAnchor_getUCLYhndl(anchor, + UCT_STAGE_MIME), + UCT_STAGE_HTEXT, + UCT_SETBY_DEFAULT); + } + if (p_in->enc != UCT_ENC_CJK) { + HTCJK = NOCJK; + if (!(p_in->codepoints & + UCT_CP_SUBSETOF_LAT1) && + chndl == current_char_set) { + HTPassEightBitRaw = TRUE; + } + } else if (p_out->enc == UCT_ENC_CJK) { + Set_HTCJK(p_in->MIMEname, p_out->MIMEname); + } + } else { + /* + * Cannot translate. If according to some heuristic the given + * charset and the current display character both are likely to be + * like ISO-8859 in structure, pretend we have some kind of match. + */ + BOOL given_is_8859 = (BOOL) (!StrNCmp(cp4, "iso-8859-", 9) && + isdigit(UCH(cp4[9]))); + BOOL given_is_8859like = (BOOL) (given_is_8859 || + !StrNCmp(cp4, "windows-", 8) || + !StrNCmp(cp4, "cp12", 4) || + !StrNCmp(cp4, "cp-12", 5)); + BOOL given_and_display_8859like = (BOOL) (given_is_8859like && + (strstr(LYchar_set_names[current_char_set], + "ISO-8859") || + strstr(LYchar_set_names[current_char_set], + "windows-"))); + + if (given_and_display_8859like) { + *cp1 = '\0'; + format = HTAtom_for(cp); + } + if (given_is_8859) { + cp1 = &cp4[10]; + while (*cp1 && + isdigit(UCH(*cp1))) + cp1++; + *cp1 = '\0'; + } + if (given_and_display_8859like) { + StrAllocCopy(anchor->charset, cp4); + HTPassEightBitRaw = TRUE; + } + HTAlert(*cp4 ? cp4 : anchor->charset); + } + FREE(cp3); + } else if (cp1 != NULL) { + /* + * No charset parameter is present. Ignore all other parameters, as we + * do when charset is present. - FM + */ + *cp1 = '\0'; + format = HTAtom_for(cp); + } + FREE(cp); + + /* + * Set up defaults, if needed. - FM + */ + if (!chartrans_ok && !anchor->charset && default_LYhndl >= 0) { + HTAnchor_setUCInfoStage(anchor, default_LYhndl, + UCT_STAGE_MIME, + UCT_SETBY_DEFAULT); + } + HTAnchor_copyUCInfoStage(anchor, + UCT_STAGE_PARSER, + UCT_STAGE_MIME, + -1); + + return format; +} + +/* Get various pieces of meta info from file name. + * ----------------------------------------------- + * + * LYGetFileInfo fills in information that can be determined without + * an actual (new) access to the filesystem, based on current suffix + * and character set configuration. If the file has been loaded and + * parsed before (with the same URL generated here!) and the anchor + * is still around, some results may be influenced by that (in + * particular, charset info from a META tag - this is not actually + * tested!). + * The caller should not keep pointers to the returned objects around + * for too long, the valid lifetimes vary. In particular, the returned + * charset string should be copied if necessary. If return of the + * file_anchor is requested, that one can be used to retrieve + * additional bits of info that are stored in the anchor object and + * are not covered here; as usual, don't keep pointers to the + * file_anchor longer than necessary since the object may disappear + * through HTuncache_current_document or at the next document load. + * - kw + */ +void LYGetFileInfo(const char *filename, + HTParentAnchor **pfile_anchor, + HTFormat *pformat, + HTAtom **pencoding, + const char **pdesc, + const char **pcharset, + int *pfile_cs) +{ + char *Afn; + char *Aname = NULL; + HTFormat format; + HTAtom *myEnc = NULL; + HTParentAnchor *file_anchor; + const char *file_csname; + int file_cs; + + /* + * Convert filename to URL. Note that it is always supposed to be a + * filename, not maybe-filename-maybe-URL, so we don't use + * LYFillLocalFileURL and LYEnsureAbsoluteURL. - kw + */ + Afn = HTEscape(filename, URL_PATH); + LYLocalFileToURL(&Aname, Afn); + file_anchor = HTAnchor_findSimpleAddress(Aname); + + format = HTFileFormat(filename, &myEnc, pdesc); + format = HTCharsetFormat(format, file_anchor, UCLYhndl_HTFile_for_unspec); + file_cs = HTAnchor_getUCLYhndl(file_anchor, UCT_STAGE_MIME); + file_csname = file_anchor->charset; + if (!file_csname) { + if (file_cs >= 0) + file_csname = LYCharSet_UC[file_cs].MIMEname; + else + file_csname = "display character set"; + } + CTRACE((tfp, "GetFileInfo: '%s' is a%s %s %s file, charset=%s (%d).\n", + filename, + ((myEnc && *HTAtom_name(myEnc) == '8') ? "n" : myEnc ? "" : + *HTAtom_name(format) == 'a' ? "n" : ""), + myEnc ? HTAtom_name(myEnc) : "", + HTAtom_name(format), + file_csname, + file_cs)); + FREE(Afn); + FREE(Aname); + if (pfile_anchor) + *pfile_anchor = file_anchor; + if (pformat) + *pformat = format; + if (pencoding) + *pencoding = myEnc; + if (pcharset) + *pcharset = file_csname; + if (pfile_cs) + *pfile_cs = file_cs; +} + +/* Determine value from file name. + * ------------------------------- + * + */ +float HTFileValue(const char *filename) +{ + HTSuffix *suff; + int n; + int i; + int lf = (int) strlen(filename); + +#ifndef NO_INIT + if (!HTSuffixes) + HTFileInit(); +#endif /* !NO_INIT */ + n = HTList_count(HTSuffixes); + for (i = 0; i < n; i++) { + int ls; + + suff = (HTSuffix *) HTList_objectAt(HTSuffixes, i); + ls = (int) strlen(suff->suffix); + if ((ls <= lf) && 0 == strcmp(suff->suffix, filename + lf - ls)) { + CTRACE((tfp, "File: Value of %s is %.3f\n", + filename, suff->quality)); + return suff->quality; /* OK -- found */ + } + } + return (float) 0.3; /* Dunno! */ +} + +/* + * Determine compression type from file name, by looking at its suffix. + * Sets as side-effect a pointer to the "dot" that begins the suffix. + */ +CompressFileType HTCompressFileType(const char *filename, + const char *dots, + int *rootlen) +{ + CompressFileType result = cftNone; + char *search; + + if ((search = FindSearch(filename)) != 0) { + char *newname = NULL; + + StrAllocCopy(newname, filename); + newname[((const char *) search) - filename] = '\0'; + result = HTCompressFileType(newname, dots, rootlen); + free(newname); + } else { + size_t len; + const char *ftype; + + VMS_DEL_VERSION(filename); + len = strlen(filename); + ftype = filename + len; + + if ((len > 3) + && !strcasecomp((ftype - 2), "br") + && StrChr(dots, ftype[-3]) != 0) { + result = cftBrotli; + ftype -= 3; + } else if ((len > 4) + && !strcasecomp((ftype - 3), "bz2") + && StrChr(dots, ftype[-4]) != 0) { + result = cftBzip2; + ftype -= 4; + } else if ((len > 3) + && !strcasecomp((ftype - 2), "gz") + && StrChr(dots, ftype[-3]) != 0) { + result = cftGzip; + ftype -= 3; + } else if ((len > 3) + && !strcasecomp((ftype - 2), "zz") + && StrChr(dots, ftype[-3]) != 0) { + result = cftDeflate; + ftype -= 3; + } else if ((len > 2) + && !strcmp((ftype - 1), "Z") + && StrChr(dots, ftype[-2]) != 0) { + result = cftCompress; + ftype -= 2; + } + + *rootlen = (int) (ftype - filename); + + CTRACE((tfp, "HTCompressFileType(%s) returns %d:%s\n", + filename, (int) result, filename + *rootlen)); + } + return result; +} + +/* + * Determine expected file-suffix from the compression method. + */ +const char *HTCompressTypeToSuffix(CompressFileType method) +{ + const char *result; + + switch (method) { + default: + case cftNone: + result = ""; + break; + case cftGzip: + result = ".gz"; + break; + case cftCompress: + result = ".Z"; + break; + case cftBzip2: + result = ".bz2"; + break; + case cftDeflate: + result = ".zz"; + break; + case cftBrotli: + result = ".br"; + break; + } + return result; +} + +/* + * Determine compression encoding from the compression method. + */ +const char *HTCompressTypeToEncoding(CompressFileType method) +{ + const char *result = NULL; + + switch (method) { + default: + case cftNone: + result = NULL; + break; + case cftGzip: + result = "gzip"; + break; + case cftCompress: + result = "compress"; + break; + case cftBzip2: + result = "bzip2"; + break; + case cftDeflate: + result = "deflate"; + break; + case cftBrotli: + result = "brotli"; + break; + } + return result; +} + +/* + * Check if the token from "Content-Encoding" corresponds to a compression + * type. RFC 2068 (and cut/paste into RFC 2616) lists these: + * gzip + * compress + * deflate + * as well as "identity" (but that does nothing). + */ +CompressFileType HTEncodingToCompressType(const char *coding) +{ + CompressFileType result = cftNone; + + if (coding == NULL) { + result = cftNone; + } else if (!strcasecomp(coding, "deflate") || + !strcasecomp(coding, "x-deflate")) { + result = cftDeflate; + } else if (!strcasecomp(coding, "gzip") || + !strcasecomp(coding, "x-gzip")) { + result = cftGzip; + } else if (!strcasecomp(coding, "compress") || + !strcasecomp(coding, "x-compress")) { + result = cftCompress; + } else if (!strcasecomp(coding, "bzip2") || + !strcasecomp(coding, "x-bzip2")) { + result = cftBzip2; + } else if (!strcasecomp(coding, "br") || /* actual */ + !strcasecomp(coding, "x-br") || + !strcasecomp(coding, "brotli")) { /* expected */ + result = cftBrotli; + } + return result; +} + +CompressFileType HTContentTypeToCompressType(const char *ct) +{ + CompressFileType method = cftNone; + + if (ct == NULL) { + method = cftNone; + } else if (!strncasecomp(ct, "application/deflate", 19) || + !strncasecomp(ct, "application/x-deflate", 21)) { + method = cftGzip; + } else if (!strncasecomp(ct, "application/gzip", 16) || + !strncasecomp(ct, "application/x-gzip", 18)) { + method = cftGzip; + } else if (!strncasecomp(ct, "application/compress", 20) || + !strncasecomp(ct, "application/x-compress", 22)) { + method = cftCompress; + } else if (!strncasecomp(ct, "application/bzip2", 17) || + !strncasecomp(ct, "application/x-bzip2", 19)) { + method = cftBzip2; + } else if (!strncasecomp(ct, "application/br", 14) || + !strncasecomp(ct, "application/x-br", 16) || + !strncasecomp(ct, "application/brotli", 18)) { + method = cftBrotli; + } + return method; +} + +/* + * "Encoding" is jargon for compression-type. Provide a reverse mapping. + */ +BOOL IsCompressionFormat(HTAtom *format, CompressFileType check) +{ + BOOL result = FALSE; + + switch (check) { + case cftDeflate: + result = (format == HTAtom_for("application/deflate") || + format == HTAtom_for("application/x-deflate")); + break; + case cftGzip: + result = (format == HTAtom_for("application/gzip") || + format == HTAtom_for("application/x-gzip")); + break; + case cftCompress: + result = (format == HTAtom_for("application/compress") || + format == HTAtom_for("application/x-compress")); + break; + case cftBzip2: + result = (format == HTAtom_for("application/bzip2") || + format == HTAtom_for("application/x-bzip2")); + break; + case cftBrotli: + result = (format == HTAtom_for("application/br") || + format == HTAtom_for("application/x-br") || + format == HTAtom_for("application/brotli")); + break; + case cftNone: + break; + } + return result; +} + +/* + * Check the anchor's content_type and content_encoding elements for a gzip or + * Unix compressed file -FM, TD + */ +CompressFileType HTContentToCompressType(HTParentAnchor *anchor) +{ + CompressFileType method = cftNone; + const char *ct = HTAnchor_content_type(anchor); + const char *ce = HTAnchor_content_encoding(anchor); + + if (ct != 0) { + method = HTContentTypeToCompressType(ct); + } else if (ce != 0) { + method = HTEncodingToCompressType(ce); + } + return method; +} + +/* Determine write access to a file. + * --------------------------------- + * + * On exit: + * Returns YES if file can be accessed and can be written to. + * + * Bugs: + * 1. No code for non-unix systems. + * 2. Isn't there a quicker way? + */ +BOOL HTEditable(const char *filename GCC_UNUSED) +{ +#ifndef NO_GROUPS + GETGROUPS_T groups[NGROUPS]; + uid_t myUid; + int ngroups; /* The number of groups */ + struct stat fileStatus; + int i; + + if (stat(filename, &fileStatus)) /* Get details of filename */ + return NO; /* Can't even access file! */ + + ngroups = getgroups(NGROUPS, groups); /* Groups to which I belong */ + myUid = geteuid(); /* Get my user identifier */ + + if (TRACE) { + int i2; + + fprintf(tfp, + "File mode is 0%o, uid=%d, gid=%d. My uid=%d, %d groups (", + (unsigned int) fileStatus.st_mode, + (int) fileStatus.st_uid, + (int) fileStatus.st_gid, + (int) myUid, + (int) ngroups); + for (i2 = 0; i2 < ngroups; i2++) + fprintf(tfp, " %d", (int) groups[i2]); + fprintf(tfp, ")\n"); + } + + if (fileStatus.st_mode & 0002) /* I can write anyway? */ + return YES; + + if ((fileStatus.st_mode & 0200) /* I can write my own file? */ + &&(fileStatus.st_uid == myUid)) + return YES; + + if (fileStatus.st_mode & 0020) /* Group I am in can write? */ + { + for (i = 0; i < ngroups; i++) { + if (groups[i] == fileStatus.st_gid) + return YES; + } + } + CTRACE((tfp, "\tFile is not editable.\n")); +#endif /* NO_GROUPS */ + return NO; /* If no excuse, can't do */ +} + +/* Make a save stream. + * ------------------- + * + * The stream must be used for writing back the file. + * @@@ no backup done + */ +HTStream *HTFileSaveStream(HTParentAnchor *anchor) +{ + const char *addr = anchor->address; + char *localname = HTLocalName(addr); + FILE *fp = fopen(localname, BIN_W); + + FREE(localname); + if (!fp) + return NULL; + + return HTFWriter_new(fp); +} + +/* Output one directory entry. + * --------------------------- + */ +void HTDirEntry(HTStructured * target, const char *tail, const char *entry) +{ + char *relative = NULL; + char *stripped = NULL; + char *escaped = NULL; + int len; + + if (entry == NULL) + entry = ""; + StrAllocCopy(escaped, entry); + LYTrimPathSep(escaped); + if (strcmp(escaped, "..") != 0) { + stripped = escaped; + escaped = HTEscape(stripped, URL_XPALPHAS); + if (((len = (int) strlen(escaped)) > 2) && + escaped[(len - 3)] == '%' && + escaped[(len - 2)] == '2' && + TOUPPER(escaped[(len - 1)]) == 'F') { + escaped[(len - 3)] = '\0'; + } + } + + if (isEmpty(tail)) { + /* + * Handle extra slash at end of path. + */ + HTStartAnchor(target, NULL, (escaped[0] != '\0' ? escaped : "/")); + } else { + /* + * If empty tail, gives absolute ref below. + */ + relative = 0; + HTSprintf0(&relative, "%s%s%s", + tail, + (*escaped != '\0' ? "/" : ""), + escaped); + HTStartAnchor(target, NULL, relative); + FREE(relative); + } + FREE(stripped); + FREE(escaped); +} + +static BOOL view_structured(HTFormat format_out) +{ + BOOL result = FALSE; + +#ifdef USE_PRETTYSRC + if (psrc_view + || (format_out == WWW_DUMP)) + result = TRUE; +#else + if (format_out == WWW_SOURCE) + result = TRUE; +#endif + return result; +} + +/* + * Write a DOCTYPE to the given stream if we happen to want to see the + * source view, or are dumping source. This is not needed when the source + * is not visible, since the document is rendered from a HTStructured object. + */ +void HTStructured_doctype(HTStructured * target, HTFormat format_out) +{ + if (view_structured(format_out)) + PUTS(LYNX_DOCTYPE "\n"); +} + +void HTStructured_meta(HTStructured * target, HTFormat format_out) +{ + if (view_structured(format_out)) + PUTS("\n"); +} +/* Output parent directory entry. + * ------------------------------ + * + * This gives the TITLE and H1 header, and also a link + * to the parent directory if appropriate. + * + * On exit: + * Returns TRUE if an "Up to " link was not created + * for a readable local directory because LONG_LIST is defined + * and NO_PARENT_DIR_REFERENCE is not defined, so that the + * calling function should use LYListFmtParse() to create a link + * to the parent directory. Otherwise, it returns FALSE. - FM + */ +BOOL HTDirTitles(HTStructured * target, HTParentAnchor *anchor, + HTFormat format_out, + int tildeIsTop) +{ + const char *logical = anchor->address; + char *path = HTParse(logical, "", PARSE_PATH + PARSE_PUNCTUATION); + char *current; + char *cp = NULL; + BOOL need_parent_link = FALSE; + int i; + +#if defined(USE_DOS_DRIVES) + BOOL local_link = (strlen(logical) > 18 + && !strncasecomp(logical, "file://localhost/", 17) + && LYIsDosDrive(logical + 17)); + BOOL is_remote = !local_link; + +#else +#define is_remote TRUE +#endif + + /* + * Check tildeIsTop for treating home directory as Welcome (assume the + * tilde is not followed by a username). - FM + */ + if (tildeIsTop && !StrNCmp(path, "/~", 2)) { + if (path[2] == '\0') { + path[1] = '\0'; + } else { + for (i = 0; path[(i + 2)]; i++) { + path[i] = path[(i + 2)]; + } + path[i] = '\0'; + } + } + + /* + * Trim out the ;type= parameter, if present. - FM + */ + if ((cp = strrchr(path, ';')) != NULL) { + if (!strncasecomp((cp + 1), "type=", 5)) { + if (TOUPPER(*(cp + 6)) == 'D' || + TOUPPER(*(cp + 6)) == 'A' || + TOUPPER(*(cp + 6)) == 'I') + *cp = '\0'; + } + cp = NULL; + } + current = LYPathLeaf(path); /* last part or "" */ + + { + char *printable = NULL; + +#ifdef DIRED_SUPPORT + printable = HTURLPath_toFile(((!strncasecomp(path, "/%2F", 4)) /* "//" ? */ + ? (path + 1) + : path), + TRUE, + is_remote); + if (0 == strncasecomp(printable, "/vmsysu:", 8) || + 0 == strncasecomp(printable, "/anonymou.", 10)) { + StrAllocCopy(cp, (printable + 1)); + StrAllocCopy(printable, cp); + FREE(cp); + } +#else + StrAllocCopy(printable, current); + HTUnEscape(printable); +#endif /* DIRED_SUPPORT */ + + HTStructured_doctype(target, format_out); + + START(HTML_HEAD); + PUTC('\n'); + START(HTML_TITLE); + PUTS(*printable ? printable : WELCOME_MSG); + PUTS(SEGMENT_DIRECTORY); + END(HTML_TITLE); + PUTC('\n'); + HTStructured_meta(target, format_out); + END(HTML_HEAD); + PUTC('\n'); + + START(HTML_BODY); + PUTC('\n'); + +#ifdef DIRED_SUPPORT + START(HTML_H2); + PUTS(*printable ? SEGMENT_CURRENT_DIR : ""); + PUTS(*printable ? printable : WELCOME_MSG); + END(HTML_H2); + PUTC('\n'); +#else + START(HTML_H1); + PUTS(*printable ? printable : WELCOME_MSG); + END(HTML_H1); + PUTC('\n'); +#endif /* DIRED_SUPPORT */ + if (((0 == strncasecomp(printable, "vmsysu:", 7)) && + (cp = StrChr(printable, '.')) != NULL && + StrChr(cp, '/') == NULL) || + (0 == strncasecomp(printable, "anonymou.", 9) && + StrChr(printable, '/') == NULL)) { + FREE(printable); + FREE(path); + return (need_parent_link); + } + FREE(printable); + } + +#ifndef NO_PARENT_DIR_REFERENCE + /* + * Make link back to parent directory. + */ + if (current - path > 0 + && LYIsPathSep(current[-1]) + && current[0] != '\0') { /* was a slash AND something else too */ + char *parent = NULL; + char *relative = NULL; + + current[-1] = '\0'; + parent = strrchr(path, '/'); /* penultimate slash */ + + if ((parent && + (!strcmp(parent, "/..") || + !strncasecomp(parent, "/%2F", 4))) || + !strncasecomp(current, "%2F", 3)) { + FREE(path); + return (need_parent_link); + } + + relative = 0; + HTSprintf0(&relative, "%s/..", current); + +#if defined(DOSPATH) || defined(__EMX__) + if (local_link) { + if (parent != 0 && strlen(parent) == 3) { + StrAllocCat(relative, "/."); + } + } else +#endif + +#if !defined (VMS) + { + /* + * On Unix, if it's not ftp and the directory cannot be read, don't + * put out a link. + * + * On VMS, this problem is dealt with internally by + * HTVMSBrowseDir(). + */ + DIR *dp = NULL; + + if (LYisLocalFile(logical)) { + /* + * We need an absolute file path for the opendir. We also need + * to unescape for this test. Don't worry about %2F now, they + * presumably have been dealt with above, and shouldn't appear + * for local files anyway... Assume OS / filesystem will just + * ignore superfluous slashes. - KW + */ + char *fullparentpath = NULL; + + /* + * Path has been shortened above. + */ + StrAllocCopy(fullparentpath, *path ? path : "/"); + + /* + * Guard against weirdness. + */ + if (0 == strcmp(current, "..")) { + StrAllocCat(fullparentpath, "/../.."); + } else if (0 == strcmp(current, ".")) { + StrAllocCat(fullparentpath, "/.."); + } + + HTUnEscape(fullparentpath); + if ((dp = opendir(fullparentpath)) == NULL) { + FREE(fullparentpath); + FREE(relative); + FREE(path); + return (need_parent_link); + } + closedir(dp); + FREE(fullparentpath); +#ifdef LONG_LIST + need_parent_link = TRUE; + FREE(path); + FREE(relative); + return (need_parent_link); +#endif /* LONG_LIST */ + } + } +#endif /* !VMS */ + HTStartAnchor(target, "", relative); + FREE(relative); + + PUTS(SEGMENT_UP_TO); + if (parent) { + if ((0 == strcmp(current, ".")) || + (0 == strcmp(current, ".."))) { + /* + * Should not happen, but if it does, at least avoid giving + * misleading info. - KW + */ + PUTS(".."); + } else { + char *printable = NULL; + + StrAllocCopy(printable, parent + 1); + HTUnEscape(printable); + PUTS(printable); + FREE(printable); + } + } else { + PUTC('/'); + } + END(HTML_A); + PUTC('\n'); + } +#endif /* !NO_PARENT_DIR_REFERENCE */ + + FREE(path); + return (need_parent_link); +} + +#if defined HAVE_READDIR +/* Send README file. + * ----------------- + * + * If a README file exists, then it is inserted into the document here. + */ +static void do_readme(HTStructured * target, const char *localname) +{ + FILE *fp; + char *readme_file_name = NULL; + int ch; + + HTSprintf0(&readme_file_name, "%s/%s", localname, HT_DIR_README_FILE); + + fp = fopen(readme_file_name, "r"); + + if (fp) { + START(HTML_PRE); + while ((ch = fgetc(fp)) != EOF) { + PUTC((char) ch); + } + END(HTML_PRE); + HTDisplayPartial(); + fclose(fp); + } + FREE(readme_file_name); +} + +#define DIRED_BLOK(obj) (((DIRED *)(obj))->sort_tags) +#define DIRED_NAME(obj) (((DIRED *)(obj))->file_name) + +#define NM_cmp(a,b) ((a) < (b) ? -1 : ((a) > (b) ? 1 : 0)) + +#if defined(LONG_LIST) && defined(DIRED_SUPPORT) +static const char *file_type(const char *path) +{ + const char *type; + + while (*path == '.') + ++path; + type = StrChr(path, '.'); + if (type == NULL) + type = ""; + return type; +} +#endif /* LONG_LIST && DIRED_SUPPORT */ + +static int dired_cmp(void *a, void *b) +{ + DIRED *p = (DIRED *) a; + DIRED *q = (DIRED *) b; + int code = p->sort_tags - q->sort_tags; + +#if defined(LONG_LIST) && defined(DIRED_SUPPORT) + if (code == 0) { + switch (dir_list_order) { + case ORDER_BY_SIZE: + code = -NM_cmp(p->file_info.st_size, q->file_info.st_size); + break; + case ORDER_BY_DATE: + code = -NM_cmp(p->file_info.st_mtime, q->file_info.st_mtime); + break; + case ORDER_BY_MODE: + code = NM_cmp(p->file_info.st_mode, q->file_info.st_mode); + break; + case ORDER_BY_USER: + code = NM_cmp(p->file_info.st_uid, q->file_info.st_uid); + break; + case ORDER_BY_GROUP: + code = NM_cmp(p->file_info.st_gid, q->file_info.st_gid); + break; + case ORDER_BY_TYPE: + code = AS_cmp(file_type(p->file_name), file_type(q->file_name)); + break; + default: + code = 0; + break; + } + } +#endif /* LONG_LIST && DIRED_SUPPORT */ + if (code == 0) + code = AS_cmp(p->file_name, q->file_name); +#if 0 + CTRACE((tfp, "dired_cmp(%d) ->%d\n\t%c:%s (%s)\n\t%c:%s (%s)\n", + dir_list_order, + code, + p->sort_tags, p->file_name, file_type(p->file_name), + q->sort_tags, q->file_name, file_type(q->file_name))); +#endif + return code; +} + +static int print_local_dir(DIR *dp, char *localname, + HTParentAnchor *anchor, + HTFormat format_out, + HTStream *sink) +{ + HTStructured *target; /* HTML object */ + HTBTree *bt; + HTStructuredClass targetClass; + STRUCT_DIRENT *dirbuf; + char *pathname = NULL; + char *tail = NULL; + const char *p; + char *tmpfilename = NULL; + BOOL need_parent_link = FALSE; + BOOL preformatted = FALSE; + int status; + struct stat *actual_info; + +#ifdef DISP_PARTIAL + int num_of_entries = 0; /* lines counter */ +#endif + +#ifdef S_IFLNK + struct stat link_info; +#endif + + CTRACE((tfp, "print_local_dir() started\n")); + + pathname = HTParse(anchor->address, "", + PARSE_PATH + PARSE_PUNCTUATION); + + if ((p = strrchr(pathname, '/')) == NULL) + p = "/"; + StrAllocCopy(tail, (p + 1)); + FREE(pathname); + + if (UCLYhndl_HTFile_for_unspec >= 0) { + HTAnchor_setUCInfoStage(anchor, + UCLYhndl_HTFile_for_unspec, + UCT_STAGE_PARSER, + UCT_SETBY_DEFAULT); + } + + target = HTML_new(anchor, format_out, sink); + targetClass = *target->isa; /* Copy routine entry points */ + + /* + * The need_parent_link flag will be set if an "Up to " link was + * not created for a readable parent in HTDirTitles() because LONG_LIST is + * defined and NO_PARENT_DIR_REFERENCE is not defined so that need we to + * create the link via an LYListFmtParse() call. - FM + */ + need_parent_link = HTDirTitles(target, anchor, format_out, FALSE); + +#ifdef DIRED_SUPPORT + if (!isLYNXCGI(anchor->address)) { + HTAnchor_setFormat(anchor, WWW_DIRED); + lynx_edit_mode = TRUE; + } +#endif /* DIRED_SUPPORT */ + if (HTDirReadme == HT_DIR_README_TOP) + do_readme(target, localname); + + bt = HTBTree_new(dired_cmp); + + _HTProgress(READING_DIRECTORY); + status = HT_LOADED; /* assume we don't get interrupted */ + while ((dirbuf = readdir(dp)) != NULL) { + /* + * While there are directory entries to be read... + */ + DIRED *data = NULL; + +#ifdef STRUCT_DIRENT__D_INO + if (dirbuf->d_ino == 0) + /* + * If the entry is not being used, skip it. + */ + continue; +#endif + /* + * Skip self, parent if handled in HTDirTitles() or if + * NO_PARENT_DIR_REFERENCE is not defined, and any dot files if + * no_dotfiles is set or show_dotfiles is not set. - FM + */ + if (!strcmp(dirbuf->d_name, ".") /* self */ || + (!strcmp(dirbuf->d_name, "..") /* parent */ && + need_parent_link == FALSE) || + ((strcmp(dirbuf->d_name, "..")) && + (dirbuf->d_name[0] == '.' && + (no_dotfiles || !show_dotfiles)))) + continue; + + StrAllocCopy(tmpfilename, localname); + /* + * If filename is not root directory, add trailing separator. + */ + LYAddPathSep(&tmpfilename); + + StrAllocCat(tmpfilename, dirbuf->d_name); + data = (DIRED *) malloc(sizeof(DIRED) + strlen(dirbuf->d_name) + 4); + if (data == NULL) { + status = HT_PARTIAL_CONTENT; + break; + } + LYTrimPathSep(tmpfilename); + + actual_info = &(data->file_info); +#ifdef S_IFLNK + if (lstat(tmpfilename, actual_info) < 0) { + actual_info->st_mode = 0; + } else { + if (S_ISLNK(actual_info->st_mode)) { + actual_info = &link_info; + if (stat(tmpfilename, actual_info) < 0) + actual_info->st_mode = 0; + } + } +#else + if (stat(tmpfilename, actual_info) < 0) + actual_info->st_mode = 0; +#endif + + strcpy(data->file_name, dirbuf->d_name); +#ifndef DIRED_SUPPORT + if (S_ISDIR(actual_info->st_mode)) { + data->sort_tags = 'D'; + } else { + data->sort_tags = 'F'; + /* D & F to have first directories, then files */ + } +#else + if (S_ISDIR(actual_info->st_mode)) { + if (dir_list_style == MIXED_STYLE) { + data->sort_tags = ' '; + LYAddPathSep0(data->file_name); + } else if (!strcmp(dirbuf->d_name, "..")) { + data->sort_tags = 'A'; + } else { + data->sort_tags = 'D'; + } + } else if (dir_list_style == MIXED_STYLE) { + data->sort_tags = ' '; + } else if (dir_list_style == FILES_FIRST) { + data->sort_tags = 'C'; + /* C & D to have first files, then directories */ + } else { + data->sort_tags = 'F'; + } +#endif /* !DIRED_SUPPORT */ + /* + * Sort dirname in the tree bt. + */ + HTBTree_add(bt, data); + +#ifdef DISP_PARTIAL + /* optimize for expensive operation: */ + if (num_of_entries % (partial_threshold > 0 ? + partial_threshold : display_lines) == 0) { + if (HTCheckForInterrupt()) { + status = HT_PARTIAL_CONTENT; + break; + } + } + num_of_entries++; +#endif /* DISP_PARTIAL */ + + } /* end while directory entries left to read */ + + if (status != HT_PARTIAL_CONTENT) + _HTProgress(OPERATION_OK); + else + CTRACE((tfp, "Reading the directory interrupted by user\n")); + + /* + * Run through tree printing out in order. + */ + { + HTBTElement *next_element = HTBTree_next(bt, NULL); + + /* pick up the first element of the list */ + int num_of_entries_output = 0; /* lines counter */ + + char state; + + /* I for initial (.. file), + D for directory file, + F for file */ + +#ifdef DIRED_SUPPORT + char test; +#endif /* DIRED_SUPPORT */ + state = 'I'; + + while (next_element != NULL) { + DIRED *entry; + +#ifndef DISP_PARTIAL + if (num_of_entries_output % HTMAX(display_lines, 10) == 0) { + if (HTCheckForInterrupt()) { + _HTProgress(TRANSFER_INTERRUPTED); + status = HT_PARTIAL_CONTENT; + break; + } + } +#endif + StrAllocCopy(tmpfilename, localname); + /* + * If filename is not root directory. + */ + LYAddPathSep(&tmpfilename); + + entry = (DIRED *) (HTBTree_object(next_element)); + /* + * Append the current entry's filename to the path. + */ + StrAllocCat(tmpfilename, entry->file_name); + HTSimplify(tmpfilename, LYIsPathSep(*tmpfilename)); + /* + * Output the directory entry. + */ + if (strcmp(DIRED_NAME(HTBTree_object(next_element)), "..")) { +#ifdef DIRED_SUPPORT + test = + (char) (DIRED_BLOK(HTBTree_object(next_element)) + == 'D' ? 'D' : 'F'); + if (state != test) { +#ifndef LONG_LIST + if (dir_list_style == FILES_FIRST) { + if (state == 'F') { + END(HTML_DIR); + PUTC('\n'); + } + } else if (dir_list_style != MIXED_STYLE) + if (state == 'D') { + END(HTML_DIR); + PUTC('\n'); + } +#endif /* !LONG_LIST */ + state = + (char) (DIRED_BLOK(HTBTree_object(next_element)) + == 'D' ? 'D' : 'F'); + if (preformatted) { + END(HTML_PRE); + PUTC('\n'); + preformatted = FALSE; + } + START(HTML_H2); + if (dir_list_style != MIXED_STYLE) { + START(HTML_EM); + PUTS(state == 'D' + ? LABEL_SUBDIRECTORIES + : LABEL_FILES); + END(HTML_EM); + } + END(HTML_H2); + PUTC('\n'); +#ifndef LONG_LIST + START(HTML_DIR); + PUTC('\n'); +#endif /* !LONG_LIST */ + } +#else + if (state != DIRED_BLOK(HTBTree_object(next_element))) { +#ifndef LONG_LIST + if (state == 'D') { + END(HTML_DIR); + PUTC('\n'); + } +#endif /* !LONG_LIST */ + state = + (char) (DIRED_BLOK(HTBTree_object(next_element)) + == 'D' ? 'D' : 'F'); + if (preformatted) { + END(HTML_PRE); + PUTC('\n'); + preformatted = FALSE; + } + START(HTML_H2); + START(HTML_EM); + PUTS(state == 'D' + ? LABEL_SUBDIRECTORIES + : LABEL_FILES); + END(HTML_EM); + END(HTML_H2); + PUTC('\n'); +#ifndef LONG_LIST + START(HTML_DIR); + PUTC('\n'); +#endif /* !LONG_LIST */ + } +#endif /* DIRED_SUPPORT */ +#ifndef LONG_LIST + START(HTML_LI); +#endif /* !LONG_LIST */ + } + if (!preformatted) { + START(HTML_PRE); + PUTC('\n'); + preformatted = TRUE; + } +#ifdef LONG_LIST + LYListFmtParse(list_format, entry, tmpfilename, target, tail); +#else + HTDirEntry(target, tail, entry->file_name); + PUTS(entry->file_name); + END(HTML_A); + MAYBE_END(HTML_LI); + PUTC('\n'); +#endif /* LONG_LIST */ + + next_element = HTBTree_next(bt, next_element); + /* pick up the next element of the list; + if none, return NULL */ + + /* optimize for expensive operation: */ +#ifdef DISP_PARTIAL + if (num_of_entries_output % + ((partial_threshold > 0) + ? partial_threshold + : display_lines) == 0) { + /* num_of_entries, num_of_entries_output... */ + HTDisplayPartial(); + + if (HTCheckForInterrupt()) { + _HTProgress(TRANSFER_INTERRUPTED); + status = HT_PARTIAL_CONTENT; + break; + } + } + num_of_entries_output++; +#endif /* DISP_PARTIAL */ + + } /* end while next_element */ + + if (status == HT_LOADED) { + if (state == 'I') { + START(HTML_P); + PUTS("Empty Directory"); + } +#ifndef LONG_LIST + else + END(HTML_DIR); +#endif /* !LONG_LIST */ + } + } /* end printing out the tree in order */ + if (preformatted) { + END(HTML_PRE); + PUTC('\n'); + } + END(HTML_BODY); + PUTC('\n'); + + FREE(tmpfilename); + FREE(tail); + HTBTreeAndObject_free(bt); + + if (status == HT_LOADED) { + if (HTDirReadme == HT_DIR_README_BOTTOM) + do_readme(target, localname); + FREE_TARGET; + } else { + ABORT_TARGET; + } + HTFinishDisplayPartial(); + return status; /* document loaded, maybe partial */ +} +#endif /* HAVE_READDIR */ + +#ifndef VMS +int HTStat(const char *filename, + struct stat *data) +{ + int result = -1; + size_t len = strlen(filename); + + if (len != 0 && LYIsPathSep(filename[len - 1])) { + char *temp_name = NULL; + + HTSprintf0(&temp_name, "%s.", filename); + result = HTStat(temp_name, data); + FREE(temp_name); + } else { + result = stat(filename, data); +#ifdef _WINDOWS + /* + * Someone claims that stat() doesn't give the proper result for a + * directory on Windows. + */ + if (result == -1 + && access(filename, 0) == 0) { + data->st_mode = S_IFDIR; + result = 0; + } +#endif + } + return result; +} +#endif + +#if defined(USE_ZLIB) || defined(USE_BZLIB) +static BOOL sniffStream(FILE *fp, char *buffer, size_t needed) +{ + long offset = ftell(fp); + BOOL result = FALSE; + + if (offset >= 0) { + if (fread(buffer, sizeof(char), needed, fp) == needed) { + result = TRUE; + } + if (fseek(fp, offset, SEEK_SET) < 0) { + CTRACE((tfp, "error seeking in stream\n")); + result = FALSE; + } + } + return result; +} +#endif + +#ifdef USE_ZLIB +static BOOL isGzipStream(FILE *fp) +{ + char buffer[3]; + BOOL result; + + if (sniffStream(fp, buffer, sizeof(buffer)) + && !MemCmp(buffer, "\037\213", sizeof(buffer) - 1)) { + result = TRUE; + } else { + CTRACE((tfp, "not a gzip-stream\n")); + result = FALSE; + } + return result; +} + +/* + * Strictly speaking, DEFLATE has no header bytes. But decode what we can, + * (to eliminate the one "reserved" pattern) and provide a trace. See RFC-1951 + * discussion of BFINAL and BTYPE. + */ +static BOOL isDeflateStream(FILE *fp) +{ + char buffer[3]; + BOOL result = FALSE; + + if (sniffStream(fp, buffer, sizeof(buffer))) { + int bit1 = ((buffer[0] >> 0) & 1); + int bit2 = ((buffer[0] >> 1) & 1); + int bit3 = ((buffer[0] >> 2) & 1); + int btype = ((bit3 << 1) + bit2); + + if (!MemCmp(buffer, "\170\234", sizeof(buffer) - 1)) { + result = TRUE; + CTRACE((tfp, "isDeflate: assume zlib-wrapped deflate\n")); + } else if (btype == 3) { + CTRACE((tfp, "isDeflate: not a deflate-stream\n")); + } else { + CTRACE((tfp, "isDeflate: %send block, %s compression\n", + (bit1 ? "" : "non-"), + (btype == 0 + ? "no" + : (btype == 1 + ? "static Huffman" + : "dynamic Huffman")))); + result = TRUE; + } + } + return result; +} +#endif + +#ifdef USE_BZLIB +static BOOL isBzip2Stream(FILE *fp) +{ + char buffer[6]; + BOOL result; + + if (sniffStream(fp, buffer, sizeof(buffer)) + && !MemCmp(buffer, "BZh", 3) + && isdigit(UCH(buffer[3])) + && isdigit(UCH(buffer[4]))) { + result = TRUE; + } else { + CTRACE((tfp, "not a bzip2-stream\n")); + result = FALSE; + } + return result; +} +#endif + +#ifdef VMS +#define FOPEN_MODE(bin) "r", "shr=put", "shr=upd" +#define DOT_STRING "._-" /* FIXME: should we check if suffix is after ']' or ':' ? */ +#else +#define FOPEN_MODE(bin) (bin ? BIN_R : "r") +#define DOT_STRING "." +#endif + +#ifdef USE_BROTLI +static FILE *brotli_open(const char *localname, const char *mode) +{ + CTRACE((tfp, "brotli_open file=%s, mode=%s\n", localname, mode)); + return fopen(localname, mode); +} +#endif + +static int decompressAndParse(HTParentAnchor *anchor, + HTFormat format_out, + HTStream *sink, + char *nodename GCC_UNUSED, + char *filename, + HTAtom *myEncoding, + HTFormat format, + int *statusp) +{ + HTAtom *encoding = 0; + +#ifdef USE_ZLIB + FILE *zzfp = 0; + gzFile gzfp = 0; +#endif /* USE_ZLIB */ +#ifdef USE_BZLIB + BZFILE *bzfp = 0; +#endif /* USE_BZLIB */ +#ifdef USE_BROTLI + FILE *brfp = 0; +#endif /* USE_BROTLI */ +#if defined(USE_ZLIB) || defined(USE_BZLIB) + CompressFileType internal_decompress = cftNone; + BOOL failed_decompress = NO; +#endif + int rootlen = 0; + char *localname = filename; + int bin; + FILE *fp; + int result = FALSE; + +#ifdef VMS + /* + * Assume that the file is in Unix-style syntax if it contains a '/' after + * the leading one. @@ + */ + localname = (StrChr(localname + 1, '/') + ? HTVMS_name(nodename, localname) + : localname + 1); +#endif /* VMS */ + + bin = HTCompressFileType(filename, ".", &rootlen) != cftNone; + fp = fopen(localname, FOPEN_MODE(bin)); + +#ifdef VMS + /* + * If the file wasn't VMS syntax, then perhaps it is Ultrix. + */ + if (!fp) { + char *ultrixname = 0; + + CTRACE((tfp, "HTLoadFile: Can't open as %s\n", localname)); + HTSprintf0(&ultrixname, "%s::\"%s\"", nodename, filename); + fp = fopen(ultrixname, FOPEN_MODE(bin)); + if (!fp) { + CTRACE((tfp, "HTLoadFile: Can't open as %s\n", ultrixname)); + } + FREE(ultrixname); + } +#endif /* VMS */ + CTRACE((tfp, "HTLoadFile: Opening `%s' gives %p\n", localname, (void *) fp)); + if (fp) { /* Good! */ + if (HTEditable(localname)) { + HTAtom *put = HTAtom_for("PUT"); + HTList *methods = HTAnchor_methods(anchor); + + if (HTList_indexOf(methods, put) == (-1)) { + HTList_addObject(methods, put); + } + } + /* + * Fake a Content-Encoding for compressed files. - FM + */ + if (!IsUnityEnc(myEncoding)) { + /* + * We already know from the call to HTFileFormat that + * this is a compressed file, no need to look at the filename + * again. - kw + */ +#if defined(USE_ZLIB) || defined(USE_BZLIB) + CompressFileType method = HTEncodingToCompressType(HTAtom_name(myEncoding)); +#endif + +#define isDOWNLOAD(m) (strcmp(format_out->name, STR_DOWNLOAD) && (method == m)) +#ifdef USE_ZLIB + if (isDOWNLOAD(cftGzip)) { + if (isGzipStream(fp)) { + fclose(fp); + fp = 0; + gzfp = gzopen(localname, BIN_R); + + CTRACE((tfp, "HTLoadFile: gzopen of `%s' gives %p\n", + localname, (void *) gzfp)); + } + internal_decompress = cftGzip; + } else if (isDOWNLOAD(cftDeflate)) { + if (isDeflateStream(fp)) { + zzfp = fp; + fp = 0; + + CTRACE((tfp, "HTLoadFile: zzopen of `%s' gives %p\n", + localname, (void *) zzfp)); + } + internal_decompress = cftDeflate; + } else +#endif /* USE_ZLIB */ +#ifdef USE_BZLIB + if (isDOWNLOAD(cftBzip2)) { + if (isBzip2Stream(fp)) { + fclose(fp); + fp = 0; + bzfp = BZ2_bzopen(localname, BIN_R); + + CTRACE((tfp, "HTLoadFile: bzopen of `%s' gives %p\n", + localname, bzfp)); + } + internal_decompress = cftBzip2; + } else +#endif /* USE_BZLIB */ +#ifdef USE_BROTLI + if (isDOWNLOAD(cftBrotli)) { + fclose(fp); + fp = 0; + brfp = brotli_open(localname, BIN_R); + + CTRACE((tfp, "HTLoadFile: brotli_open of `%s' gives %p\n", + localname, (void *) brfp)); + internal_decompress = cftBrotli; + } else +#endif /* USE_BROTLI */ + { + StrAllocCopy(anchor->content_type, format->name); + StrAllocCopy(anchor->content_encoding, HTAtom_name(myEncoding)); + format = HTAtom_for("www/compressed"); + } + } else { + CompressFileType cft = HTCompressFileType(localname, DOT_STRING, &rootlen); + + if (cft != cftNone) { + char *cp = NULL; + + StrAllocCopy(cp, localname); + cp[rootlen] = '\0'; + format = HTFileFormat(cp, &encoding, NULL); + FREE(cp); + format = HTCharsetFormat(format, anchor, + UCLYhndl_HTFile_for_unspec); + StrAllocCopy(anchor->content_type, format->name); + } + + switch (cft) { + case cftCompress: + StrAllocCopy(anchor->content_encoding, "x-compress"); + format = HTAtom_for("www/compressed"); + break; + case cftDeflate: + StrAllocCopy(anchor->content_encoding, "x-deflate"); +#ifdef USE_ZLIB + if (strcmp(format_out->name, STR_DOWNLOAD) != 0) { + if (isDeflateStream(fp)) { + zzfp = fp; + fp = 0; + + CTRACE((tfp, "HTLoadFile: zzopen of `%s' gives %p\n", + localname, (void *) zzfp)); + } + internal_decompress = cftDeflate; + } +#else /* USE_ZLIB */ + format = HTAtom_for("www/compressed"); +#endif /* USE_ZLIB */ + break; + case cftGzip: + StrAllocCopy(anchor->content_encoding, "x-gzip"); +#ifdef USE_ZLIB + if (strcmp(format_out->name, STR_DOWNLOAD) != 0) { + if (isGzipStream(fp)) { + fclose(fp); + fp = 0; + gzfp = gzopen(localname, BIN_R); + + CTRACE((tfp, "HTLoadFile: gzopen of `%s' gives %p\n", + localname, (void *) gzfp)); + } + internal_decompress = cftGzip; + } +#else /* USE_ZLIB */ + format = HTAtom_for("www/compressed"); +#endif /* USE_ZLIB */ + break; + case cftBzip2: + StrAllocCopy(anchor->content_encoding, "x-bzip2"); +#ifdef USE_BZLIB + if (strcmp(format_out->name, STR_DOWNLOAD) != 0) { + if (isBzip2Stream(fp)) { + fclose(fp); + fp = 0; + bzfp = BZ2_bzopen(localname, BIN_R); + + CTRACE((tfp, "HTLoadFile: bzopen of `%s' gives %p\n", + localname, bzfp)); + } + internal_decompress = cftBzip2; + } +#else /* USE_BZLIB */ + format = HTAtom_for("www/compressed"); +#endif /* USE_BZLIB */ + break; + case cftBrotli: + StrAllocCopy(anchor->content_encoding, "x-brotli"); +#ifdef USE_BROTLI + if (strcmp(format_out->name, STR_DOWNLOAD) != 0) { + fclose(fp); + fp = 0; + brfp = brotli_open(localname, BIN_R); + + CTRACE((tfp, "HTLoadFile: brotli_open of `%s' gives %p\n", + localname, (void *) brfp)); + internal_decompress = cftBrotli; + } +#else /* USE_BROTLI */ + format = HTAtom_for("www/compressed"); +#endif /* USE_BROTLI */ + break; + case cftNone: + break; + } + } +#if defined(USE_ZLIB) || defined(USE_BZLIB) + if (internal_decompress != cftNone) { + switch (internal_decompress) { +#ifdef USE_ZLIB + case cftDeflate: + failed_decompress = (BOOLEAN) (zzfp == NULL); + break; + case cftCompress: + case cftGzip: + failed_decompress = (BOOLEAN) (gzfp == NULL); + break; +#endif +#ifdef USE_BZLIB + case cftBzip2: + failed_decompress = (BOOLEAN) (bzfp == NULL); + break; +#endif +#ifdef USE_BROTLI + case cftBrotli: + failed_decompress = (BOOLEAN) (brfp == NULL); + break; +#endif + default: + failed_decompress = YES; + break; + } + if (failed_decompress) { + *statusp = HTLoadError(NULL, + -(HT_ERROR), + FAILED_OPEN_COMPRESSED_FILE); + } else { + char *sugfname = NULL; + + if (anchor->SugFname) { + StrAllocCopy(sugfname, anchor->SugFname); + } else { + char *anchor_path = HTParse(anchor->address, "", + PARSE_PATH + PARSE_PUNCTUATION); + char *lastslash; + + HTUnEscape(anchor_path); + lastslash = strrchr(anchor_path, '/'); + if (lastslash) + StrAllocCopy(sugfname, lastslash + 1); + FREE(anchor_path); + } + FREE(anchor->content_encoding); + if (sugfname && *sugfname) + HTCheckFnameForCompression(&sugfname, anchor, + TRUE); + if (sugfname && *sugfname) + StrAllocCopy(anchor->SugFname, sugfname); + FREE(sugfname); +#ifdef USE_BROTLI + if (brfp) + *statusp = HTParseBrFile(format, format_out, + anchor, + brfp, sink); +#endif +#ifdef USE_BZLIB + if (bzfp) + *statusp = HTParseBzFile(format, format_out, + anchor, + bzfp, sink); +#endif +#ifdef USE_ZLIB + if (gzfp) + *statusp = HTParseGzFile(format, format_out, + anchor, + gzfp, sink); + else if (zzfp) + *statusp = HTParseZzFile(format, format_out, + anchor, + zzfp, sink); +#endif + } + } else +#endif /* USE_ZLIB || USE_BZLIB */ + { + *statusp = HTParseFile(format, format_out, anchor, fp, sink); + } + if (fp != 0) { + fclose(fp); + fp = 0; + } + result = TRUE; + } /* If successful open */ + return result; +} + +/* Load a document. + * ---------------- + * + * On entry: + * addr must point to the fully qualified hypertext reference. + * This is the physical address of the file + * + * On exit: + * returns <0 Error has occurred. + * HTLOADED OK + * + */ +int HTLoadFile(const char *addr, + HTParentAnchor *anchor, + HTFormat format_out, + HTStream *sink) +{ + char *filename = NULL; + char *acc_method = NULL; + HTFormat format; + char *nodename = NULL; + char *newname = NULL; /* Simplified name of file */ + HTAtom *myEncoding = NULL; /* enc of this file, may be gzip etc. */ + int status = -1; + +#ifndef DISABLE_FTP + char *ftp_newhost; +#endif + +#ifdef VMS + struct stat stat_info; +#endif /* VMS */ + + /* + * Reduce the filename to a basic form (hopefully unique!). + */ + StrAllocCopy(newname, addr); + filename = HTParse(newname, "", PARSE_PATH | PARSE_PUNCTUATION); + nodename = HTParse(newname, "", PARSE_HOST); + + /* + * If access is ftp, or file is on another host, invoke ftp now. + */ + acc_method = HTParse(newname, "", PARSE_ACCESS); + if (strcmp("ftp", acc_method) == 0 || + (!LYSameHostname("localhost", nodename) && + !LYSameHostname(nodename, HTHostName()))) { + status = -1; + FREE(newname); + FREE(filename); + FREE(nodename); + FREE(acc_method); +#ifndef DISABLE_FTP + ftp_newhost = HTParse(addr, "", PARSE_HOST); + if (strcmp(ftp_lasthost, ftp_newhost)) + ftp_local_passive = ftp_passive; + + status = HTFTPLoad(addr, anchor, format_out, sink); + + if (ftp_passive == ftp_local_passive) { + if ((status >= 400) || (status < 0)) { + ftp_local_passive = (BOOLEAN) !ftp_passive; + status = HTFTPLoad(addr, anchor, format_out, sink); + } + } + + free(ftp_lasthost); + ftp_lasthost = ftp_newhost; +#endif /* DISABLE_FTP */ + return status; + } else { + FREE(newname); + FREE(acc_method); + } +#if defined(VMS) || defined(USE_DOS_DRIVES) + HTUnEscape(filename); +#endif /* VMS */ + + /* + * Determine the format and encoding mapped to any suffix. + */ + if (anchor->content_type && anchor->content_encoding) { + /* + * If content_type and content_encoding are BOTH already set in the + * anchor object, we believe it and don't try to derive format and + * encoding from the filename. - kw + */ + format = HTAtom_for(anchor->content_type); + myEncoding = HTAtom_for(anchor->content_encoding); + } else { + int default_UCLYhndl = UCLYhndl_HTFile_for_unspec; + + if (force_old_UCLYhndl_on_reload) { + force_old_UCLYhndl_on_reload = FALSE; + default_UCLYhndl = forced_UCLYhdnl; + } + + format = HTFileFormat(filename, &myEncoding, NULL); + + /* + * Check the format for an extended MIME charset value, and act on it + * if present. Otherwise, assume what is indicated by the last + * parameter (fallback will effectively be UCLYhndl_for_unspec, by + * default ISO-8859-1). - kw + */ + format = HTCharsetFormat(format, anchor, default_UCLYhndl); + } + +#ifdef VMS + /* + * Check to see if the 'filename' is in fact a directory. If it is create + * a new hypertext object containing a list of files and subdirectories + * contained in the directory. All of these are links to the directories + * or files listed. + */ + if (HTStat(filename, &stat_info) == -1) { + CTRACE((tfp, "HTLoadFile: Can't stat %s\n", filename)); + } else { + if (S_ISDIR(stat_info.st_mode)) { + if (HTDirAccess == HT_DIR_FORBID) { + FREE(filename); + FREE(nodename); + return HTLoadError(sink, 403, DISALLOWED_DIR_SCAN); + } + + if (HTDirAccess == HT_DIR_SELECTIVE) { + char *enable_file_name = NULL; + + HTSprintf0(&enable_file_name, "%s/%s", filename, HT_DIR_ENABLE_FILE); + if (HTStat(enable_file_name, &stat_info) == -1) { + FREE(filename); + FREE(nodename); + FREE(enable_file_name); + return HTLoadError(sink, 403, DISALLOWED_SELECTIVE_ACCESS); + } + } + + FREE(filename); + FREE(nodename); + return HTVMSBrowseDir(addr, anchor, format_out, sink); + } + } + + if (decompressAndParse(anchor, + format_out, + sink, + nodename, + filename, + myEncoding, + format, + &status)) { + FREE(nodename); + FREE(filename); + return status; + } + FREE(filename); + +#else /* not VMS: */ + + FREE(filename); + + /* + * For unix, we try to translate the name into the name of a transparently + * mounted file. + * + * Not allowed in secure (HTClientHost) situations. TBL 921019 + */ +#ifndef NO_UNIX_IO + /* Need protection here for telnet server but not httpd server. */ + + if (!HTSecure) { /* try local file system */ + char *localname = HTLocalName(addr); + struct stat dir_info; + +#ifdef HAVE_READDIR + /* + * Multiformat handling. + * + * If needed, scan directory to find a good file. Bug: We don't stat + * the file to find the length. + */ + if ((strlen(localname) > strlen(MULTI_SUFFIX)) && + (0 == strcmp(localname + strlen(localname) - strlen(MULTI_SUFFIX), + MULTI_SUFFIX))) { + DIR *dp = 0; + BOOL forget_multi = NO; + + STRUCT_DIRENT *dirbuf; + float best = (float) NO_VALUE_FOUND; /* So far best is bad */ + HTFormat best_rep = NULL; /* Set when rep found */ + HTAtom *best_enc = NULL; + char *best_name = NULL; /* Best dir entry so far */ + + char *base = strrchr(localname, '/'); + size_t baselen = 0; + + if (!base || base == localname) { + forget_multi = YES; + } else { + *base++ = '\0'; /* Just got directory name */ + baselen = strlen(base) - strlen(MULTI_SUFFIX); + base[baselen] = '\0'; /* Chop off suffix */ + + dp = opendir(localname); + } + if (forget_multi || !dp) { + FREE(localname); + FREE(nodename); + return HTLoadError(sink, 500, FAILED_DIR_SCAN); + } + + while ((dirbuf = readdir(dp)) != NULL) { + /* + * While there are directory entries to be read... + */ +#ifdef STRUCT_DIRENT__D_INO + if (dirbuf->d_ino == 0) + continue; /* if the entry is not being used, skip it */ +#endif + if (strlen(dirbuf->d_name) > baselen && /* Match? */ + !StrNCmp(dirbuf->d_name, base, baselen)) { + HTAtom *enc; + HTFormat rep = HTFileFormat(dirbuf->d_name, &enc, NULL); + float filevalue = HTFileValue(dirbuf->d_name); + float value = HTStackValue(rep, format_out, + filevalue, + 0L /* @@@@@@ */ ); + + if (value <= 0.0) { + int rootlen = 0; + const char *atomname = NULL; + CompressFileType cft = + HTCompressFileType(dirbuf->d_name, ".", &rootlen); + char *cp = NULL; + + enc = NULL; + if (cft != cftNone) { + StrAllocCopy(cp, dirbuf->d_name); + cp[rootlen] = '\0'; + format = HTFileFormat(cp, NULL, NULL); + FREE(cp); + value = HTStackValue(format, format_out, + filevalue, 0L); + } + switch (cft) { + case cftCompress: + atomname = "application/x-compressed"; + break; + case cftGzip: + atomname = "application/x-gzip"; + break; + case cftDeflate: + atomname = "application/x-deflate"; + break; + case cftBzip2: + atomname = "application/x-bzip2"; + break; + case cftBrotli: + atomname = "application/x-brotli"; + break; + case cftNone: + break; + } + + if (atomname != NULL) { + value = HTStackValue(format, format_out, + filevalue, 0L); + if (value <= 0.0) { + format = HTAtom_for(atomname); + value = HTStackValue(format, format_out, + filevalue, 0L); + } + if (value <= 0.0) { + format = HTAtom_for("www/compressed"); + value = HTStackValue(format, format_out, + filevalue, 0L); + } + } + } + if (value < NO_VALUE_FOUND) { + CTRACE((tfp, + "HTLoadFile: value of presenting %s is %f\n", + HTAtom_name(rep), value)); + if (value > best) { + best_rep = rep; + best_enc = enc; + best = value; + StrAllocCopy(best_name, dirbuf->d_name); + } + } /* if best so far */ + } + /* if match */ + } /* end while directory entries left to read */ + closedir(dp); + + if (best_rep) { + format = best_rep; + myEncoding = best_enc; + base[-1] = '/'; /* Restore directory name */ + base[0] = '\0'; + StrAllocCat(localname, best_name); + FREE(best_name); + } else { /* If not found suitable file */ + FREE(localname); + FREE(nodename); + return HTLoadError(sink, 403, FAILED_NO_REPRESENTATION); + } + /*NOTREACHED */ + } + /* if multi suffix */ + /* + * Check to see if the 'localname' is in fact a directory. If it is + * create a new hypertext object containing a list of files and + * subdirectories contained in the directory. All of these are links + * to the directories or files listed. NB This assumes the existence + * of a type 'STRUCT_DIRENT', which will hold the directory entry, and + * a type 'DIR' which is used to point to the current directory being + * read. + */ +#if defined(USE_DOS_DRIVES) + if (strlen(localname) == 2 && LYIsDosDrive(localname)) + LYAddPathSep(&localname); +#endif + if (HTStat(localname, &dir_info) == -1) /* get file information */ + { + /* if can't read file information */ + CTRACE((tfp, "HTLoadFile: can't stat %s\n", localname)); + + } else { /* Stat was OK */ + + if (S_ISDIR(dir_info.st_mode)) { + /* + * If localname is a directory. + */ + DIR *dp; + struct stat file_info; + + CTRACE((tfp, "%s is a directory\n", localname)); + + /* + * Check directory access. Selective access means only those + * directories containing a marker file can be browsed. + */ + if (HTDirAccess == HT_DIR_FORBID) { + FREE(localname); + FREE(nodename); + return HTLoadError(sink, 403, DISALLOWED_DIR_SCAN); + } + + if (HTDirAccess == HT_DIR_SELECTIVE) { + char *enable_file_name = NULL; + + HTSprintf0(&enable_file_name, "%s/%s", localname, HT_DIR_ENABLE_FILE); + if (stat(enable_file_name, &file_info) != 0) { + FREE(localname); + FREE(nodename); + FREE(enable_file_name); + return HTLoadError(sink, 403, DISALLOWED_SELECTIVE_ACCESS); + } + } + + CTRACE((tfp, "Opening directory %s\n", localname)); + dp = opendir(localname); + if (!dp) { + FREE(localname); + FREE(nodename); + return HTLoadError(sink, 403, FAILED_DIR_UNREADABLE); + } + + /* + * Directory access is allowed and possible. + */ + + status = print_local_dir(dp, localname, + anchor, format_out, sink); + closedir(dp); + FREE(localname); + FREE(nodename); + return status; /* document loaded, maybe partial */ + + } + /* end if localname is a directory */ + if (S_ISREG(dir_info.st_mode)) { +#ifdef LONG_MAX + if (dir_info.st_size <= LONG_MAX) +#endif + anchor->content_length = (long) dir_info.st_size; + } + + } /* end if file stat worked */ + +/* End of directory reading section +*/ +#endif /* HAVE_READDIR */ + if (decompressAndParse(anchor, + format_out, + sink, + nodename, + localname, + myEncoding, + format, + &status)) { + FREE(nodename); + FREE(localname); + return status; + } + FREE(localname); + } /* local unix file system */ +#endif /* !NO_UNIX_IO */ +#endif /* VMS */ + +#ifndef DECNET + /* + * Now, as transparently mounted access has failed, we try FTP. + */ + { + /* + * Deal with case-sensitivity differences on VMS versus Unix. + */ +#ifdef VMS + if (strcasecomp(nodename, HTHostName()) != 0) +#else + if (strcmp(nodename, HTHostName()) != 0) +#endif /* VMS */ + { + status = -1; + FREE(nodename); + if (StrNCmp(addr, "file://localhost", 16)) { + /* never go to ftp site when URL + * is file://localhost + */ +#ifndef DISABLE_FTP + status = HTFTPLoad(addr, anchor, format_out, sink); +#endif /* DISABLE_FTP */ + } + return status; + } + FREE(nodename); + } +#endif /* !DECNET */ + + /* + * All attempts have failed. + */ + { + CTRACE((tfp, "Can't open `%s', errno=%d\n", addr, SOCKET_ERRNO)); + + return HTLoadError(sink, 403, FAILED_FILE_UNREADABLE); + } +} + +static const char *program_paths[pp_Last]; + +#if defined(UNIX) || defined(_WINDOWS) +#ifndef X_OK +#define X_OK 0 +#endif +static int VerifyProgram(const char *path) +{ + int result = 0; + struct stat info; + + if (stat(path, &info) == 0) { + if (S_ISREG(info.st_mode) && + info.st_size != 0 && + access(path, X_OK) == 0) { + result = 1; + } else { + result = -1; + } + } + return result; +} +#endif + +/* + * Given a program number, return its path + */ +const char *HTGetProgramPath(ProgramPaths code) +{ + const char *result = NULL; + + if (code > ppUnknown && code < pp_Last) { + result = program_paths[code]; +#if defined(UNIX) || defined(_WINDOWS) +#define VERIFY_FMT "not a program: %s\n" +#define EXISTS_FMT "no file found: %s\n" + if (result != NULL) { + char *path; + + switch (VerifyProgram(result)) { + case -1: + CTRACE((tfp, VERIFY_FMT, result)); + result = NULL; + break; + case 0: + if (*result == '/') { + CTRACE((tfp, EXISTS_FMT, result)); + result = NULL; + } else if ((path = getenv("PATH")) != NULL) { + int check = 0; + char *list = NULL; + + StrAllocCopy(list, path); + if (list != NULL) { + char *item; + + path = list; + while ((item = strtok(path, PATH_SEPARATOR)) != NULL) { + path = NULL; + if (*item != '\0') { + char *full = NULL; + + HTSprintf0(&full, "%s%s%s", + item, FILE_SEPARATOR, result); + check = VerifyProgram(full); + free(full); + if (check != 0) + break; + } + } + free(list); + } + if (check <= 0) { + CTRACE((tfp, check ? VERIFY_FMT : EXISTS_FMT, result)); + result = NULL; + } + } + break; + default: + break; + } + } +#endif + } + return result; +} + +/* + * Store a program's path. The caller must allocate the string used for 'path', + * since HTInitProgramPaths() may free it. + */ +void HTSetProgramPath(ProgramPaths code, const char *path) +{ + if (code > ppUnknown && code < pp_Last) { + program_paths[code] = isEmpty(path) ? 0 : path; + } +} + +/* + * Reset the list of known program paths to the ones that are compiled-in + */ +void HTInitProgramPaths(BOOL init) +{ + ProgramPaths code; + int n; + const char *path; + const char *test; + + for (n = (int) ppUnknown + 1; n < (int) pp_Last; ++n) { + switch (code = (ProgramPaths) n) { +#ifdef BROTLI_PATH + case ppBROTLI: + path = BROTLI_PATH; + break; +#endif +#ifdef BZIP2_PATH + case ppBZIP2: + path = BZIP2_PATH; + break; +#endif +#ifdef CHMOD_PATH + case ppCHMOD: + path = CHMOD_PATH; + break; +#endif +#ifdef COMPRESS_PATH + case ppCOMPRESS: + path = COMPRESS_PATH; + break; +#endif +#ifdef COPY_PATH + case ppCOPY: + path = COPY_PATH; + break; +#endif +#ifdef CSWING_PATH + case ppCSWING: + path = CSWING_PATH; + break; +#endif +#ifdef GZIP_PATH + case ppGZIP: + path = GZIP_PATH; + break; +#endif +#ifdef INFLATE_PATH + case ppINFLATE: + path = INFLATE_PATH; + break; +#endif +#ifdef INSTALL_PATH + case ppINSTALL: + path = INSTALL_PATH; + break; +#endif +#ifdef MKDIR_PATH + case ppMKDIR: + path = MKDIR_PATH; + break; +#endif +#ifdef MV_PATH + case ppMV: + path = MV_PATH; + break; +#endif +#ifdef RLOGIN_PATH + case ppRLOGIN: + path = RLOGIN_PATH; + break; +#endif +#ifdef RM_PATH + case ppRM: + path = RM_PATH; + break; +#endif +#ifdef RMDIR_PATH + case ppRMDIR: + path = RMDIR_PATH; + break; +#endif +#ifdef SETFONT_PATH + case ppSETFONT: + path = SETFONT_PATH; + break; +#endif +#ifdef TAR_PATH + case ppTAR: + path = TAR_PATH; + break; +#endif +#ifdef TELNET_PATH + case ppTELNET: + path = TELNET_PATH; + break; +#endif +#ifdef TN3270_PATH + case ppTN3270: + path = TN3270_PATH; + break; +#endif +#ifdef TOUCH_PATH + case ppTOUCH: + path = TOUCH_PATH; + break; +#endif +#ifdef UNCOMPRESS_PATH + case ppUNCOMPRESS: + path = UNCOMPRESS_PATH; + break; +#endif +#ifdef UNZIP_PATH + case ppUNZIP: + path = UNZIP_PATH; + break; +#endif +#ifdef UUDECODE_PATH + case ppUUDECODE: + path = UUDECODE_PATH; + break; +#endif +#ifdef ZCAT_PATH + case ppZCAT: + path = ZCAT_PATH; + break; +#endif +#ifdef ZIP_PATH + case ppZIP: + path = ZIP_PATH; + break; +#endif + default: + path = NULL; + break; + } + test = HTGetProgramPath(code); + if (test != NULL && test != path) { + free(DeConst(test)); + } + if (init) { + HTSetProgramPath(code, path); + } + } +} + +/* + * Protocol descriptors + */ +#ifdef GLOBALDEF_IS_MACRO +#define _HTFILE_C_1_INIT { "ftp", HTLoadFile, 0 } +GLOBALDEF(HTProtocol, HTFTP, _HTFILE_C_1_INIT); +#define _HTFILE_C_2_INIT { "file", HTLoadFile, HTFileSaveStream } +GLOBALDEF(HTProtocol, HTFile, _HTFILE_C_2_INIT); +#else +GLOBALDEF HTProtocol HTFTP = +{"ftp", HTLoadFile, 0}; +GLOBALDEF HTProtocol HTFile = +{"file", HTLoadFile, HTFileSaveStream}; +#endif /* GLOBALDEF_IS_MACRO */ diff --git a/WWW/Library/Implementation/HTFile.h b/WWW/Library/Implementation/HTFile.h new file mode 100644 index 0000000..9340963 --- /dev/null +++ b/WWW/Library/Implementation/HTFile.h @@ -0,0 +1,372 @@ +/* + * $LynxId: HTFile.h,v 1.37 2024/05/27 10:08:57 tom Exp $ + * File access in libwww + * FILE ACCESS + * + * These are routines for local file access used by WWW browsers and servers. + * Implemented by HTFile.c. + * + * If the file is not a local file, then we pass it on to HTFTP in case it + * can be reached by FTP. + */ +#ifndef HTFILE_H +#define HTFILE_H + +#include +#include + +#ifndef HTML_H +#include /* SCW */ +#endif /* HTML_H */ + +#ifdef __cplusplus +extern "C" { +#endif +/* + * Controlling globals + * + * These flags control how directories and files are represented as + * hypertext, and are typically set by the application from command + * line options, etc. + */ extern int HTDirAccess; + /* Directory access level */ + +#define HT_DIR_FORBID 0 /* Altogether forbidden */ +#define HT_DIR_SELECTIVE 1 /* If HT_DIR_ENABLE_FILE exists */ +#define HT_DIR_OK 2 /* Any accessible directory */ + +#define HT_DIR_ENABLE_FILE ".www_browsable" /* If exists, can browse */ + + extern int HTDirReadme; /* Include readme files in listing? */ + + /* Values: */ +#define HT_DIR_README_NONE 0 /* No */ +#define HT_DIR_README_TOP 1 /* Yes, first */ +#define HT_DIR_README_BOTTOM 2 /* Yes, at the end */ + +#define HT_DIR_README_FILE "README" + +/* + * Convert filenames between local and WWW formats + */ + extern char *HTURLPath_toFile(const char *name, int expand_all, int is_remote); + extern char *HTnameOfFile_WWW(const char *name, int WWW_prefix, int expand_all); + +#define HTLocalName(name) HTnameOfFile_WWW(name,TRUE,TRUE) +#define HTfullURL_toFile(name) HTnameOfFile_WWW(name,FALSE,TRUE) +#define HTpartURL_toFile(name) HTnameOfFile_WWW(name,FALSE,FALSE) + +/* + * Make a WWW name from a full local path name + */ + extern char *WWW_nameOfFile(const char *name); + +/* + * Generate the name of a cache file + */ + extern char *HTCacheFileName(const char *name); + +/* + * Generate fragments of HTML for source-view: + */ + extern void HTStructured_doctype(HTStructured * target, HTFormat format_out); + + extern void HTStructured_meta(HTStructured * target, HTFormat format_out); +/* + * Output directory titles + * + * This is (like the next one) used by HTFTP. It is common code to generate + * the title and heading 1 and the parent directory link for any anchor. + * + * changed to return TRUE if parent directory link was generated, + * FALSE otherwise - KW + */ + extern BOOL HTDirTitles(HTStructured * target, HTParentAnchor *anchor, + HTFormat format_out, + int tildeIsTop); + +/* + * Check existence. + */ + extern int HTStat(const char *filename, + struct stat *data); + +/* Load a document. + * ---------------- + */ + extern int HTLoadFile(const char *addr, + HTParentAnchor *anchor, + HTFormat format_out, + HTStream *sink); + +/* + * Output a directory entry + * + * This is used by HTFTP.c for example -- it is a common routine for + * generating a linked directory entry. + */ + extern void HTDirEntry(HTStructured * target, /* in which to put the linked text */ const char *tail, /* last part of directory name */ + const char *entry); /* name of this entry */ + +/* + * HTSetSuffix: Define the representation for a file suffix + * + * This defines a mapping between local file suffixes and file content + * types and encodings. + * + * ON ENTRY, + * + * suffix includes the "." if that is important (normally, yes!) + * + * representation is MIME-style content-type + * + * encoding is MIME-style content-transfer-encoding + * (8bit, 7bit, etc) or HTTP-style content-encoding + * (gzip, compress etc.) + * + * quality an a priori judgement of the quality of such files + * (0.0..1.0) + * + * HTSetSuffix5 has one more parameter for a short description of the type + * which is otherwise derived from the representation: + * + * desc is a short textual description, or NULL + * + * Examples: HTSetSuffix(".ps", "application/postscript", "8bit", 1.0); + * Examples: HTSetSuffix(".psz", "application/postscript", "gzip", 1.0); + * A MIME type could also indicate a non-trivial encoding on its own + * ("application/x-compressed-tar"), but in that case don't use encoding + * to also indicate it but use "binary" etc. + */ + extern void HTSetSuffix5(const char *suffix, + const char *representation, + const char *encoding, + const char *desc, + double quality); + +#define HTSetSuffix(suff,rep,enc,q) HTSetSuffix5(suff, rep, enc, NULL, q) + +/* + * HTFileFormat: Get Representation and Encoding from file name. + * + * ON EXIT, + * + * return The representation it imagines the file is in. + * + * *pEncoding The encoding (binary, 7bit, etc). See HTSetSuffix. + */ + extern HTFormat HTFileFormat(const char *filename, + HTAtom **pEncoding, + const char **pDesc); + +/* + * HTCharsetFormat: Revise the file format in relation to the Lynx charset. + * + * This checks the format associated with an anchor for + * for an extended MIME Content-Type, and if a charset is + * indicated, sets Lynx up for proper handling in relation + * to the currently selected character set. - FM + */ + extern HTFormat HTCharsetFormat(HTFormat format, + HTParentAnchor *anchor, + int default_LYhndl); + +/* Get various pieces of meta info from file name. + * ----------------------------------------------- + * + * LYGetFileInfo fills in information that can be determined without + * an actual (new) access to the filesystem, based on current suffix + * and character set configuration. If the file has been loaded and + * parsed before (with the same URL generated here!) and the anchor + * is still around, some results may be influenced by that (in + * particular, charset info from a META tag - this is not actually + * tested!). + * The caller should not keep pointers to the returned objects around + * for too long, the valid lifetimes vary. In particular, the returned + * charset string should be copied if necessary. If return of the + * file_anchor is requested, that one can be used to retrieve + * additional bits of info that are stored in the anchor object and + * are not covered here; as usual, don't keep pointers to the + * file_anchor longer than necessary since the object may disappear + * through HTuncache_current_document or at the next document load. + * - kw + */ + extern void LYGetFileInfo(const char *filename, + HTParentAnchor **pfile_anchor, + HTFormat *pformat, + HTAtom **pencoding, + const char **pdesc, + const char **pcharset, + int *pfile_cs); + +/* + * Determine file value from file name. + */ + extern float HTFileValue(const char *filename); + +/* + * Known compression types. + */ + typedef enum { + cftNone + ,cftCompress + ,cftGzip + ,cftBzip2 + ,cftDeflate + ,cftBrotli + } CompressFileType; + +/* + * Determine compression type from file name, by looking at its suffix. + */ + extern CompressFileType HTCompressFileType(const char *filename, + const char *dots, + int *rootlen); + +/* + * Determine compression type from the content-encoding. + */ + extern CompressFileType HTEncodingToCompressType(const char *encoding); +/* + * Determine compression type from the content-encoding. + */ + extern CompressFileType HTContentTypeToCompressType(const char *ct); +/* + * Check if a given content-encoding (format) matches something we support. + */ + extern BOOL IsCompressionFormat(HTAtom *format, CompressFileType check); +/* + * Determine compression type from the content-type and/or content-encoding. + */ + extern CompressFileType HTContentToCompressType(HTParentAnchor *anchor); +/* + * Determine compression encoding from the compression method. + */ + extern const char *HTCompressTypeToEncoding(CompressFileType method); +/* + * Determine expected file-suffix from the compression method. + */ + extern const char *HTCompressTypeToSuffix(CompressFileType method); +/* + * Determine write access to a file. + * + * ON EXIT, + * + * return value YES if file can be accessed and can be written to. + * + * BUGS + * + * Isn't there a quicker way? + */ + +#if defined(HAVE_CONFIG_H) + +#ifndef HAVE_GETGROUPS +#define NO_GROUPS +#endif + +#else + +#ifdef VMS +#define NO_GROUPS +#endif /* VMS */ +#ifdef NO_UNIX_IO +#define NO_GROUPS +#endif /* NO_UNIX_IO */ +#ifdef PCNFS +#define NO_GROUPS +#endif /* PCNFS */ +#ifdef NOUSERS +#define NO_GROUPS +#endif /* PCNFS */ + +#endif /* HAVE_CONFIG_H */ + + extern BOOL HTEditable(const char *filename); + +/* Make a save stream. + * ------------------- + */ + extern HTStream *HTFileSaveStream(HTParentAnchor *anchor); + +/* + * Determine a suitable suffix, given the representation. + * + * ON ENTRY, + * + * rep is the atomized MIME style representation + * enc is an encoding (8bit, binary, gzip, compress,..) + * + * ON EXIT, + * + * returns a pointer to a suitable suffix string if one has + * been found, else NULL. + */ + extern const char *HTFileSuffix(HTAtom *rep, + const char *enc); + +/* + * Enumerate external programs that lynx may assume exists. Unlike those + * given in download scripts, etc., lynx would really like to know their + * absolute paths, for better security. + */ + typedef enum { + ppUnknown = 0 + ,ppBROTLI + ,ppBZIP2 + ,ppCHMOD + ,ppCOMPRESS + ,ppCOPY + ,ppCSWING + ,ppGZIP + ,ppINFLATE + ,ppINSTALL + ,ppMKDIR + ,ppMV + ,ppRLOGIN + ,ppRM + ,ppRMDIR + ,ppSETFONT + ,ppTAR + ,ppTELNET + ,ppTN3270 + ,ppTOUCH + ,ppUNCOMPRESS + ,ppUNZIP + ,ppUUDECODE + ,ppZCAT + ,ppZIP + ,pp_Last + } ProgramPaths; + +/* + * Given a program number, return its path + */ + extern const char *HTGetProgramPath(ProgramPaths code); + +/* + * Store a program's path + */ + extern void HTSetProgramPath(ProgramPaths code, + const char *path); + +/* + * Reset the list of known program paths to the ones that are compiled-in + */ + extern void HTInitProgramPaths(BOOL init); + +/* + * The Protocols + */ +#ifdef GLOBALREF_IS_MACRO + extern GLOBALREF (HTProtocol, HTFTP); + extern GLOBALREF (HTProtocol, HTFile); + +#else + GLOBALREF HTProtocol HTFTP, HTFile; +#endif /* GLOBALREF_IS_MACRO */ + +#ifdef __cplusplus +} +#endif +#endif /* HTFILE_H */ diff --git a/WWW/Library/Implementation/HTFinger.c b/WWW/Library/Implementation/HTFinger.c new file mode 100644 index 0000000..566761a --- /dev/null +++ b/WWW/Library/Implementation/HTFinger.c @@ -0,0 +1,418 @@ +/* + * $LynxId: HTFinger.c,v 1.31 2013/11/28 11:27:50 tom Exp $ + * + * FINGER ACCESS HTFinger.c + * ============= + * Authors: + * ARB Andrew Brooks + * + * History: + * 21 Apr 94 First version (ARB, from HTNews.c by TBL) + * 12 Mar 96 Made the URL and command buffering secure from + * stack modifications, beautified the HTLoadFinger() + * and response() functions, and added support for the + * following URL formats for sending a "", "/w", + * "username[@host]", or "/w username[@host]" command + * to the server: + * finger://host + * finger://host/ + * finger://host/%2fw + * finger://host/%2fw%20username[@host] + * finger://host/w/username[@host] + * finger://host/username[@host] + * finger://host/username[@host]/w + * finger://username@host + * finger://username@host/ + * finger://username@host/w + * 15 Mar 96 Added support for port 79 gtype 0 gopher URLs + * relayed from HTLoadGopher. - FM + */ + +#include + +#ifndef DISABLE_FINGER + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define FINGER_PORT 79 /* See rfc742 */ +#define BIG 1024 /* Bug */ + +#define PUTC(c) (*targetClass.put_character)(target, c) +#define PUTS(s) (*targetClass.put_string)(target, s) +#define START(e) (*targetClass.start_element)(target, e, 0, 0, -1, 0) +#define END(e) (*targetClass.end_element)(target, e, 0) +#define FREE_TARGET (*targetClass._free)(target) +#define NEXT_CHAR HTGetCharacter() + +/* Module-wide variables +*/ +static int finger_fd; /* Socket for FingerHost */ + +struct _HTStructured { + const HTStructuredClass *isa; /* For gopher streams */ + /* ... */ +}; + +static HTStructured *target; /* The output sink */ +static HTStructuredClass targetClass; /* Copy of fn addresses */ + +/* Initialisation for this module + * ------------------------------ + */ +static BOOL initialized = NO; +static BOOL initialize(void) +{ + finger_fd = -1; /* Disconnected */ + return YES; +} + +/* Start anchor element + * -------------------- + */ +static void start_anchor(const char *href) +{ + BOOL present[HTML_A_ATTRIBUTES]; + const char *value[HTML_A_ATTRIBUTES]; + + { + int i; + + for (i = 0; i < HTML_A_ATTRIBUTES; i++) + present[i] = (BOOL) (i == HTML_A_HREF); + } + ((const char **) value)[HTML_A_HREF] = href; + (*targetClass.start_element) (target, HTML_A, present, + (const char **) value, -1, 0); + +} + +/* Send Finger Command line to remote host & Check Response + * -------------------------------------------------------- + * + * On entry, + * command points to the command to be sent, including CRLF, or is null + * pointer if no command to be sent. + * On exit, + * Negative status indicates transmission error, socket closed. + * Positive status is a Finger status. + */ + +static int response(char *command, + char *sitename, + HTParentAnchor *anAnchor, + HTFormat format_out, + HTStream *sink) +{ + int status; + int length = (int) strlen(command); + int ch, i; + char line[BIG], *l, *cmd = NULL; + char *p = line, *href = NULL; + + if (length == 0) + return (-1); + + /* Set up buffering. + */ + HTInitInput(finger_fd); + + /* Send the command. + */ + CTRACE((tfp, "HTFinger command to be sent: %s", command)); + status = (int) NETWRITE(finger_fd, (char *) command, (unsigned) length); + if (status < 0) { + CTRACE((tfp, "HTFinger: Unable to send command. Disconnecting.\n")); + NETCLOSE(finger_fd); + finger_fd = -1; + return status; + } + /* if bad status */ + /* Make a hypertext object with an anchor list. + */ + target = HTML_new(anAnchor, format_out, sink); + targetClass = *target->isa; /* Copy routine entry points */ + + /* Create the results report. + */ + CTRACE((tfp, "HTFinger: Reading finger information\n")); + START(HTML_HTML); + PUTC('\n'); + START(HTML_HEAD); + PUTC('\n'); + START(HTML_TITLE); + PUTS("Finger server on "); + PUTS(sitename); + END(HTML_TITLE); + PUTC('\n'); + END(HTML_HEAD); + PUTC('\n'); + START(HTML_BODY); + PUTC('\n'); + START(HTML_H1); + PUTS("Finger server on "); + START(HTML_EM); + PUTS(sitename); + END(HTML_EM); + PUTS(": "); + StrAllocCopy(cmd, command); + for (i = ((int) strlen(cmd) - 1); i >= 0; i--) { + if (cmd[i] == LF || cmd[i] == CR) { + cmd[i] = '\0'; + } else { + break; + } + } + PUTS(cmd); + FREE(cmd); + END(HTML_H1); + PUTC('\n'); + START(HTML_PRE); + + while ((ch = NEXT_CHAR) != EOF) { + + if (interrupted_in_htgetcharacter) { + CTRACE((tfp, + "HTFinger: Interrupted in HTGetCharacter, apparently.\n")); + _HTProgress(CONNECTION_INTERRUPTED); + goto end_html; + } + + if (ch != LF) { + *p = (char) ch; /* Put character in line */ + if (p < &line[BIG - 1]) { + p++; + } + } else { + *p = '\0'; /* Terminate line */ + /* + * OK we now have a line. + * Load it as 'l' and parse it. + */ + p = l = line; + while (*l) { + if (StrNCmp(l, STR_NEWS_URL, LEN_NEWS_URL) && + StrNCmp(l, "snews://", 8) && + StrNCmp(l, "nntp://", 7) && + StrNCmp(l, "snewspost:", 10) && + StrNCmp(l, "snewsreply:", 11) && + StrNCmp(l, "newspost:", 9) && + StrNCmp(l, "newsreply:", 10) && + StrNCmp(l, "ftp://", 6) && + StrNCmp(l, "file:/", 6) && + StrNCmp(l, "finger://", 9) && + StrNCmp(l, "http://", 7) && + StrNCmp(l, "https://", 8) && + StrNCmp(l, "wais://", 7) && + StrNCmp(l, STR_MAILTO_URL, LEN_MAILTO_URL) && + StrNCmp(l, "cso://", 6) && + StrNCmp(l, "gopher://", 9)) + PUTC(*l++); + else { + StrAllocCopy(href, l); + start_anchor(strtok(href, " \r\n\t,>)\"")); + while (*l && !StrChr(" \r\n\t,>)\"", *l)) + PUTC(*l++); + END(HTML_A); + FREE(href); + } + } + PUTC('\n'); + } + } + NETCLOSE(finger_fd); + finger_fd = -1; + + end_html: + END(HTML_PRE); + PUTC('\n'); + END(HTML_BODY); + PUTC('\n'); + END(HTML_HTML); + PUTC('\n'); + FREE_TARGET; + return (0); +} + +/* Load by name HTLoadFinger + * ============ + */ +int HTLoadFinger(const char *arg, + HTParentAnchor *anAnchor, + HTFormat format_out, + HTStream *stream) +{ + static char empty[1]; + + char *username, *sitename; /* Fields extracted from URL */ + char *slash, *at_sign; /* Fields extracted from URL */ + char *command, *str, *param; /* Buffers */ + int port; /* Port number from URL */ + int status; /* tcp return */ + int result = HT_LOADED; + BOOL IsGopherURL = FALSE; + const char *p1 = arg; + + CTRACE((tfp, "HTFinger: Looking for %s\n", (arg ? arg : "NULL"))); + + if (!(arg && *arg)) { + HTAlert(COULD_NOT_LOAD_DATA); + return HT_NOT_LOADED; /* Ignore if no name */ + } + + if (!initialized) + initialized = initialize(); + if (!initialized) { + HTAlert(gettext("Could not set up finger connection.")); + return HT_NOT_LOADED; /* FAIL */ + } + + /* Set up the host and command fields. + */ + if (!strncasecomp(arg, "finger://", 9)) { + p1 = arg + 9; /* Skip "finger://" prefix */ + } else if (!strncasecomp(arg, "gopher://", 9)) { + p1 = arg + 9; /* Skip "gopher://" prefix */ + IsGopherURL = TRUE; + } + + param = 0; + sitename = StrAllocCopy(param, p1); + if (param == 0) { + HTAlert(COULD_NOT_LOAD_DATA); + return HT_NOT_LOADED; + } else if ((slash = StrChr(sitename, '/')) != NULL) { + *slash++ = '\0'; + HTUnEscape(slash); + if (IsGopherURL) { + if (*slash != '0') { + HTAlert(COULD_NOT_LOAD_DATA); + return HT_NOT_LOADED; /* FAIL */ + } + *slash++ = '\0'; + } + } + + if ((at_sign = StrChr(sitename, '@')) != NULL) { + if (IsGopherURL) { + HTAlert(COULD_NOT_LOAD_DATA); + return HT_NOT_LOADED; /* FAIL */ + } else { + *at_sign++ = '\0'; + username = sitename; + sitename = at_sign; + HTUnEscape(username); + } + } else if (slash) { + username = slash; + } else { + username = empty; + } + + if (*sitename == '\0') { + HTAlert(gettext("Could not load data (no sitename in finger URL)")); + result = HT_NOT_LOADED; /* Ignore if no name */ + } else if (HTParsePort(sitename, &port) != NULL) { + if (port != 79) { + HTAlert(gettext("Invalid port number - will only use port 79!")); + result = HT_NOT_LOADED; /* Ignore if wrong port */ + } + } + + if (result == HT_LOADED) { + /* Load the string for making a connection/ + */ + str = 0; + HTSprintf0(&str, "lose://%s/", sitename); + + /* Load the command for the finger server. + */ + command = 0; + if (at_sign && slash) { + if (*slash == 'w' || *slash == 'W') { + HTSprintf0(&command, "/w %s%c%c", username, CR, LF); + } else { + HTSprintf0(&command, "%s%c%c", username, CR, LF); + } + } else if (at_sign) { + HTSprintf0(&command, "%s%c%c", username, CR, LF); + } else if (*username == '/') { + if ((slash = StrChr((username + 1), '/')) != NULL) { + *slash = ' '; + } + HTSprintf0(&command, "%s%c%c", username, CR, LF); + } else if ((*username == 'w' || *username == 'W') && + *(username + 1) == '/') { + if (*username + 2 != '\0') { + *(username + 1) = ' '; + } else { + *(username + 1) = '\0'; + } + HTSprintf0(&command, "/%s%c%c", username, CR, LF); + } else if ((*username == 'w' || *username == 'W') && + *(username + 1) == '\0') { + HTSprintf0(&command, "/%s%c%c", username, CR, LF); + } else if ((slash = StrChr(username, '/')) != NULL) { + *slash++ = '\0'; + if (*slash == 'w' || *slash == 'W') { + HTSprintf0(&command, "/w %s%c%c", username, CR, LF); + } else { + HTSprintf0(&command, "%s%c%c", username, CR, LF); + } + } else { + HTSprintf0(&command, "%s%c%c", username, CR, LF); + } + + /* Now, let's get a stream setup up from the FingerHost: + * CONNECTING to finger host + */ + CTRACE((tfp, "HTFinger: doing HTDoConnect on '%s'\n", str)); + status = HTDoConnect(str, "finger", FINGER_PORT, &finger_fd); + CTRACE((tfp, "HTFinger: Done DoConnect; status %d\n", status)); + + if (status == HT_INTERRUPTED) { + /* Interrupt cleanly */ + CTRACE((tfp, + "HTFinger: Interrupted on connect; recovering cleanly.\n")); + HTProgress(CONNECTION_INTERRUPTED); + result = HT_NOT_LOADED; + } else if (status < 0) { + NETCLOSE(finger_fd); + finger_fd = -1; + CTRACE((tfp, "HTFinger: Unable to connect to finger host.\n")); + HTAlert(gettext("Could not access finger host.")); + result = HT_NOT_LOADED; /* FAIL */ + } else { + CTRACE((tfp, "HTFinger: Connected to finger host '%s'.\n", str)); + + /* Send the command, and process response if successful. + */ + if (response(command, sitename, anAnchor, format_out, stream) != 0) { + HTAlert(gettext("No response from finger server.")); + result = HT_NOT_LOADED; + } + } + FREE(str); + FREE(command); + } + FREE(param); + return result; +} + +#ifdef GLOBALDEF_IS_MACRO +#define _HTFINGER_C_1_INIT { "finger", HTLoadFinger, NULL } +GLOBALDEF(HTProtocol, HTFinger, _HTFINGER_C_1_INIT); +#else +GLOBALDEF HTProtocol HTFinger = +{"finger", HTLoadFinger, NULL}; +#endif /* GLOBALDEF_IS_MACRO */ + +#endif /* not DISABLE_FINGER */ diff --git a/WWW/Library/Implementation/HTFinger.h b/WWW/Library/Implementation/HTFinger.h new file mode 100644 index 0000000..071d43b --- /dev/null +++ b/WWW/Library/Implementation/HTFinger.h @@ -0,0 +1,30 @@ +/* Finger protocol module for the WWW library */ +/* History: + * 21 Apr 94 Andrew Brooks + */ + +#ifndef HTFINGER_H +#define HTFINGER_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif +#ifdef GLOBALREF_IS_MACRO + extern GLOBALREF (HTProtocol, HTFinger); + +#else + GLOBALREF HTProtocol HTFinger; +#endif /* GLOBALREF_IS_MACRO */ + + extern int HTLoadFinger(const char *arg, + HTParentAnchor *anAnchor, + HTFormat format_out, + HTStream *stream); + +#ifdef __cplusplus +} +#endif +#endif /* HTFINGER_H */ diff --git a/WWW/Library/Implementation/HTFormat.c b/WWW/Library/Implementation/HTFormat.c new file mode 100644 index 0000000..198ea51 --- /dev/null +++ b/WWW/Library/Implementation/HTFormat.c @@ -0,0 +1,2181 @@ +/* + * $LynxId: HTFormat.c,v 1.97 2024/04/11 20:19:35 tom Exp $ + * + * Manage different file formats HTFormat.c + * ============================= + * + * Bugs: + * Not reentrant. + * + * Assumes the incoming stream is ASCII, rather than a local file + * format, and so ALWAYS converts from ASCII on non-ASCII machines. + * Therefore, non-ASCII machines can't read local files. + * + */ + +#define HTSTREAM_INTERNAL 1 + +#include + +/* Implements: +*/ +#include + +static float HTMaxSecs = 1e10; /* No effective limit */ + +#ifdef UNIX +#ifdef NeXT +#define PRESENT_POSTSCRIPT "open %s; /bin/rm -f %s\n" +#else +#define PRESENT_POSTSCRIPT "(ghostview %s ; /bin/rm -f %s)&\n" + /* Full pathname would be better! */ +#endif /* NeXT */ +#endif /* UNIX */ + +#include +#include +#include +#include +#include +#include +#include +#include +/* Streams and structured streams which we use: +*/ +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifdef DISP_PARTIAL +#include +#endif + +#ifdef USE_BROTLI +#include +#endif + +BOOL HTOutputSource = NO; /* Flag: shortcut parser to stdout */ + +/* this version used by the NetToText stream */ +struct _HTStream { + const HTStreamClass *isa; + BOOL had_cr; + HTStream *sink; +}; + +/* Presentation methods + * -------------------- + */ +HTList *HTPresentations = NULL; +HTPresentation *default_presentation = NULL; + +/* + * To free off the presentation list. + */ +#ifdef LY_FIND_LEAKS +static void HTFreePresentations(void); +#endif + +/* Define a presentation system command for a content-type + * ------------------------------------------------------- + */ +void HTSetPresentation(const char *representation, + const char *command, + const char *testcommand, + double quality, + double secs, + double secs_per_byte, + long int maxbytes, + AcceptMedia media) +{ + HTPresentation *pres = typecalloc(HTPresentation); + + if (pres == NULL) + outofmem(__FILE__, "HTSetPresentation"); + + assert(representation != NULL); + + CTRACE2(TRACE_CFG, + (tfp, + "HTSetPresentation rep=%s, command=%s, test=%s, qual=%f\n", + NonNull(representation), + NonNull(command), + NonNull(testcommand), + quality)); + + pres->rep = HTAtom_for(representation); + pres->rep_out = WWW_PRESENT; /* Fixed for now ... :-) */ + pres->converter = HTSaveAndExecute; /* Fixed for now ... */ + pres->quality = (float) quality; + pres->secs = (float) secs; + pres->secs_per_byte = (float) secs_per_byte; + pres->maxbytes = maxbytes; + pres->get_accept = 0; + pres->accept_opt = media; + + pres->command = NULL; + StrAllocCopy(pres->command, command); + + pres->testcommand = NULL; + StrAllocCopy(pres->testcommand, testcommand); + + /* + * Memory leak fixed. + * 05-28-94 Lynx 2-3-1 Garrett Arch Blythe + */ + if (!HTPresentations) { + HTPresentations = HTList_new(); +#ifdef LY_FIND_LEAKS + atexit(HTFreePresentations); +#endif + } + + if (strcmp(representation, "*") == 0) { + FREE(default_presentation); + default_presentation = pres; + } else { + HTList_addObject(HTPresentations, pres); + } +} + +/* Define a built-in function for a content-type + * --------------------------------------------- + */ +void HTSetConversion(const char *representation_in, + const char *representation_out, + HTConverter *converter, + double quality, + double secs, + double secs_per_byte, + long int maxbytes, + AcceptMedia media) +{ + HTPresentation *pres = typecalloc(HTPresentation); + + if (pres == NULL) + outofmem(__FILE__, "HTSetConversion"); + + CTRACE2(TRACE_CFG, + (tfp, + "HTSetConversion rep_in=%s, rep_out=%s, qual=%f\n", + NonNull(representation_in), + NonNull(representation_out), + quality)); + + pres->rep = HTAtom_for(representation_in); + pres->rep_out = HTAtom_for(representation_out); + pres->converter = converter; + pres->command = NULL; + pres->testcommand = NULL; + pres->quality = (float) quality; + pres->secs = (float) secs; + pres->secs_per_byte = (float) secs_per_byte; + pres->maxbytes = maxbytes; + pres->get_accept = TRUE; + pres->accept_opt = media; + + /* + * Memory Leak fixed. + * 05-28-94 Lynx 2-3-1 Garrett Arch Blythe + */ + if (!HTPresentations) { + HTPresentations = HTList_new(); +#ifdef LY_FIND_LEAKS + atexit(HTFreePresentations); +#endif + } + + HTList_addObject(HTPresentations, pres); +} + +#ifdef LY_FIND_LEAKS +/* + * Purpose: Free the presentation list. + * Arguments: void + * Return Value: void + * Remarks/Portability/Dependencies/Restrictions: + * Made to clean up Lynx's bad leakage. + * Revision History: + * 05-28-94 created Lynx 2-3-1 Garrett Arch Blythe + */ +static void HTFreePresentations(void) +{ + HTPresentation *pres = NULL; + + /* + * Loop through the list. + */ + while (!HTList_isEmpty(HTPresentations)) { + /* + * Free off each item. May also need to free off it's items, but not + * sure as of yet. + */ + pres = (HTPresentation *) HTList_removeLastObject(HTPresentations); + FREE(pres->command); + FREE(pres->testcommand); + FREE(pres); + } + /* + * Free the list itself. + */ + HTList_delete(HTPresentations); + HTPresentations = NULL; +} +#endif /* LY_FIND_LEAKS */ + +/* File buffering + * -------------- + * + * The input file is read using the macro which can read from + * a socket or a file. + * The input buffer size, if large will give greater efficiency and + * release the server faster, and if small will save space on PCs etc. + */ +#define INPUT_BUFFER_SIZE 4096 /* Tradeoff */ +static char input_buffer[INPUT_BUFFER_SIZE]; +static char *input_pointer; +static char *input_limit; +static int input_file_number; + +/* Set up the buffering + * + * These routines are public because they are in fact needed by + * many parsers, and on PCs and Macs we should not duplicate + * the static buffer area. + */ +void HTInitInput(int file_number) +{ + input_file_number = file_number; + input_pointer = input_limit = input_buffer; +} + +int interrupted_in_htgetcharacter = 0; +int HTGetCharacter(void) +{ + char ch; + + interrupted_in_htgetcharacter = 0; + do { + if (input_pointer >= input_limit) { + int status = NETREAD(input_file_number, + input_buffer, INPUT_BUFFER_SIZE); + + if (status <= 0) { + if (status == 0) + return EOF; + if (status == HT_INTERRUPTED) { + CTRACE((tfp, "HTFormat: Interrupted in HTGetCharacter\n")); + interrupted_in_htgetcharacter = 1; + return EOF; + } + CTRACE((tfp, "HTFormat: File read error %d\n", status)); + return EOF; /* -1 is returned by UCX + at end of HTTP link */ + } + input_pointer = input_buffer; + input_limit = input_buffer + status; + } + ch = *input_pointer++; + } while (ch == (char) 13); /* Ignore ASCII carriage return */ + + return FROMASCII(UCH(ch)); +} + +#ifdef USE_SSL +int HTGetSSLCharacter(void *handle) +{ + char ch; + + interrupted_in_htgetcharacter = 0; + if (!handle) + return (char) EOF; + do { + if (input_pointer >= input_limit) { + int status = SSL_read((SSL *) handle, + input_buffer, INPUT_BUFFER_SIZE); + + if (status <= 0) { + if (status == 0) + return (char) EOF; + if (status == HT_INTERRUPTED) { + CTRACE((tfp, + "HTFormat: Interrupted in HTGetSSLCharacter\n")); + interrupted_in_htgetcharacter = 1; + return (char) EOF; + } + CTRACE((tfp, "HTFormat: SSL_read error %d\n", status)); + return (char) EOF; /* -1 is returned by UCX + at end of HTTP link */ + } + input_pointer = input_buffer; + input_limit = input_buffer + status; + } + ch = *input_pointer++; + } while (ch == (char) 13); /* Ignore ASCII carriage return */ + + return FROMASCII(ch); +} +#endif /* USE_SSL */ + +/* Match maintype to any MIME type starting with maintype, for example: + * image/gif should match image + */ +static int half_match(char *trial_type, char *target) +{ + char *cp = StrChr(trial_type, '/'); + + /* if no '/' or no '*' */ + if (!cp || *(cp + 1) != '*') + return 0; + + CTRACE((tfp, "HTFormat: comparing %s and %s for half match\n", + trial_type, target)); + + /* main type matches */ + if (!StrNCmp(trial_type, target, ((cp - trial_type) - 1))) + return 1; + + return 0; +} + +/* + * Evaluate a deferred mailcap test command, i.e.,. one that substitutes the + * document's charset or other values in %{name} format. + */ +static BOOL failsMailcap(HTPresentation *pres, HTParentAnchor *anchor) +{ + if (pres->testcommand != NULL && + anchor != NULL && + anchor->content_type_params != NULL) { + if (LYTestMailcapCommand(pres->testcommand, + anchor->content_type_params) != 0) + return TRUE; + } + return FALSE; +} + +#define WWW_WILDCARD_REP_OUT HTAtom_for("*") + +/* Look up a presentation + * ---------------------- + * + * If fill_in is NULL, only look for an exact match. + * If a wildcard match is made, *fill_in is used to store + * a possibly modified presentation, and a pointer to it is + * returned. For an exact match, a pointer to the presentation + * in the HTPresentations list is returned. Returns NULL if + * nothing found. - kw + * + */ +static HTPresentation *HTFindPresentation(HTFormat rep_in, + HTFormat rep_out, + HTPresentation *fill_in, + HTParentAnchor *anchor) +{ +#undef THIS_FUNC +#define THIS_FUNC "HTFindPresentation" + HTAtom *wildcard = NULL; /* = HTAtom_for("*"); lookup when needed - kw */ + int n; + int i; + HTPresentation *pres; + HTPresentation *match; + HTPresentation *strong_wildcard_match = 0; + HTPresentation *weak_wildcard_match = 0; + HTPresentation *last_default_match = 0; + HTPresentation *strong_subtype_wildcard_match = 0; + + CTRACE((tfp, THIS_FUNC ": Looking up presentation for %s to %s\n", + HTAtom_name(rep_in), HTAtom_name(rep_out))); + + n = HTList_count(HTPresentations); + for (i = 0; i < n; i++) { + pres = (HTPresentation *) HTList_objectAt(HTPresentations, i); + if (pres->rep == rep_in) { + if (pres->rep_out == rep_out) { + if (failsMailcap(pres, anchor)) + continue; + CTRACE((tfp, THIS_FUNC ": found exact match: %s -> %s\n", + HTAtom_name(pres->rep), + HTAtom_name(pres->rep_out))); + return pres; + + } else if (!fill_in) { + continue; + } else { + if (!wildcard) + wildcard = WWW_WILDCARD_REP_OUT; + if (pres->rep_out == wildcard) { + if (failsMailcap(pres, anchor)) + continue; + if (!strong_wildcard_match) + strong_wildcard_match = pres; + /* otherwise use the first one */ + CTRACE((tfp, THIS_FUNC + ": found strong wildcard match: %s -> %s\n", + HTAtom_name(pres->rep), + HTAtom_name(pres->rep_out))); + } + } + + } else if (!fill_in) { + continue; + + } else if (half_match(HTAtom_name(pres->rep), + HTAtom_name(rep_in))) { + if (pres->rep_out == rep_out) { + if (failsMailcap(pres, anchor)) + continue; + if (!strong_subtype_wildcard_match) + strong_subtype_wildcard_match = pres; + /* otherwise use the first one */ + CTRACE((tfp, THIS_FUNC + ": found strong subtype wildcard match: %s -> %s\n", + HTAtom_name(pres->rep), + HTAtom_name(pres->rep_out))); + } + } + + if (pres->rep == WWW_SOURCE) { + if (pres->rep_out == rep_out) { + if (failsMailcap(pres, anchor)) + continue; + if (!weak_wildcard_match) + weak_wildcard_match = pres; + /* otherwise use the first one */ + CTRACE((tfp, + THIS_FUNC ": found weak wildcard match: %s\n", + HTAtom_name(pres->rep_out))); + + } else if (!last_default_match) { + if (!wildcard) + wildcard = WWW_WILDCARD_REP_OUT; + if (pres->rep_out == wildcard) { + if (failsMailcap(pres, anchor)) + continue; + last_default_match = pres; + /* otherwise use the first one */ + } + } + } + } + + match = (strong_subtype_wildcard_match + ? strong_subtype_wildcard_match + : (strong_wildcard_match + ? strong_wildcard_match + : (weak_wildcard_match + ? weak_wildcard_match + : last_default_match))); + + if (match) { + *fill_in = *match; /* Specific instance */ + fill_in->rep = rep_in; /* yuk */ + fill_in->rep_out = rep_out; /* yuk */ + return fill_in; + } + + return NULL; +#undef THIS_FUNC +} + +/* Create a filter stack + * --------------------- + * + * If a wildcard match is made, a temporary HTPresentation + * structure is made to hold the destination format while the + * new stack is generated. This is just to pass the out format to + * MIME so far. Storing the format of a stream in the stream might + * be a lot neater. + * + */ +HTStream *HTStreamStack(HTFormat rep_in, + HTFormat rep_out, + HTStream *sink, + HTParentAnchor *anchor) +{ +#undef THIS_FUNC +#define THIS_FUNC "HTStreamStack" + HTPresentation temp; + HTPresentation *match; + HTStream *result; + + CTRACE((tfp, THIS_FUNC ": Constructing stream stack for %s to %s (%s)\n", + HTAtom_name(rep_in), + HTAtom_name(rep_out), + NONNULL(anchor->content_type_params))); + + if (rep_out == rep_in) { + result = sink; + + } else if ((match = HTFindPresentation(rep_in, rep_out, &temp, anchor))) { + if (match == &temp) { + CTRACE((tfp, THIS_FUNC ": Using %s\n", HTAtom_name(temp.rep_out))); + } else { + CTRACE((tfp, THIS_FUNC ": found exact match: %s -> %s\n", + HTAtom_name(match->rep), + HTAtom_name(match->rep_out))); + } + result = (*match->converter) (match, anchor, sink); + } else { + result = NULL; + } + if (TRACE) { + if (result && result->isa && result->isa->name) { + CTRACE((tfp, THIS_FUNC ": Returning \"%s\"\n", result->isa->name)); + } else if (result) { + CTRACE((tfp, THIS_FUNC ": Returning *unknown* stream!\n")); + } else { + CTRACE((tfp, THIS_FUNC ": Returning NULL!\n")); + CTRACE_FLUSH(tfp); /* a crash may be imminent... - kw */ + } + } + return result; +#undef THIS_FUNC +} + +/* Put a presentation near start of list + * ------------------------------------- + * + * Look up a presentation (exact match only) and, if found, reorder + * it to the start of the HTPresentations list. - kw + */ +void HTReorderPresentation(HTFormat rep_in, + HTFormat rep_out) +{ + HTPresentation *match; + + if ((match = HTFindPresentation(rep_in, rep_out, NULL, NULL))) { + HTList_removeObject(HTPresentations, match); + HTList_addObject(HTPresentations, match); + } +} + +/* + * Setup 'get_accept' flag to denote presentations that are not redundant, + * and will be listed in "Accept:" header. + */ +void HTFilterPresentations(void) +{ + int i, j; + int n = HTList_count(HTPresentations); + HTPresentation *p, *q; + BOOL matched; + char *s, *t; + + CTRACE((tfp, "HTFilterPresentations (AcceptMedia %#x)\n", LYAcceptMedia)); + for (i = 0; i < n; i++) { + p = (HTPresentation *) HTList_objectAt(HTPresentations, i); + s = HTAtom_name(p->rep); + + p->get_accept = FALSE; + if ((LYAcceptMedia & p->accept_opt) != 0 + && p->rep_out == WWW_PRESENT + && p->rep != WWW_SOURCE + && strcasecomp(s, "www/mime") + && strcasecomp(s, "www/compressed") + && p->quality <= 1.0 && p->quality >= 0.0) { + matched = TRUE; + for (j = 0; j < i; j++) { + q = (HTPresentation *) HTList_objectAt(HTPresentations, j); + t = HTAtom_name(q->rep); + + if (!strcasecomp(s, t)) { + matched = FALSE; + CTRACE((tfp, " match %s %s\n", s, t)); + break; + } + } + p->get_accept = matched; + } + } +} + +/* Find the cost of a filter stack + * ------------------------------- + * + * Must return the cost of the same stack which HTStreamStack would set up. + * + * On entry, + * length The size of the data to be converted + */ +float HTStackValue(HTFormat rep_in, + HTFormat rep_out, + double initial_value, + long int length) +{ + HTAtom *wildcard = WWW_WILDCARD_REP_OUT; + + CTRACE((tfp, "HTFormat: Evaluating stream stack for %s worth %.3f to %s\n", + HTAtom_name(rep_in), initial_value, HTAtom_name(rep_out))); + + if (rep_out == WWW_SOURCE || rep_out == rep_in) + return 0.0; + + { + int n = HTList_count(HTPresentations); + int i; + HTPresentation *pres; + + for (i = 0; i < n; i++) { + pres = (HTPresentation *) HTList_objectAt(HTPresentations, i); + if (pres->rep == rep_in && + (pres->rep_out == rep_out || pres->rep_out == wildcard)) { + float value = (float) (initial_value * pres->quality); + + if (HTMaxSecs > 0.0) + value = (value + - ((float) length * pres->secs_per_byte + + pres->secs) + / HTMaxSecs); + return value; + } + } + } + + return (float) -1e30; /* Really bad */ + +} + +/* Display the page while transfer in progress + * ------------------------------------------- + * + * Repaint the page only when necessary. + * This is a traverse call for HText_pageDisplay() - it works!. + * + */ +void HTDisplayPartial(void) +{ +#ifdef DISP_PARTIAL + if (display_partial) { + /* + * HText_getNumOfLines() = "current" number of complete lines received + * NumOfLines_partial = number of lines at the moment of last repaint. + * (we update NumOfLines_partial only when we repaint the display.) + * + * display_partial could only be enabled in HText_new() so a new + * HTMainText object available - all HText_ functions use it, lines + * counter HText_getNumOfLines() in particular. + * + * Otherwise HTMainText holds info from the previous document and we + * may repaint it instead of the new one: prev doc scrolled to the + * first line (=Newline_partial) is not good looking :-) 23 Aug 1998 + * Leonid Pauzner + * + * So repaint the page only when necessary: + */ + int Newline_partial = LYGetNewline(); + + if (((Newline_partial + display_lines) - 1 > NumOfLines_partial) + /* current page not complete... */ + && (partial_threshold > 0 ? + ((Newline_partial + partial_threshold) - 1 <= + HText_getNumOfLines()) : + ((Newline_partial + display_lines) - 1 <= HText_getNumOfLines())) + /* + * Originally we rendered by increments of 2 lines, + * but that got annoying on slow network connections. + * Then we switched to full-pages. Now it's configurable. + * If partial_threshold <= 0, then it's a full page + */ + ) { + if (LYMainLoop_pageDisplay(Newline_partial)) + NumOfLines_partial = HText_getNumOfLines(); + } + } +#else /* nothing */ +#endif /* DISP_PARTIAL */ +} + +/* Put this as early as possible, OK just after HTDisplayPartial() */ +void HTFinishDisplayPartial(void) +{ +#ifdef DISP_PARTIAL + /* + * End of incremental rendering stage here. + */ + display_partial = FALSE; +#endif /* DISP_PARTIAL */ +} + +/* Push data from a socket down a stream + * ------------------------------------- + * + * This routine is responsible for creating and PRESENTING any + * graphic (or other) objects described by the file. + * + * The file number given is assumed to be a TELNET stream, i.e., containing + * CRLF at the end of lines which need to be stripped to LF for unix + * when the format is textual. + * + * State of socket and target stream on entry: + * socket (file_number) assumed open, + * target (sink) assumed valid. + * + * Return values: + * HT_INTERRUPTED Interruption or error after some data received. + * -2 Unexpected disconnect before any data received. + * -1 Interruption or error before any data received, or + * (UNIX) other read error before any data received, or + * download cancelled. + * HT_LOADED Normal close of socket (end of file indication + * received), or + * unexpected disconnect after some data received, or + * other read error after some data received, or + * (not UNIX) other read error before any data received. + * + * State of socket and target stream on return depends on return value: + * HT_INTERRUPTED socket still open, target aborted. + * -2 socket still open, target stream still valid. + * -1 socket still open, target aborted. + * otherwise socket closed, target stream still valid. + */ +int HTCopy(HTParentAnchor *anchor, + int file_number, + void *handle GCC_UNUSED, + HTStream *sink) +{ + HTStreamClass targetClass; + BOOL suppress_readprogress = NO; + off_t limit = anchor ? anchor->content_length : 0; + off_t bytes = 0; + off_t header_length = 0; + int rv = 0; + + /* Push the data down the stream + */ + targetClass = *(sink->isa); /* Copy pointers to procedures */ + + /* + * Push binary from socket down sink + * + * This operation could be put into a main event loop + */ + HTReadProgress(bytes, (off_t) 0); + for (;;) { + int status; + + if (LYCancelDownload) { + LYCancelDownload = FALSE; + (*targetClass._abort) (sink, NULL); + rv = -1; + goto finished; + } + + if (HTCheckForInterrupt()) { + _HTProgress(TRANSFER_INTERRUPTED); + (*targetClass._abort) (sink, NULL); + if (bytes) + rv = HT_INTERRUPTED; + else + rv = -1; + goto finished; + } +#ifdef USE_SSL + if (handle) + status = SSL_read((SSL *) handle, input_buffer, INPUT_BUFFER_SIZE); + else + status = NETREAD(file_number, input_buffer, INPUT_BUFFER_SIZE); +#else + status = NETREAD(file_number, input_buffer, INPUT_BUFFER_SIZE); +#endif /* USE_SSL */ + if (status <= 0) { + if (status == 0) { + break; + } else if (status == HT_INTERRUPTED) { + _HTProgress(TRANSFER_INTERRUPTED); + (*targetClass._abort) (sink, NULL); + if (bytes) + rv = HT_INTERRUPTED; + else + rv = -1; + goto finished; + } else if (SOCKET_ERRNO == ENOTCONN || +#ifdef _WINDOWS /* 1997/11/10 (Mon) 16:57:18 */ + SOCKET_ERRNO == ETIMEDOUT || +#endif + SOCKET_ERRNO == ECONNRESET || + SOCKET_ERRNO == EPIPE) { + /* + * Arrrrgh, HTTP 0/1 compatibility problem, maybe. + */ + if (bytes <= 0) { + /* + * Don't have any data, so let the calling function decide + * what to do about it. - FM + */ + rv = -2; + goto finished; + } else { +#ifdef UNIX + /* + * Treat what we've received already as the complete + * transmission, but not without giving the user an alert. + * I don't know about all the different TCP stacks for VMS + * etc., so this is currently only for UNIX. - kw + */ + HTInetStatus("NETREAD"); + HTAlert("Unexpected server disconnect."); + CTRACE((tfp, + "HTCopy: Unexpected server disconnect. Treating as completed.\n")); +#else /* !UNIX */ + /* + * Treat what we've gotten already as the complete + * transmission. - FM + */ + CTRACE((tfp, + "HTCopy: Unexpected server disconnect. Treating as completed.\n")); + status = 0; +#endif /* UNIX */ + } +#ifdef UNIX + } else { /* status < 0 and other errno */ + /* + * Treat what we've received already as the complete + * transmission, but not without giving the user an alert. I + * don't know about all the different TCP stacks for VMS etc., + * so this is currently only for UNIX. - kw + */ + HTInetStatus("NETREAD"); + HTAlert("Unexpected read error."); + if (bytes) { + (void) NETCLOSE(file_number); + rv = HT_LOADED; + } else { + (*targetClass._abort) (sink, NULL); + rv = -1; + } + goto finished; +#endif + } + break; + } + + /* + * Suppress ReadProgress messages when collecting a redirection + * message, at least initially (unless/until anchor->content_type gets + * changed, probably by the MIME message parser). That way messages + * put up by the HTTP module or elsewhere can linger in the statusline + * for a while. - kw + */ + suppress_readprogress = (BOOL) (anchor && anchor->content_type && + !strcmp(anchor->content_type, + "message/x-http-redirection")); +#ifdef NOT_ASCII + { + char *p; + + for (p = input_buffer; p < input_buffer + status; p++) { + *p = FROMASCII(*p); + } + } +#endif /* NOT_ASCII */ + + header_length = anchor != 0 ? anchor->header_length : 0; + + (*targetClass.put_block) (sink, input_buffer, status); + if (anchor != 0 && anchor->inHEAD) { + if (!suppress_readprogress) { + statusline(gettext("Reading headers...")); + } + CTRACE((tfp, "HTCopy read %" PRI_off_t " header bytes\n", + CAST_off_t (anchor->header_length))); + } else { + /* + * If header-length is increased at this point, that is due to + * HTMIME, which detects the end of the server headers. There + * may be additional (non-header) data in that block. + */ + if (anchor != 0 && (anchor->header_length > header_length)) { + int header = (int) (anchor->header_length - header_length); + + CTRACE((tfp, "HTCopy read %" PRI_off_t " header bytes " + "(%d extra vs %d total)\n", + CAST_off_t (anchor->header_length), + header, status)); + if (status > header) { + bytes += (status - header); + } + } else { + bytes += status; + } + if (!suppress_readprogress) { + HTReadProgress(bytes, limit); + } + HTDisplayPartial(); + } + + /* a few buggy implementations do not close the connection properly + * and will hang if we try to read past the declared content-length. + */ + if (limit > 0 && bytes >= limit) + break; + } /* next bufferload */ + if (anchor != 0) { + CTRACE((tfp, "HTCopy copied %" + PRI_off_t " actual, %" + PRI_off_t " limit\n", CAST_off_t (bytes), CAST_off_t (limit))); + anchor->actual_length = bytes; + } + + _HTProgress(TRANSFER_COMPLETE); + (void) NETCLOSE(file_number); + rv = HT_LOADED; + + finished: + HTFinishDisplayPartial(); + return (rv); +} + +/* Push data from a file pointer down a stream + * ------------------------------------- + * + * This routine is responsible for creating and PRESENTING any + * graphic (or other) objects described by the file. + * + * + * State of file and target stream on entry: + * FILE* (fp) assumed open, + * target (sink) assumed valid. + * + * Return values: + * HT_INTERRUPTED Interruption after some data read. + * HT_PARTIAL_CONTENT Error after some data read. + * -1 Error before any data read. + * HT_LOADED Normal end of file indication on reading. + * + * State of file and target stream on return: + * always fp still open, target stream still valid. + */ +int HTFileCopy(FILE *fp, HTStream *sink) +{ + HTStreamClass targetClass; + int status; + off_t bytes; + int rv = HT_OK; + + /* Push the data down the stream + */ + targetClass = *(sink->isa); /* Copy pointers to procedures */ + + /* Push binary from socket down sink + */ + HTReadProgress(bytes = 0, (off_t) 0); + for (;;) { + status = (int) fread(input_buffer, + (size_t) 1, + (size_t) INPUT_BUFFER_SIZE, fp); + if (status == 0) { /* EOF or error */ + if (ferror(fp) == 0) { + rv = HT_LOADED; + break; + } + CTRACE((tfp, "HTFormat: Read error, read returns %d\n", + ferror(fp))); + if (bytes) { + rv = HT_PARTIAL_CONTENT; + } else { + rv = -1; + } + break; + } + + (*targetClass.put_block) (sink, input_buffer, status); + bytes += status; + HTReadProgress(bytes, (off_t) 0); + /* Suppress last screen update in partial mode - a regular update under + * control of mainloop() should follow anyway. - kw + */ +#ifdef DISP_PARTIAL + if (display_partial && bytes != HTMainAnchor->content_length) + HTDisplayPartial(); +#endif + + if (HTCheckForInterrupt()) { + _HTProgress(TRANSFER_INTERRUPTED); + if (bytes) { + rv = HT_INTERRUPTED; + } else { + rv = -1; + } + break; + } + } /* next bufferload */ + + HTFinishDisplayPartial(); + return rv; +} + +#ifdef USE_SOURCE_CACHE +/* Push data from an HTChunk down a stream + * --------------------------------------- + * + * This routine is responsible for creating and PRESENTING any + * graphic (or other) objects described by the file. + * + * State of memory and target stream on entry: + * HTChunk* (chunk) and target (sink) assumed valid. + * + * Return values: + * HT_LOADED All data sent. + * HT_INTERRUPTED Interruption after some data read. + * + * State of memory and target stream on return: + * always chunk unchanged, target stream still valid. + */ +int HTMemCopy(HTChunk *chunk, HTStream *sink) +{ + HTStreamClass targetClass; + off_t bytes; + int rv = HT_OK; + + targetClass = *(sink->isa); + HTReadProgress(bytes = 0, (off_t) 0); + for (; chunk != NULL; chunk = chunk->next) { + + /* Push the data down the stream a piece at a time, in case we're + * running a large document on a slow machine. + */ + (*targetClass.put_block) (sink, chunk->data, chunk->size); + bytes += chunk->size; + + HTReadProgress(bytes, (off_t) 0); + HTDisplayPartial(); + + if (HTCheckForInterrupt()) { + _HTProgress(TRANSFER_INTERRUPTED); + if (bytes) { + rv = HT_INTERRUPTED; + } else { + rv = -1; + } + break; + } + } + + HTFinishDisplayPartial(); + return rv; +} +#endif + +#ifdef USE_ZLIB +/* Push data from a gzip file pointer down a stream + * ------------------------------------- + * + * This routine is responsible for creating and PRESENTING any + * graphic (or other) objects described by the file. + * + * + * State of file and target stream on entry: + * gzFile (gzfp) assumed open (should have gzipped content), + * target (sink) assumed valid. + * + * Return values: + * HT_INTERRUPTED Interruption after some data read. + * HT_PARTIAL_CONTENT Error after some data read. + * -1 Error before any data read. + * HT_LOADED Normal end of file indication on reading. + * + * State of file and target stream on return: + * always gzfp still open, target stream still valid. + */ +static int HTGzFileCopy(gzFile gzfp, HTStream *sink) +{ + HTStreamClass targetClass; + int status; + off_t bytes; + int gzerrnum; + int rv = HT_OK; + + /* Push the data down the stream + */ + targetClass = *(sink->isa); /* Copy pointers to procedures */ + + /* read and inflate gzip'd file, and push binary down sink + */ + HTReadProgress(bytes = 0, (off_t) 0); + for (;;) { + status = gzread(gzfp, input_buffer, INPUT_BUFFER_SIZE); + if (status <= 0) { /* EOF or error */ + if (status == 0) { + rv = HT_LOADED; + break; + } + CTRACE((tfp, "HTGzFileCopy: Read error, gzread returns %d\n", + status)); + CTRACE((tfp, "gzerror : %s\n", + gzerror(gzfp, &gzerrnum))); + if (TRACE) { + if (gzerrnum == Z_ERRNO) + perror("gzerror "); + } + if (bytes) { + rv = HT_PARTIAL_CONTENT; + } else { + rv = -1; + } + break; + } + + (*targetClass.put_block) (sink, input_buffer, status); + bytes += status; + HTReadProgress(bytes, (off_t) -1); + HTDisplayPartial(); + + if (HTCheckForInterrupt()) { + _HTProgress(TRANSFER_INTERRUPTED); + rv = HT_INTERRUPTED; + break; + } + } /* next bufferload */ + + HTFinishDisplayPartial(); + return rv; +} + +#ifndef HAVE_ZERROR +#define zError(s) LynxZError(s) +static const char *zError(int status) +{ + static char result[80]; + + sprintf(result, "zlib error %d", status); + return result; +} +#endif + +/* Push data from a deflate file pointer down a stream + * ------------------------------------- + * + * This routine is responsible for creating and PRESENTING any + * graphic (or other) objects described by the file. The code is + * loosely based on the inflate.c file from w3m. + * + * + * State of file and target stream on entry: + * FILE (zzfp) assumed open (should have deflated content), + * target (sink) assumed valid. + * + * Return values: + * HT_INTERRUPTED Interruption after some data read. + * HT_PARTIAL_CONTENT Error after some data read. + * -1 Error before any data read. + * HT_LOADED Normal end of file indication on reading. + * + * State of file and target stream on return: + * always zzfp still open, target stream still valid. + */ +static int HTZzFileCopy(FILE *zzfp, HTStream *sink) +{ +#undef THIS_FUNC +#define THIS_FUNC "HTZzFileCopy" + static char dummy_head[1 + 1] = + { + 0x8 + 0x7 * 0x10, + (((0x8 + 0x7 * 0x10) * 0x100 + 30) / 31 * 31) & 0xFF, + }; + + z_stream s; + HTStreamClass targetClass; + off_t bytes; + int rv = HT_OK; + char output_buffer[INPUT_BUFFER_SIZE]; + int status; + int flush; + int retry = 0; + int len = 0; + + /* Push the data down the stream + */ + targetClass = *(sink->isa); /* Copy pointers to procedures */ + + memset(&s, 0, sizeof(s)); + status = inflateInit(&s); + if (status != Z_OK) { + CTRACE((tfp, THIS_FUNC " inflateInit() %s\n", zError(status))); + exit_immediately(EXIT_FAILURE); + } + s.avail_in = 0; + s.next_out = (Bytef *) output_buffer; + s.avail_out = sizeof(output_buffer); + flush = Z_NO_FLUSH; + + /* read and inflate deflate'd file, and push binary down sink + */ + HTReadProgress(bytes = 0, (off_t) 0); + for (;;) { + if (s.avail_in == 0) { + s.next_in = (Bytef *) input_buffer; + s.avail_in = (uInt) fread(input_buffer, + (size_t) 1, + (size_t) INPUT_BUFFER_SIZE, zzfp); + len = (int) s.avail_in; + } + status = inflate(&s, flush); + if (status == Z_STREAM_END || status == Z_BUF_ERROR) { + len = (int) sizeof(output_buffer) - (int) s.avail_out; + if (len > 0) { + (*targetClass.put_block) (sink, output_buffer, len); + bytes += len; + HTReadProgress(bytes, (off_t) -1); + HTDisplayPartial(); + } + rv = HT_LOADED; + break; + } else if (status == Z_DATA_ERROR && !retry++) { + status = inflateReset(&s); + if (status != Z_OK) { + CTRACE((tfp, THIS_FUNC " inflateReset() %s\n", zError(status))); + rv = -1; + break; + } + s.next_in = (Bytef *) dummy_head; + s.avail_in = sizeof(dummy_head); + (void) inflate(&s, flush); + s.next_in = (Bytef *) input_buffer; + s.avail_in = (unsigned) len; + continue; + } else if (status != Z_OK) { + CTRACE((tfp, THIS_FUNC " inflate() %s\n", zError(status))); + rv = bytes ? HT_PARTIAL_CONTENT : -1; + break; + } else if (s.avail_out == 0) { + len = sizeof(output_buffer); + s.next_out = (Bytef *) output_buffer; + s.avail_out = sizeof(output_buffer); + + (*targetClass.put_block) (sink, output_buffer, len); + bytes += len; + HTReadProgress(bytes, (off_t) -1); + HTDisplayPartial(); + + if (HTCheckForInterrupt()) { + _HTProgress(TRANSFER_INTERRUPTED); + rv = bytes ? HT_INTERRUPTED : -1; + break; + } + } + retry = 1; + } /* next bufferload */ + + inflateEnd(&s); + HTFinishDisplayPartial(); + return rv; +#undef THIS_FUNC +} +#endif /* USE_ZLIB */ + +#ifdef USE_BZLIB +/* Push data from a bzip file pointer down a stream + * ------------------------------------- + * + * This routine is responsible for creating and PRESENTING any + * graphic (or other) objects described by the file. + * + * + * State of file and target stream on entry: + * BZFILE (bzfp) assumed open (should have bzipped content), + * target (sink) assumed valid. + * + * Return values: + * HT_INTERRUPTED Interruption after some data read. + * HT_PARTIAL_CONTENT Error after some data read. + * -1 Error before any data read. + * HT_LOADED Normal end of file indication on reading. + * + * State of file and target stream on return: + * always bzfp still open, target stream still valid. + */ +static int HTBzFileCopy(BZFILE * bzfp, HTStream *sink) +{ + HTStreamClass targetClass; + int status; + off_t bytes; + int bzerrnum; + int rv = HT_OK; + + /* Push the data down the stream + */ + targetClass = *(sink->isa); /* Copy pointers to procedures */ + + /* read and inflate bzip'd file, and push binary down sink + */ + HTReadProgress(bytes = 0, (off_t) 0); + for (;;) { + status = BZ2_bzread(bzfp, input_buffer, INPUT_BUFFER_SIZE); + if (status <= 0) { /* EOF or error */ + if (status == 0) { + rv = HT_LOADED; + break; + } + CTRACE((tfp, "HTBzFileCopy: Read error, bzread returns %d\n", + status)); + CTRACE((tfp, "bzerror : %s\n", + BZ2_bzerror(bzfp, &bzerrnum))); + if (bytes) { + rv = HT_PARTIAL_CONTENT; + } else { + rv = -1; + } + break; + } + + (*targetClass.put_block) (sink, input_buffer, status); + bytes += status; + HTReadProgress(bytes, (off_t) -1); + HTDisplayPartial(); + + if (HTCheckForInterrupt()) { + _HTProgress(TRANSFER_INTERRUPTED); + rv = HT_INTERRUPTED; + break; + } + } /* next bufferload */ + + HTFinishDisplayPartial(); + return rv; +} +#endif /* USE_BZLIB */ + +#ifdef USE_BROTLI +/* Push data from a brotli file pointer down a stream + * ------------------------------------- + * + * This routine is responsible for creating and PRESENTING any + * graphic (or other) objects described by the file. + * + * + * State of file and target stream on entry: + * BZFILE (bzfp) assumed open (should have bzipped content), + * target (sink) assumed valid. + * + * Return values: + * HT_INTERRUPTED Interruption after some data read. + * HT_PARTIAL_CONTENT Error after some data read. + * -1 Error before any data read. + * HT_LOADED Normal end of file indication on reading. + * + * State of file and target stream on return: + * always bzfp still open, target stream still valid. + */ +static int HTBrFileCopy(FILE *brfp, HTStream *sink) +{ +#undef THIS_FUNC +#define THIS_FUNC "HTBrFileCopy" + HTStreamClass targetClass; + int status; + off_t bytes; + int rv = HT_OK; + BrotliDecoderResult status2 = BROTLI_DECODER_RESULT_ERROR; + + char *brotli_buffer = NULL; + char *normal_buffer = NULL; + size_t brotli_size; + size_t brotli_limit = 0; + size_t brotli_offset; + size_t normal_size; + size_t normal_limit = 0; + + /* Push the data down the stream + */ + targetClass = *(sink->isa); /* Copy pointers to procedures */ + + /* read and inflate brotli'd file, and push binary down sink + */ + HTReadProgress(bytes = 0, (off_t) 0); + /* + * first, read all of the brotli'd file into memory, to work with the + * library's limitations. + */ + for (;;) { + size_t input_chunk = INPUT_BUFFER_SIZE; + + brotli_offset = brotli_limit; + brotli_limit += input_chunk; + brotli_buffer = realloc(brotli_buffer, brotli_limit); + if (brotli_buffer == NULL) + outofmem(__FILE__, THIS_FUNC); + status = (int) fread(brotli_buffer + brotli_offset, sizeof(char), + input_chunk, brfp); + + if (status <= 0) { /* EOF or error */ + if (status == 0) { + rv = HT_LOADED; + break; + } + CTRACE((tfp, THIS_FUNC ": Read error, fread returns %d\n", status)); + if (bytes) { + if (!feof(brfp)) + rv = HT_PARTIAL_CONTENT; + } else { + rv = -1; + } + break; + } + bytes += status; + } + + /* + * next, unless we encountered an error (and have no data), try + * decompressing with increasing output buffer sizes until the brotli + * library succeeds. + */ + if (bytes > 0) { + do { + if (normal_limit == 0) + normal_limit = (10 * brotli_limit) + INPUT_BUFFER_SIZE; + else + normal_limit *= 2; + normal_buffer = realloc(normal_buffer, normal_limit); + if (normal_buffer == NULL) + outofmem(__FILE__, THIS_FUNC); + brotli_size = (size_t) bytes; + normal_size = normal_limit; + status2 = BrotliDecoderDecompress(brotli_size, + (uint8_t *) brotli_buffer, + &normal_size, + (uint8_t *) normal_buffer); + /* + * brotli library should return + * BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT, + * but actually returns + * BROTLI_DECODER_RESULT_ERROR + * + * Accommodate possible improvements... + */ + } while (status2 != BROTLI_DECODER_RESULT_SUCCESS); + } + + /* + * finally, pump that data into the output stream. + */ + if (status2 == BROTLI_DECODER_RESULT_SUCCESS) { + CTRACE((tfp, THIS_FUNC ": decompressed %ld -> %ld (1:%.1f)\n", + brotli_size, normal_size, + (double) normal_size / (double) brotli_size)); + (*targetClass.put_block) (sink, normal_buffer, (int) normal_size); + bytes += status; + HTReadProgress(bytes, (off_t) -1); + HTDisplayPartial(); + + if (HTCheckForInterrupt()) { + _HTProgress(TRANSFER_INTERRUPTED); + rv = HT_INTERRUPTED; + } + } + free(brotli_buffer); + free(normal_buffer); + + /* next bufferload */ + HTFinishDisplayPartial(); + return rv; +#undef THIS_FUNC +} +#endif /* USE_BZLIB */ + +/* Push data from a socket down a stream STRIPPING CR + * -------------------------------------------------- + * + * This routine is responsible for creating and PRESENTING any + * graphic (or other) objects described by the socket. + * + * The file number given is assumed to be a TELNET stream ie containing + * CRLF at the end of lines which need to be stripped to LF for unix + * when the format is textual. + * + */ +void HTCopyNoCR(HTParentAnchor *anchor GCC_UNUSED, + int file_number, + HTStream *sink) +{ + HTStreamClass targetClass; + int character; + + /* Push the data, ignoring CRLF, down the stream + */ + targetClass = *(sink->isa); /* Copy pointers to procedures */ + + /* + * Push text from telnet socket down sink + * + * @@@@@ To push strings could be faster? (especially is we cheat and + * don't ignore CR! :-} + */ + HTInitInput(file_number); + for (;;) { + character = HTGetCharacter(); + if (character == EOF) + break; + (*targetClass.put_character) (sink, (char) character); + } +} + +/* Parse a socket given format and file number + * + * This routine is responsible for creating and PRESENTING any + * graphic (or other) objects described by the file. + * + * The file number given is assumed to be a TELNET stream ie containing + * CRLF at the end of lines which need to be stripped to LF for unix + * when the format is textual. + * + * State of socket and target stream on entry: + * socket (file_number) assumed open, + * target (sink) usually NULL (will call stream stack). + * + * Return values: + * HT_INTERRUPTED Interruption or error after some data received. + * -501 Stream stack failed (cannot present or convert). + * -2 Unexpected disconnect before any data received. + * -1 Stream stack failed (cannot present or convert), or + * Interruption or error before any data received, or + * (UNIX) other read error before any data received, or + * download cancelled. + * HT_LOADED Normal close of socket (end of file indication + * received), or + * unexpected disconnect after some data received, or + * other read error after some data received, or + * (not UNIX) other read error before any data received. + * + * State of socket and target stream on return depends on return value: + * HT_INTERRUPTED socket still open, target aborted. + * -501 socket still open, target stream NULL. + * -2 socket still open, target freed. + * -1 socket still open, target stream aborted or NULL. + * otherwise socket closed, target stream freed. + */ +int HTParseSocket(HTFormat rep_in, + HTFormat format_out, + HTParentAnchor *anchor, + int file_number, + HTStream *sink) +{ + HTStream *stream; + HTStreamClass targetClass; + int rv; + + stream = HTStreamStack(rep_in, format_out, sink, anchor); + + if (!stream) { + char *buffer = 0; + + if (LYCancelDownload) { + LYCancelDownload = FALSE; + return -1; + } + HTSprintf0(&buffer, CANNOT_CONVERT_I_TO_O, + HTAtom_name(rep_in), HTAtom_name(format_out)); + CTRACE((tfp, "HTFormat: %s\n", buffer)); + rv = HTLoadError(sink, 501, buffer); /* returns -501 */ + FREE(buffer); + } else { + /* + * Push the data, don't worry about CRLF we can strip them later. + */ + targetClass = *(stream->isa); /* Copy pointers to procedures */ + rv = HTCopy(anchor, file_number, NULL, stream); + if (rv != -1 && rv != HT_INTERRUPTED) + (*targetClass._free) (stream); + } + return rv; + /* Originally: full: HT_LOADED; partial: HT_INTERRUPTED; no bytes: -1 */ +} + +/* Parse a file given format and file pointer + * + * This routine is responsible for creating and PRESENTING any + * graphic (or other) objects described by the file. + * + * The file number given is assumed to be a TELNET stream ie containing + * CRLF at the end of lines which need to be stripped to \n for unix + * when the format is textual. + * + * State of file and target stream on entry: + * FILE* (fp) assumed open, + * target (sink) usually NULL (will call stream stack). + * + * Return values: + * -501 Stream stack failed (cannot present or convert). + * -1 Download cancelled. + * HT_NO_DATA Error before any data read. + * HT_PARTIAL_CONTENT Interruption or error after some data read. + * HT_LOADED Normal end of file indication on reading. + * + * State of file and target stream on return: + * always fp still open; target freed, aborted, or NULL. + */ +int HTParseFile(HTFormat rep_in, + HTFormat format_out, + HTParentAnchor *anchor, + FILE *fp, + HTStream *sink) +{ + HTStream *stream; + HTStreamClass targetClass; + int rv; + int result; + + if (fp == NULL) { + result = HT_LOADED; + } else { + stream = HTStreamStack(rep_in, format_out, sink, anchor); + + if (!stream || !stream->isa) { + char *buffer = 0; + + if (LYCancelDownload) { + LYCancelDownload = FALSE; + result = -1; + } else { + HTSprintf0(&buffer, CANNOT_CONVERT_I_TO_O, + HTAtom_name(rep_in), HTAtom_name(format_out)); + CTRACE((tfp, "HTFormat(in HTParseFile): %s\n", buffer)); + rv = HTLoadError(sink, 501, buffer); + FREE(buffer); + result = rv; + } + } else { + + /* + * Push the data down the stream + * + * @@ Bug: This decision ought to be made based on "encoding" + * rather than on content-type. @@@ When we handle encoding. The + * current method smells anyway. + */ + targetClass = *(stream->isa); /* Copy pointers to procedures */ + rv = HTFileCopy(fp, stream); + if (rv == -1 || rv == HT_INTERRUPTED) { + (*targetClass._abort) (stream, NULL); + } else { + (*targetClass._free) (stream); + } + + if (rv == -1) { + result = HT_NO_DATA; + } else if (rv == HT_INTERRUPTED || (rv > 0 && rv != HT_LOADED)) { + result = HT_PARTIAL_CONTENT; + } else { + result = HT_LOADED; + } + } + } + return result; +} + +#ifdef USE_SOURCE_CACHE +/* Parse a document in memory given format and memory block pointer + * + * This routine is responsible for creating and PRESENTING any + * graphic (or other) objects described by the file. + * + * State of memory and target stream on entry: + * HTChunk* (chunk) assumed valid, + * target (sink) usually NULL (will call stream stack). + * + * Return values: + * -501 Stream stack failed (cannot present or convert). + * HT_LOADED All data sent. + * + * State of memory and target stream on return: + * always chunk unchanged; target freed, aborted, or NULL. + */ +int HTParseMem(HTFormat rep_in, + HTFormat format_out, + HTParentAnchor *anchor, + HTChunk *chunk, + HTStream *sink) +{ + HTStream *stream; + HTStreamClass targetClass; + int rv; + int result; + + stream = HTStreamStack(rep_in, format_out, sink, anchor); + if (!stream || !stream->isa) { + char *buffer = 0; + + HTSprintf0(&buffer, CANNOT_CONVERT_I_TO_O, + HTAtom_name(rep_in), HTAtom_name(format_out)); + CTRACE((tfp, "HTFormat(in HTParseMem): %s\n", buffer)); + rv = HTLoadError(sink, 501, buffer); + FREE(buffer); + result = rv; + } else { + + /* Push the data down the stream + */ + targetClass = *(stream->isa); + (void) HTMemCopy(chunk, stream); + (*targetClass._free) (stream); + result = HT_LOADED; + } + return result; +} +#endif + +#ifdef USE_ZLIB +static int HTCloseGzFile(gzFile gzfp) +{ + int gzres; + + if (gzfp == NULL) + return 0; + gzres = gzclose(gzfp); + if (TRACE) { + if (gzres == Z_ERRNO) { + perror("gzclose "); + } else if (gzres != Z_OK) { + CTRACE((tfp, "gzclose : error number %d\n", gzres)); + } + } + return (gzres); +} + +/* HTParseGzFile + * + * State of file and target stream on entry: + * gzFile (gzfp) assumed open, + * target (sink) usually NULL (will call stream stack). + * + * Return values: + * -501 Stream stack failed (cannot present or convert). + * -1 Download cancelled. + * HT_NO_DATA Error before any data read. + * HT_PARTIAL_CONTENT Interruption or error after some data read. + * HT_LOADED Normal end of file indication on reading. + * + * State of file and target stream on return: + * always gzfp closed; target freed, aborted, or NULL. + */ +int HTParseGzFile(HTFormat rep_in, + HTFormat format_out, + HTParentAnchor *anchor, + gzFile gzfp, + HTStream *sink) +{ + HTStream *stream; + HTStreamClass targetClass; + int rv; + int result; + + stream = HTStreamStack(rep_in, format_out, sink, anchor); + + if (!stream || !stream->isa) { + char *buffer = 0; + + HTCloseGzFile(gzfp); + if (LYCancelDownload) { + LYCancelDownload = FALSE; + result = -1; + } else { + HTSprintf0(&buffer, CANNOT_CONVERT_I_TO_O, + HTAtom_name(rep_in), HTAtom_name(format_out)); + CTRACE((tfp, "HTFormat(in HTParseGzFile): %s\n", buffer)); + rv = HTLoadError(sink, 501, buffer); + FREE(buffer); + result = rv; + } + } else { + + /* + * Push the data down the stream + * + * @@ Bug: This decision ought to be made based on "encoding" rather than + * on content-type. @@@ When we handle encoding. The current method + * smells anyway. + */ + targetClass = *(stream->isa); /* Copy pointers to procedures */ + rv = HTGzFileCopy(gzfp, stream); + if (rv == -1 || rv == HT_INTERRUPTED) { + (*targetClass._abort) (stream, NULL); + } else { + (*targetClass._free) (stream); + } + + HTCloseGzFile(gzfp); + if (rv == -1) { + result = HT_NO_DATA; + } else if (rv == HT_INTERRUPTED || (rv > 0 && rv != HT_LOADED)) { + result = HT_PARTIAL_CONTENT; + } else { + result = HT_LOADED; + } + } + return result; +} + +/* HTParseZzFile + * + * State of file and target stream on entry: + * FILE (zzfp) assumed open, + * target (sink) usually NULL (will call stream stack). + * + * Return values: + * -501 Stream stack failed (cannot present or convert). + * -1 Download cancelled. + * HT_NO_DATA Error before any data read. + * HT_PARTIAL_CONTENT Interruption or error after some data read. + * HT_LOADED Normal end of file indication on reading. + * + * State of file and target stream on return: + * always zzfp closed; target freed, aborted, or NULL. + */ +int HTParseZzFile(HTFormat rep_in, + HTFormat format_out, + HTParentAnchor *anchor, + FILE *zzfp, + HTStream *sink) +{ + HTStream *stream; + HTStreamClass targetClass; + int rv; + int result; + + stream = HTStreamStack(rep_in, format_out, sink, anchor); + + if (!stream || !stream->isa) { + char *buffer = 0; + + fclose(zzfp); + if (LYCancelDownload) { + LYCancelDownload = FALSE; + result = -1; + } else { + HTSprintf0(&buffer, CANNOT_CONVERT_I_TO_O, + HTAtom_name(rep_in), HTAtom_name(format_out)); + CTRACE((tfp, "HTFormat(in HTParseGzFile): %s\n", buffer)); + rv = HTLoadError(sink, 501, buffer); + FREE(buffer); + result = rv; + } + } else { + + /* + * Push the data down the stream + * + * @@ Bug: This decision ought to be made based on "encoding" rather than + * on content-type. @@@ When we handle encoding. The current method + * smells anyway. + */ + targetClass = *(stream->isa); /* Copy pointers to procedures */ + rv = HTZzFileCopy(zzfp, stream); + if (rv == -1 || rv == HT_INTERRUPTED) { + (*targetClass._abort) (stream, NULL); + } else { + (*targetClass._free) (stream); + } + + fclose(zzfp); + if (rv == -1) { + result = HT_NO_DATA; + } else if (rv == HT_INTERRUPTED || (rv > 0 && rv != HT_LOADED)) { + result = HT_PARTIAL_CONTENT; + } else { + result = HT_LOADED; + } + } + return result; +} +#endif /* USE_ZLIB */ + +#ifdef USE_BZLIB +static void HTCloseBzFile(BZFILE * bzfp) +{ + if (bzfp) + BZ2_bzclose(bzfp); +} + +/* HTParseBzFile + * + * State of file and target stream on entry: + * bzFile (bzfp) assumed open, + * target (sink) usually NULL (will call stream stack). + * + * Return values: + * -501 Stream stack failed (cannot present or convert). + * -1 Download cancelled. + * HT_NO_DATA Error before any data read. + * HT_PARTIAL_CONTENT Interruption or error after some data read. + * HT_LOADED Normal end of file indication on reading. + * + * State of file and target stream on return: + * always bzfp closed; target freed, aborted, or NULL. + */ +int HTParseBzFile(HTFormat rep_in, + HTFormat format_out, + HTParentAnchor *anchor, + BZFILE * bzfp, + HTStream *sink) +{ + HTStream *stream; + HTStreamClass targetClass; + int rv; + int result; + + stream = HTStreamStack(rep_in, format_out, sink, anchor); + + if (!stream || !stream->isa) { + char *buffer = 0; + + HTCloseBzFile(bzfp); + if (LYCancelDownload) { + LYCancelDownload = FALSE; + result = -1; + } else { + HTSprintf0(&buffer, CANNOT_CONVERT_I_TO_O, + HTAtom_name(rep_in), HTAtom_name(format_out)); + CTRACE((tfp, "HTFormat(in HTParseBzFile): %s\n", buffer)); + rv = HTLoadError(sink, 501, buffer); + FREE(buffer); + result = rv; + } + } else { + + /* + * Push the data down the stream + * + * @@ Bug: This decision ought to be made based on "encoding" rather than + * on content-type. @@@ When we handle encoding. The current method + * smells anyway. + */ + targetClass = *(stream->isa); /* Copy pointers to procedures */ + rv = HTBzFileCopy(bzfp, stream); + if (rv == -1 || rv == HT_INTERRUPTED) { + (*targetClass._abort) (stream, NULL); + } else { + (*targetClass._free) (stream); + } + + HTCloseBzFile(bzfp); + if (rv == -1) { + result = HT_NO_DATA; + } else if (rv == HT_INTERRUPTED || (rv > 0 && rv != HT_LOADED)) { + result = HT_PARTIAL_CONTENT; + } else { + result = HT_LOADED; + } + } + return result; +} +#endif /* USE_BZLIB */ + +#ifdef USE_BROTLI +/* HTParseBrFile + * + * State of file and target stream on entry: + * FILE* (brfp) assumed open, + * target (sink) usually NULL (will call stream stack). + * + * Return values: + * -501 Stream stack failed (cannot present or convert). + * -1 Download cancelled. + * HT_NO_DATA Error before any data read. + * HT_PARTIAL_CONTENT Interruption or error after some data read. + * HT_LOADED Normal end of file indication on reading. + * + * State of file and target stream on return: + * always brfp closed; target freed, aborted, or NULL. + */ +int HTParseBrFile(HTFormat rep_in, + HTFormat format_out, + HTParentAnchor *anchor, + FILE *brfp, + HTStream *sink) +{ +#undef THIS_FUNC +#define THIS_FUNC "HTParseBrFile" + HTStream *stream; + HTStreamClass targetClass; + int rv; + int result; + + stream = HTStreamStack(rep_in, format_out, sink, anchor); + + if (!stream || !stream->isa) { + char *buffer = 0; + + fclose(brfp); + if (LYCancelDownload) { + LYCancelDownload = FALSE; + result = -1; + } else { + HTSprintf0(&buffer, CANNOT_CONVERT_I_TO_O, + HTAtom_name(rep_in), HTAtom_name(format_out)); + CTRACE((tfp, "HTFormat(in " THIS_FUNC "): %s\n", buffer)); + rv = HTLoadError(sink, 501, buffer); + FREE(buffer); + result = rv; + } + } else { + + /* + * Push the data down the stream + * + * @@ Bug: This decision ought to be made based on "encoding" rather than + * on content-type. @@@ When we handle encoding. The current method + * smells anyway. + */ + targetClass = *(stream->isa); /* Copy pointers to procedures */ + rv = HTBrFileCopy(brfp, stream); + if (rv == -1 || rv == HT_INTERRUPTED) { + (*targetClass._abort) (stream, NULL); + } else { + (*targetClass._free) (stream); + } + + fclose(brfp); + if (rv == -1) { + result = HT_NO_DATA; + } else if (rv == HT_INTERRUPTED || (rv > 0 && rv != HT_LOADED)) { + result = HT_PARTIAL_CONTENT; + } else { + result = HT_LOADED; + } + } + return result; +#undef THIS_FUNC +} +#endif /* USE_BROTLI */ + +/* Converter stream: Network Telnet to internal character text + * ----------------------------------------------------------- + * + * The input is assumed to be in ASCII, with lines delimited + * by (13,10) pairs, These pairs are converted into (CR,LF) + * pairs in the local representation. The (CR,LF) sequence + * when found is changed to a '\n' character, the internal + * C representation of a new line. + */ + +static void NetToText_put_character(HTStream *me, int net_char) +{ + char c = (char) FROMASCII(net_char); + + if (me->had_cr) { + if (c == LF) { + me->sink->isa->put_character(me->sink, '\n'); /* Newline */ + me->had_cr = NO; + return; + } else { + me->sink->isa->put_character(me->sink, CR); /* leftover */ + } + } + me->had_cr = (BOOL) (c == CR); + if (!me->had_cr) + me->sink->isa->put_character(me->sink, c); /* normal */ +} + +static void NetToText_put_string(HTStream *me, const char *s) +{ + const char *p; + + for (p = s; *p; p++) + NetToText_put_character(me, *p); +} + +static void NetToText_put_block(HTStream *me, const char *s, int l) +{ + const char *p; + + for (p = s; p < (s + l); p++) + NetToText_put_character(me, *p); +} + +static void NetToText_free(HTStream *me) +{ + (me->sink->isa->_free) (me->sink); /* Close rest of pipe */ + FREE(me); +} + +static void NetToText_abort(HTStream *me, HTError e) +{ + me->sink->isa->_abort(me->sink, e); /* Abort rest of pipe */ + FREE(me); +} + +/* The class structure +*/ +static HTStreamClass NetToTextClass = +{ + "NetToText", + NetToText_free, + NetToText_abort, + NetToText_put_character, + NetToText_put_string, + NetToText_put_block +}; + +/* The creation method +*/ +HTStream *HTNetToText(HTStream *sink) +{ + HTStream *me = typecalloc(HTStream); + + if (me == NULL) + outofmem(__FILE__, "NetToText"); + + me->isa = &NetToTextClass; + + me->had_cr = NO; + me->sink = sink; + return me; +} + +static HTStream HTBaseStreamInstance; /* Made static */ + +/* + * ERROR STREAM + * ------------ + * There is only one error stream shared by anyone who wants a + * generic error returned from all stream methods. + */ +static void HTErrorStream_put_character(HTStream *me GCC_UNUSED, int c GCC_UNUSED) +{ + LYCancelDownload = TRUE; +} + +static void HTErrorStream_put_string(HTStream *me GCC_UNUSED, const char *s) +{ + if (s && *s) + LYCancelDownload = TRUE; +} + +static void HTErrorStream_write(HTStream *me GCC_UNUSED, const char *s, int l) +{ + if (l && s) + LYCancelDownload = TRUE; +} + +static void HTErrorStream_free(HTStream *me GCC_UNUSED) +{ + return; +} + +static void HTErrorStream_abort(HTStream *me GCC_UNUSED, HTError e GCC_UNUSED) +{ + return; +} + +static const HTStreamClass HTErrorStreamClass = +{ + "ErrorStream", + HTErrorStream_free, + HTErrorStream_abort, + HTErrorStream_put_character, + HTErrorStream_put_string, + HTErrorStream_write +}; + +HTStream *HTErrorStream(void) +{ + CTRACE((tfp, "ErrorStream. Created\n")); + HTBaseStreamInstance.isa = &HTErrorStreamClass; /* The rest is random */ + return &HTBaseStreamInstance; +} diff --git a/WWW/Library/Implementation/HTFormat.h b/WWW/Library/Implementation/HTFormat.h new file mode 100644 index 0000000..835daef --- /dev/null +++ b/WWW/Library/Implementation/HTFormat.h @@ -0,0 +1,588 @@ +/* + * $LynxId: HTFormat.h,v 1.42 2022/04/01 07:54:14 tom Exp $ + * + * HTFormat: The format manager in the WWW Library + * MANAGE DIFFERENT DOCUMENT FORMATS + * + * Here we describe the functions of the HTFormat module which handles conversion between + * different data representations. (In MIME parlance, a representation is known as a + * content-type. In WWW the term "format" is often used as it is shorter). + * + * This module is implemented by HTFormat.c. This hypertext document is used to generate + * the HTFormat.h include file. Part of the WWW library. + */ +#ifndef HTFORMAT_H +#define HTFORMAT_H + +#include +#include +#include +#include + +#ifdef USE_SOURCE_CACHE +#include +#endif + +#ifdef USE_BZLIB +#include +#endif + +#ifdef USE_ZLIB +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif +/* + + These macros (which used to be constants) define some basic internally + referenced representations. The www/xxx ones are of course not MIME + standard. + + www/source is an output format which leaves the input untouched. It is + useful for diagnostics, and for users who want to see the original, whatever + it is. + + */ +/* Internal ones */ +#define STR_SOURCE "www/source" + extern HTAtom *WWW_SOURCE; /* calculated once, heavy used */ + +/* + + www/present represents the user's perception of the document. If you + convert to www/present, you present the material to the user. + + */ +#define STR_PRESENT "www/present" +#define WWW_PRESENT HTAtom_for(STR_PRESENT) /* The user's perception */ + +#define WWW_DEBUG HTAtom_for("www/debug") +/* + + WWW_DEBUG represents the user's perception of debug information, for example + sent as a HTML document in a HTTP redirection message. + + */ + +/* + + The message/rfc822 format means a MIME message or a plain text message with + no MIME header. This is what is returned by an HTTP server. + + */ +#define WWW_MIME HTAtom_for("www/mime") /* A MIME message */ + +/* + For parsing only the header. - kw + */ +#define WWW_MIME_HEAD HTAtom_for("message/x-rfc822-head") + +/* + + www/print is like www/present except it represents a printed copy. + + */ +#define WWW_PRINT HTAtom_for("www/print") /* A printed copy */ + +/* + + www/unknown is a really unknown type. Some default action is appropriate. + + */ +#define WWW_UNKNOWN HTAtom_for("www/unknown") + +#ifdef DIRED_SUPPORT +/* + www/dired signals directory edit mode. +*/ +#define WWW_DIRED HTAtom_for("www/dired") +#endif + +/* + * Miscellaneous internal MIME types. + */ +#define STR_DOWNLOAD "www/download" +#define WWW_DOWNLOAD HTAtom_for(STR_DOWNLOAD) + +#define STR_DUMP "www/dump" +#define WWW_DUMP HTAtom_for(STR_DUMP) + +/* + + These are regular MIME types. HTML is assumed to be added by the W3 code. + application/octet-stream was mistakenly application/binary in earlier libwww + versions (pre 2.11). + + */ +#define STR_BINARY "application/octet-stream" +#define STR_PLAINTEXT "text/plain" +#define STR_HTML "text/html" + +#define WWW_BINARY HTAtom_for(STR_BINARY) +#define WWW_PLAINTEXT HTAtom_for(STR_PLAINTEXT) +#define WWW_HTML HTAtom_for(STR_HTML) + +#define WWW_POSTSCRIPT HTAtom_for("application/postscript") +#define WWW_RICHTEXT HTAtom_for("application/rtf") +#define WWW_AUDIO HTAtom_for("audio/basic") + + typedef HTAtom *HTEncoding; + +/* + * The following are values for the MIME types: + */ +#define WWW_ENC_7BIT HTAtom_for("7bit") +#define WWW_ENC_8BIT HTAtom_for("8bit") +#define WWW_ENC_BINARY HTAtom_for("binary") + +/* + * We also add + */ +#define WWW_ENC_COMPRESS HTAtom_for("compress") + +/* + * Does a string designate a real encoding, or is it just + * a "dummy" as for example 7bit, 8bit, and binary? + */ +#define IsUnityEncStr(senc) \ + ((senc)==NULL || *(senc)=='\0' || !strcmp(senc,"identity") ||\ + !strcmp(senc,"8bit") || !strcmp(senc,"binary") || !strcmp(senc,"7bit")) + +#define IsUnityEnc(enc) \ + ((enc)==NULL || (enc)==HTAtom_for("identity") ||\ + (enc)==WWW_ENC_8BIT || (enc)==WWW_ENC_BINARY || (enc)==WWW_ENC_7BIT) + +/* + +The HTPresentation and HTConverter types + + This HTPresentation structure represents a possible conversion algorithm + from one format to another. It includes a pointer to a conversion routine. + The conversion routine returns a stream to which data should be fed. See + also HTStreamStack which scans the list of registered converters and calls + one. See the initialisation module for a list of conversion routines. + + */ + typedef struct _HTPresentation HTPresentation; + + typedef HTStream *HTConverter (HTPresentation *pres, + HTParentAnchor *anchor, + HTStream *sink); + + struct _HTPresentation { + HTAtom *rep; /* representation name atomized */ + HTAtom *rep_out; /* resulting representation */ + HTConverter *converter; /* routine to gen the stream stack */ + char *command; /* MIME-format command string */ + char *testcommand; /* MIME-format test string */ + float quality; /* Between 0 (bad) and 1 (good) */ + float secs; + float secs_per_byte; + off_t maxbytes; + BOOL get_accept; /* list in "Accept:" for GET */ + int accept_opt; /* matches against LYAcceptMedia */ + }; + +/* + + The list of presentations is kept by this module. It is also scanned by + modules which want to know the set of formats supported. for example. + + */ + extern HTList *HTPresentations; + +/* + + The default presentation is used when no other is appropriate + + */ + extern HTPresentation *default_presentation; + +/* + * Options used for "Content-Type" string + */ + typedef enum { + contentBINARY = 0 + ,contentTEXT + ,contentHTML + } ContentType; + +/* + * Options used for "Accept:" string + */ + typedef enum { + /* make the components powers of two so we can add them */ + mediaINT = 1 /* internal types predefined in HTInit.c */ + ,mediaEXT = 2 /* external types predefined in HTInit.c */ + ,mediaCFG = 4 /* types, e.g., viewers, from lynx.cfg */ + ,mediaUSR = 8 /* user's mime-types, etc. */ + ,mediaSYS = 16 /* system's mime-types, etc. */ + /* these are useful flavors for the options menu */ + ,mediaOpt1 = mediaINT + ,mediaOpt2 = mediaINT + mediaCFG + ,mediaOpt3 = mediaINT + mediaCFG + mediaUSR + ,mediaOpt4 = mediaINT + mediaCFG + mediaUSR + mediaSYS + /* this is the flavor from pre-2.8.6 */ + ,mediaALL = mediaINT + mediaEXT + mediaCFG + mediaUSR + mediaSYS + } AcceptMedia; + +/* + * Options used for "Accept-Encoding:" string + */ + typedef enum { + encodingNONE = 0 + ,encodingGZIP = 1 + ,encodingDEFLATE = 2 + ,encodingCOMPRESS = 4 + ,encodingBZIP2 = 8 + ,encodingBROTLI = 16 + ,encodingALL = (encodingGZIP + + encodingDEFLATE + + encodingCOMPRESS + + encodingBZIP2 + + encodingBROTLI) + } AcceptEncoding; + +/* + +HTSetPresentation: Register a system command to present a format + + ON ENTRY, + + rep is the MIME - style format name + + command is the MAILCAP - style command template + + testcommand is the MAILCAP - style testcommand template + + quality A degradation faction 0..1.0 + + secs A limit on the time user will wait (0.0 for infinity) + secs_per_byte + + maxbytes A limit on the length acceptable as input (0 infinite) + + media Used in filtering presentation types for "Accept:" + + */ + extern void HTSetPresentation(const char *representation, + const char *command, + const char *testcommand, + double quality, + double secs, + double secs_per_byte, + long int maxbytes, + AcceptMedia media + ); + +/* + +HTSetConversion: Register a conversion routine + + ON ENTRY, + + rep_in is the content-type input + + rep_out is the resulting content-type + + converter is the routine to make the stream to do it + + */ + + extern void HTSetConversion(const char *rep_in, + const char *rep_out, + HTConverter *converter, + double quality, + double secs, + double secs_per_byte, + long int maxbytes, + AcceptMedia media + ); + +/* + +HTStreamStack: Create a stack of streams + + This is the routine which actually sets up the conversion. It currently + checks only for direct conversions, but multi-stage conversions are forseen. + It takes a stream into which the output should be sent in the final format, + builds the conversion stack, and returns a stream into which the data in the + input format should be fed. The anchor is passed because hypertxet objects + load information into the anchor object which represents them. + + */ + extern HTStream *HTStreamStack(HTFormat format_in, + HTFormat format_out, + HTStream *stream_out, + HTParentAnchor *anchor); + +/* +HTReorderPresentation: put presentation near head of list + + Look up a presentation (exact match only) and, if found, reorder it to the + start of the HTPresentations list. - kw + */ + + extern void HTReorderPresentation(HTFormat format_in, + HTFormat format_out); + +/* + * Setup 'get_accept' flag to denote presentations that are not redundant, + * and will be listed in "Accept:" header. + */ + extern void HTFilterPresentations(void); + +/* + +HTStackValue: Find the cost of a filter stack + + Must return the cost of the same stack which HTStreamStack would set up. + + ON ENTRY, + + format_in The format of the data to be converted + + format_out The format required + + initial_value The intrinsic "value" of the data before conversion on a scale + from 0 to 1 + + length The number of bytes expected in the input format + + */ + extern float HTStackValue(HTFormat format_in, + HTFormat rep_out, + double initial_value, + long int length); + +#define NO_VALUE_FOUND -1e20 /* returned if none found */ + +/* Display the page while transfer in progress + * ------------------------------------------- + * + * Repaint the page only when necessary. + * This is a traverse call for HText_pageDispaly() - it works!. + * + */ + extern void HTDisplayPartial(void); + + extern void HTFinishDisplayPartial(void); + +/* + +HTCopy: Copy a socket to a stream + + This is used by the protocol engines to send data down a stream, typically + one which has been generated by HTStreamStack. + + */ + extern int HTCopy(HTParentAnchor *anchor, + int file_number, + void *handle, + HTStream *sink); + +/* + +HTFileCopy: Copy a file to a stream + + This is used by the protocol engines to send data down a stream, typically + one which has been generated by HTStreamStack. It is currently called by + HTParseFile + + */ + extern int HTFileCopy(FILE *fp, + HTStream *sink); + +#ifdef USE_SOURCE_CACHE +/* + +HTMemCopy: Copy a memory chunk to a stream + + This is used by the protocol engines to send data down a stream, typically + one which has been generated by HTStreamStack. It is currently called by + HTParseMem + + */ + extern int HTMemCopy(HTChunk *chunk, + HTStream *sink); +#endif + +/* + +HTCopyNoCR: Copy a socket to a stream, stripping CR characters. + + It is slower than HTCopy . + + */ + + extern void HTCopyNoCR(HTParentAnchor *anchor, + int file_number, + HTStream *sink); + +/* + +Clear input buffer and set file number + + This routine and the one below provide simple character input from sockets. + (They are left over from the older architecture and may not be used very + much.) The existence of a common routine and buffer saves memory space in + small implementations. + + */ + extern void HTInitInput(int file_number); + +/* + +Get next character from buffer + + */ + extern int interrupted_in_htgetcharacter; + extern int HTGetCharacter(void); + +/* + +HTParseSocket: Parse a socket given its format + + This routine is called by protocol modules to load an object. uses + HTStreamStack and the copy routines above. Returns HT_LOADED if successful, + <0 if not. + + */ + extern int HTParseSocket(HTFormat format_in, + HTFormat format_out, + HTParentAnchor *anchor, + int file_number, + HTStream *sink); + +/* + +HTParseFile: Parse a File through a file pointer + + This routine is called by protocols modules to load an object. uses + HTStreamStack and HTFileCopy. Returns HT_LOADED if successful, can also + return HT_PARTIAL_CONTENT, HT_NO_DATA, or other <0 for failure. + + */ + extern int HTParseFile(HTFormat format_in, + HTFormat format_out, + HTParentAnchor *anchor, + FILE *fp, + HTStream *sink); + +#ifdef USE_SOURCE_CACHE +/* + +HTParseMem: Parse a document in memory + + This routine is called by protocols modules to load an object. uses + HTStreamStack and HTMemCopy. Returns HT_LOADED if successful, can also + return <0 for failure. + + */ + extern int HTParseMem(HTFormat format_in, + HTFormat format_out, + HTParentAnchor *anchor, + HTChunk *chunk, + HTStream *sink); +#endif + +#ifdef USE_ZLIB +/* +HTParseGzFile: Parse a gzip'ed File through a file pointer + + This routine is called by protocols modules to load an object. uses + HTStreamStack and HTGzFileCopy. Returns HT_LOADED if successful, can also + return HT_PARTIAL_CONTENT, HT_NO_DATA, or other <0 for failure. + */ + extern int HTParseGzFile(HTFormat format_in, + HTFormat format_out, + HTParentAnchor *anchor, + gzFile gzfp, + HTStream *sink); + +/* +HTParseZzFile: Parse a deflate'd File through a file pointer + + This routine is called by protocols modules to load an object. uses + HTStreamStack and HTZzFileCopy. Returns HT_LOADED if successful, can also + return HT_PARTIAL_CONTENT, HT_NO_DATA, or other <0 for failure. + */ + extern int HTParseZzFile(HTFormat format_in, + HTFormat format_out, + HTParentAnchor *anchor, + FILE *zzfp, + HTStream *sink); + +#endif /* USE_ZLIB */ + +#ifdef USE_BZLIB +/* +HTParseBzFile: Parse a bzip2'ed File through a file pointer + + This routine is called by protocols modules to load an object. uses + HTStreamStack and HTBzFileCopy. Returns HT_LOADED if successful, can also + return HT_PARTIAL_CONTENT, HT_NO_DATA, or other <0 for failure. + */ + extern int HTParseBzFile(HTFormat format_in, + HTFormat format_out, + HTParentAnchor *anchor, + BZFILE * bzfp, + HTStream *sink); + +#endif /* USE_BZLIB */ + +#ifdef USE_BROTLI +/* +HTParseBrFile: Parse a brotli'ed File through a file pointer + + This routine is called by protocols modules to load an object. uses + HTStreamStack and HTBrFileCopy. Returns HT_LOADED if successful, can also + return HT_PARTIAL_CONTENT, HT_NO_DATA, or other <0 for failure. + */ + extern int HTParseBrFile(HTFormat format_in, + HTFormat format_out, + HTParentAnchor *anchor, + FILE *brfp, + HTStream *sink); + +#endif /* USE_BROTLI */ + +/* + +HTNetToText: Convert Net ASCII to local representation + + This is a filter stream suitable for taking text from a socket and passing + it into a stream which expects text in the local C representation. It does + ASCII and newline conversion. As usual, pass its output stream to it when + creating it. + + */ + extern HTStream *HTNetToText(HTStream *sink); + +/* + +HTFormatInit: Set up default presentations and conversions + + These are defined in HTInit.c or HTSInit.c if these have been replaced. If + you don't call this routine, and you don't define any presentations, then + this routine will automatically be called the first time a conversion is + needed. However, if you explicitly add some conversions (eg using + HTLoadRules) then you may want also to explicitly call this to get the + defaults as well. + + */ + extern void HTFormatInit(void); + +/* + +Epilogue + + */ + extern BOOL HTOutputSource; /* Flag: shortcut parser */ + +#ifdef __cplusplus +} +#endif +#endif /* HTFORMAT_H */ diff --git a/WWW/Library/Implementation/HTGopher.c b/WWW/Library/Implementation/HTGopher.c new file mode 100644 index 0000000..0314179 --- /dev/null +++ b/WWW/Library/Implementation/HTGopher.c @@ -0,0 +1,2071 @@ +/* + * $LynxId: HTGopher.c,v 1.77 2022/04/01 00:18:09 tom Exp $ + * + * GOPHER ACCESS HTGopher.c + * ============= + * + * History: + * 26 Sep 90 Adapted from other accesses (News, HTTP) TBL + * 29 Nov 91 Downgraded to C, for portable implementation. + * 10 Mar 96 Foteos Macrides (macrides@sci.wfbr.edu). Added a + * form-based CSO/PH gateway. Can be invoked via a + * "cso://host[:port]/" or "gopher://host:105/2" + * URL. If a gopher URL is used with a query token + * ('?'), the old ISINDEX procedure will be used + * instead of the form-based gateway. + * 15 Mar 96 Foteos Macrides (macrides@sci.wfbr.edu). Pass + * port 79, gtype 0 gopher URLs to the finger + * gateway. + */ + +#define HTSTREAM_INTERNAL 1 + +#include /* Coding convention macros */ +#include /* For HTFileFormat() */ + +#ifndef DISABLE_GOPHER +#include +#include +#include +#include +#include + +/* + * Implements. + */ +#include + +#define GOPHER_PORT 70 /* See protocol spec */ +#define CSO_PORT 105 /* See protocol spec */ +#define BIG 1024 /* Bug */ +#define LINE_LENGTH 256 /* Bug */ + +/* + * Gopher entity types. + */ +#define GOPHER_TEXT '0' +#define GOPHER_MENU '1' +#define GOPHER_CSO '2' +#define GOPHER_ERROR '3' +#define GOPHER_MACBINHEX '4' +#define GOPHER_PCBINARY '5' +#define GOPHER_UUENCODED '6' +#define GOPHER_INDEX '7' +#define GOPHER_TELNET '8' +#define GOPHER_BINARY '9' +#define GOPHER_GIF 'g' +#define GOPHER_HTML 'h' /* HTML */ +#define GOPHER_CHTML 'H' /* HTML */ +#define GOPHER_SOUND 's' +#define GOPHER_WWW 'w' /* W3 address */ +#define GOPHER_IMAGE 'I' +#define GOPHER_TN3270 'T' +#define GOPHER_INFO 'i' +#define GOPHER_DUPLICATE '+' +#define GOPHER_PLUS_IMAGE ':' /* Addition from Gopher Plus */ +#define GOPHER_PLUS_MOVIE ';' +#define GOPHER_PLUS_SOUND '<' +#define GOPHER_PLUS_PDF 'P' + +#include + +/* + * Hypertext object building machinery. + */ +#include + +#include +#include +#include + +#define PUTC(c) (*targetClass.put_character)(target, c) +#define PUTS(s) (*targetClass.put_string)(target, s) +#define START(e) (*targetClass.start_element)(target, e, 0, 0, -1, 0) +#define END(e) (*targetClass.end_element)(target, e, 0) +#define FREE_TARGET (*targetClass._free)(target) + +#define NEXT_CHAR HTGetCharacter() + +/* + * Module-wide variables. + */ +static int s; /* Socket for gopher or CSO host */ + +struct _HTStructured { + const HTStructuredClass *isa; /* For gopher streams */ + /* ... */ +}; + +static HTStructured *target; /* the new gopher hypertext */ +static HTStructuredClass targetClass; /* Its action routines */ + +struct _HTStream { + HTStreamClass *isa; /* For form-based CSO gateway - FM */ +}; + +typedef struct _CSOfield_info { /* For form-based CSO gateway - FM */ + struct _CSOfield_info *next; + char *name; + char *attributes; + char *description; + int id; + int lookup; + int indexed; + int url; + int max_size; + int defreturn; + int explicit_return; + int reserved; + int gpublic; + char name_buf[16]; /* Avoid malloc if we can */ + char desc_buf[32]; /* Avoid malloc if we can */ + char attr_buf[80]; /* Avoid malloc if we can */ +} CSOfield_info; + +static CSOfield_info *CSOfields = NULL; /* For form-based CSO gateway - FM */ + +typedef struct _CSOformgen_context { /* For form-based CSO gateway - FM */ + const char *host; + const char *seek; + CSOfield_info *fld; + int port; + int cur_line; + int cur_off; + int rep_line; + int rep_off; + int public_override; + int field_select; +} CSOformgen_context; + +/* Matrix of allowed characters in filenames + * ========================================= + */ +static BOOL acceptable_html[256]; +static BOOL acceptable_file[256]; +static BOOL acceptable_inited = NO; + +static void init_acceptable(void) +{ + unsigned int i; + const char *good = + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./-_$"; + + for (i = 0; i < 256; i++) { + acceptable_html[i] = NO; + acceptable_file[i] = NO; + } + for (; *good; good++) { + acceptable_html[(unsigned int) *good] = YES; + acceptable_file[(unsigned int) *good] = YES; + } + for (good = ";?=#"; *good; ++good) { + acceptable_html[(unsigned int) *good] = YES; + } + acceptable_inited = YES; +} + +/* Decode one hex character + * ======================== + */ +static const char hex[17] = "0123456789abcdef"; + +static char from_hex(int c) +{ + return (char) ((c >= '0') && (c <= '9') ? c - '0' + : (c >= 'A') && (c <= 'F') ? c - 'A' + 10 + : (c >= 'a') && (c <= 'f') ? c - 'a' + 10 + : 0); +} + +/* Paste in an Anchor + * ================== + * + * The title of the destination is set, as there is no way + * of knowing what the title is when we arrive. + * + * On entry, + * HT is in append mode. + * text points to the text to be put into the file, 0 terminated. + * addr points to the hypertext reference address 0 terminated. + */ +BOOLEAN HT_Is_Gopher_URL = FALSE; + +static void write_anchor(const char *text, const char *addr) +{ + BOOL present[HTML_A_ATTRIBUTES]; + const char *value[HTML_A_ATTRIBUTES]; + + int i; + + for (i = 0; i < HTML_A_ATTRIBUTES; i++) + present[i] = 0; + present[HTML_A_HREF] = YES; + ((const char **) value)[HTML_A_HREF] = addr; + present[HTML_A_TITLE] = YES; + ((const char **) value)[HTML_A_TITLE] = text; + + CTRACE((tfp, "HTGopher: adding URL: %s\n", addr)); + + HT_Is_Gopher_URL = TRUE; /* tell HTML.c that this is a Gopher URL */ + (*targetClass.start_element) (target, HTML_A, present, + (const char **) value, -1, 0); + + PUTS(text); + END(HTML_A); +} + +/* Parse a Gopher Menu document + * ============================ + */ +static void parse_menu(const char *arg GCC_UNUSED, + HTParentAnchor *anAnchor) +{ + char gtype; + char this_type; + int ich; + char line[BIG]; + char *name = NULL, *selector = NULL; /* Gopher menu fields */ + char *host = NULL; + char *port; + char *p = line; + const char *title; + int bytes = 0; + int BytesReported = 0; + char buffer[128]; + BOOL *valid_chars; + +#define TAB '\t' +#define HEX_ESCAPE '%' + + START(HTML_HTML); + PUTC('\n'); + START(HTML_HEAD); + PUTC('\n'); + START(HTML_TITLE); + if ((title = HTAnchor_title(anAnchor))) + PUTS(title); + else + PUTS(GOPHER_MENU_TITLE); + END(HTML_TITLE); + PUTC('\n'); + END(HTML_HEAD); + PUTC('\n'); + + START(HTML_BODY); + PUTC('\n'); + START(HTML_H1); + if ((title = HTAnchor_title(anAnchor))) + PUTS(title); + else + PUTS(GOPHER_MENU_TITLE); + END(HTML_H1); + PUTC('\n'); + START(HTML_PRE); + PUTC('\n'); /* newline after HTML_PRE forces split-line */ + this_type = GOPHER_ERROR; + while ((ich = NEXT_CHAR) != EOF) { + + if (interrupted_in_htgetcharacter) { + CTRACE((tfp, + "HTGopher: Interrupted in HTGetCharacter, apparently.\n")); + goto end_html; + } + + if ((char) ich != LF) { + const char *ss = NULL; + + /* + * Help the -source output to look like the HTML equivalent of the + * Gopher menu. + */ + if (dump_output_immediately + && HTOutputFormat == WWW_DUMP) { + if (ich == '<') { + ss = "<"; + } else if (ich == '>') { + ss = ">"; + } else if (ich == '&') { + ss = "&"; + } + } + if (ss != NULL) { + if ((p + 5) < &line[BIG - 1]) { + while (*ss != '\0') { + *p++ = *ss++; + } + } + } else { + *p = (char) ich; /* Put character in line */ + if (p < &line[BIG - 1]) + p++; + } + + } else { + *p++ = '\0'; /* Terminate line */ + bytes += (int) (p - line); /* add size */ + p = line; /* Scan it to parse it */ + port = 0; /* Flag "not parsed" */ + CTRACE((tfp, "HTGopher: Menu item: %s\n", line)); + gtype = *p++; + + if (bytes > BytesReported + 1024) { + sprintf(buffer, TRANSFERRED_X_BYTES, bytes); + HTProgress(buffer); + BytesReported = bytes; + } + + /* Break on line with a dot by itself */ + if ((gtype == '.') && ((*p == '\r') || (*p == 0))) + break; + + if (gtype && *p) { + name = p; + selector = StrChr(name, TAB); + if (selector) { + *selector++ = '\0'; /* Terminate name */ + /* + * Gopher+ Type=0+ objects can be binary, and will + * have 9 or 5 beginning their selector. Make sure + * we don't trash the terminal by treating them as + * text. - FM + */ + if (gtype == GOPHER_TEXT && (*selector == GOPHER_BINARY || + *selector == GOPHER_PCBINARY)) + gtype = *selector; + host = StrChr(selector, TAB); + if (host) { + *host++ = '\0'; /* Terminate selector */ + port = StrChr(host, TAB); + if (port) { + char *junk; + + port[0] = ':'; /* delimit host a la W3 */ + junk = StrChr(port, TAB); + if (junk) + *junk = '\0'; /* Chop port */ + if ((port[1] == '0') && (!port[2])) + port[0] = '\0'; /* 0 means none */ + } /* no port */ + } /* host ok */ + } /* selector ok */ + } + /* gtype and name ok */ + /* Nameless files are a separator line */ + if (name != NULL && gtype == GOPHER_TEXT) { + int i = (int) strlen(name) - 1; + + while (name[i] == ' ' && i >= 0) + name[i--] = '\0'; + if (i < 0) + gtype = GOPHER_INFO; + } + + if (gtype == GOPHER_WWW) { /* Gopher pointer to W3 */ + PUTS("(HTML) "); + write_anchor(name, selector); + + } else if (gtype == GOPHER_INFO) { + /* Information or separator line */ + PUTS(" "); + PUTS(name); + + } else if (port && /* Other types need port */ + (gtype != GOPHER_DUPLICATE || + this_type != GOPHER_ERROR)) { + char *address = 0; + const char *format = *selector ? "%s//%s@%s/" : "%s//%s/"; + + if (gtype == GOPHER_TELNET) { + PUTS(" (TEL) "); + if (*selector == '/') + ++selector; + HTSprintf0(&address, format, STR_TELNET_URL, selector, host); + } else if (gtype == GOPHER_TN3270) { + PUTS("(3270) "); + if (*selector == '/') + ++selector; + HTSprintf0(&address, format, STR_TN3270_URL, selector, host); + } else { /* If parsed ok */ + char *r; + + switch (gtype) { + case GOPHER_TEXT: + PUTS("(FILE) "); + break; + case GOPHER_MENU: + PUTS(" (DIR) "); + break; + case GOPHER_DUPLICATE: + PUTS(" (+++) "); + break; + case GOPHER_CSO: + PUTS(" (CSO) "); + break; + case GOPHER_PCBINARY: + PUTS(" (BIN) "); + break; + case GOPHER_UUENCODED: + PUTS(" (UUE) "); + break; + case GOPHER_INDEX: + PUTS(" (?) "); + break; + case GOPHER_BINARY: + PUTS(" (BIN) "); + break; + case GOPHER_GIF: + case GOPHER_IMAGE: + case GOPHER_PLUS_IMAGE: + PUTS(" (IMG) "); + break; + case GOPHER_SOUND: + case GOPHER_PLUS_SOUND: + PUTS(" (SND) "); + break; + case GOPHER_MACBINHEX: + PUTS(" (HQX) "); + break; + case GOPHER_HTML: + case GOPHER_CHTML: + PUTS("(HTML) "); + break; + case 'm': + PUTS("(MIME) "); + break; + case GOPHER_PLUS_MOVIE: + PUTS(" (MOV) "); + break; + case GOPHER_PLUS_PDF: + PUTS(" (PDF) "); + break; + default: + PUTS("(UNKN) "); + break; + } + + if (gtype != GOPHER_DUPLICATE) + this_type = gtype; + + HTSprintf0(&address, "//%s/%c", host, this_type); + if (gtype == GOPHER_HTML) { + valid_chars = acceptable_html; + if (*selector == '/') + ++selector; + } else { + valid_chars = acceptable_file; + } + + for (r = selector; *r; r++) { /* Encode selector string */ + if (valid_chars[UCH(*r)]) { + HTSprintf(&address, "%c", *r); + } else { + HTSprintf(&address, "%c%c%c", + HEX_ESCAPE, /* Means hex coming */ + hex[(TOASCII(*r)) >> 4], + hex[(TOASCII(*r)) & 15]); + } + } + } + /* Error response from Gopher doesn't deserve to + be a hyperlink. */ + if (strcmp(address, "gopher://error.host:1/0")) + write_anchor(name, address); + else + PUTS(name); + FREE(address); + } else { /* parse error */ + CTRACE((tfp, "HTGopher: Bad menu item (type %d, port %s).\n", + gtype, NonNull(port))); + PUTS(line); + + } /* parse error */ + + PUTC('\n'); + p = line; /* Start again at beginning of line */ + + } /* if end of line */ + + } /* Loop over characters */ + + end_html: + END(HTML_PRE); + PUTC('\n'); + END(HTML_BODY); + PUTC('\n'); + END(HTML_HTML); + PUTC('\n'); + FREE_TARGET; + + return; +} + +/* Parse a Gopher CSO document from an ISINDEX query. + * ================================================== + * + * Accepts an open socket to a CSO server waiting to send us + * data and puts it on the screen in a reasonable manner. + * + * Perhaps this data can be automatically linked to some + * other source as well??? + * + * Taken from hacking by Lou Montulli@ukanaix.cc.ukans.edu + * on XMosaic-1.1, and put on libwww 2.11 by Arthur Secret, + * secret@dxcern.cern.ch . + */ +static void parse_cso(const char *arg, + HTParentAnchor *anAnchor) +{ + int ich; + char line[BIG]; + char *p = line; + char *first_colon, *second_colon, last_char = '\0'; + const char *title; + + START(HTML_HEAD); + PUTC('\n'); + START(HTML_TITLE); + if ((title = HTAnchor_title(anAnchor))) + PUTS(title); + else + PUTS(GOPHER_CSO_SEARCH_RESULTS); + END(HTML_TITLE); + PUTC('\n'); + END(HTML_HEAD); + PUTC('\n'); + START(HTML_H1); + if ((title = HTAnchor_title(anAnchor))) + PUTS(title); + else { + PUTS(arg); + PUTS(GOPHER_SEARCH_RESULTS); + } + END(HTML_H1); + PUTC('\n'); + START(HTML_PRE); + + /* + * Start grabbing chars from the network. + */ + while ((ich = NEXT_CHAR) != EOF) { + if ((char) ich != LF) { + *p = (char) ich; /* Put character in line */ + if (p < &line[BIG - 1]) + p++; + } else { + *p = '\0'; /* Terminate line */ + p = line; /* Scan it to parse it */ + /* + * OK we now have a line in 'p'. Lets parse it and print it. + */ + + /* + * Break on line that begins with a 2. It's the end of data. + */ + if (*p == '2') + break; + + /* + * Lines beginning with 5 are errors. Print them and quit. + */ + if (*p == '5') { + START(HTML_H2); + PUTS(p + 4); + END(HTML_H2); + break; + } + + if (*p == '-') { + /* + * Data lines look like -200:#: + * where # is the search result number and can be multiple + * digits (infinite?). + * Find the second colon and check the digit to the left of it + * to see if they are different. If they are then a different + * person is starting. Make this line an

. + */ + + /* + * Find the second_colon. + */ + second_colon = NULL; + first_colon = StrChr(p, ':'); + if (first_colon != NULL) { + second_colon = StrChr(first_colon + 1, ':'); + } + + if (second_colon != NULL) { /* error check */ + + if (*(second_colon - 1) != last_char) + /* print separator */ + { + END(HTML_PRE); + START(HTML_H2); + } + + /* + * Right now the record appears with the alias (first line) + * as the header and the rest as
 text.
+		     *
+		     * It might look better with the name as the header and the
+		     * rest as a \n";
+    for $n ( 0 .. $#major ) {
+        printf FP "\n";
+        printf FP "

%s

\n", $cats{ $major[$n] }, + $major[$n]; + if ( $descs{ $major[$n] } !~ /^$/ ) { + printf FP "

Description

\n

%s\n", $descs{ $major[$n] }; + } + $c = $index{ $major[$n] }; + if ( $c ne "" ) { + my @c = split( /\n/, $c ); + @c = sort @c; + printf FP + "

Here is a list of settings that belong to this category\n"; + printf FP "

    \n"; + for $m ( 0 .. $#c ) { + my $avail = ok( $c[$m] ); + my $mark = ( !$avail && defined $opt_m ) ? "*" : ""; + printf FP "
  • %s\n", $c[$m], + $c[$m] . $mark; + } + printf FP "
\n"; + } + } + my $str = <<'EOF' +

+To list of settings by name +EOF + . ( + defined $opt_a && defined $opt_m + ? "

Support for all settings suffixed with '*' was disabled at compile time." + : "" + ) + . <<'EOF' + + +EOF + ; + print FP $str; + close(FP); + return @cats; +} diff --git a/scripts/cfg_defs.sh b/scripts/cfg_defs.sh new file mode 100755 index 0000000..560dfc4 --- /dev/null +++ b/scripts/cfg_defs.sh @@ -0,0 +1,51 @@ +#!/bin/sh +# $LynxId: cfg_defs.sh,v 1.2 2021/01/07 00:38:05 tom Exp $ +# Translate the lynx_cfg.h and config.cache data into a table, useful for +# display at runtime. + +TOP="${1-.}" +OUT=cfg_defs.h + +# just in case we want to run this outside the makefile +: "${SHELL:=/bin/sh}" + +{ +cat <$OUT diff --git a/scripts/cfg_edit.sh b/scripts/cfg_edit.sh new file mode 100755 index 0000000..99f00be --- /dev/null +++ b/scripts/cfg_edit.sh @@ -0,0 +1,17 @@ +#!/bin/sh +# Invoked from cfg_defs.sh as a filter +# Strip leading and trailing whitespace +# Escape any iternal '\' +# Escape any iternal '"' +# Entify any iternal '&', '<' or '>' +# Append a '=' if none present' +# Break into two strings at '=' +# Prefix ' { "' and suffix '" },' +LC_ALL=C sort | +sed -e 's!^[ ]*!!' -e 's![ ]*$!!' \ + -e 's!\\!\\\\!g' \ + -e 's!"!\\"!g' \ + -e 's!&!\&!g' -e 's!!\>!g' \ + -e 's!^[^=]*$!&=!' \ + -e 's!=!", "!' \ + -e 's!^! { "!' -e 's!$!" },!' diff --git a/scripts/cfg_path.sh b/scripts/cfg_path.sh new file mode 100755 index 0000000..321f5b9 --- /dev/null +++ b/scripts/cfg_path.sh @@ -0,0 +1,5 @@ +#!/bin/sh +# Use this script for substituting the configured path into lynx.cfg - +# not all paths begin with a slash. +SECOND=`echo "$2" | sed -e 's,^/,,'` +sed -e "/^[abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_]*:file:/s,/PATH_TO/$1,/$SECOND," diff --git a/scripts/collapse-br b/scripts/collapse-br new file mode 100755 index 0000000..a3378b5 --- /dev/null +++ b/scripts/collapse-br @@ -0,0 +1,162 @@ +#!/usr/bin/env perl +# $LynxId: collapse-br,v 1.8 2017/07/04 19:35:45 tom Exp $ +# Generate a series of HTML files containing a mixture of text and
tags, +# comparing dumps of those to w3m and elinks. + +use warnings; +use strict; +use diagnostics; + +$| = 1; + +use Getopt::Std; +use File::Temp qw/ tempdir /; + +our ( $opt_C, $opt_T, $opt_e, $opt_l, $opt_p, $opt_t, $opt_w ); +our $tempdir = tempdir( CLEANUP => 1 ); + +sub dumpit($$) { + my $prog = shift; + my $html = shift; + my $opts = "-dump"; + $html = + "" + . "T" + . "$html" + . "" + if ($opt_t); + my @result; + if ( $prog =~ /lynx$/ ) { + my $name = "$tempdir/lynx.cfg"; + $opts .= " -cfg=$name"; + open my $fh, ">$name"; + printf $fh "collapse_br_tags:%s\n", $opt_C ? "false" : "true"; + printf $fh "trim_blank_lines:%s\n", $opt_T ? "false" : "true"; + close $fh; + } + if ($opt_p) { + $opts .= " -stdin" if ( $prog =~ /lynx$/ ); + $opts .= " -force-html" if ( $prog =~ /elinks$/ ); + $opts .= " -T text/html" if ( $prog =~ /w3m$/ ); + if ( open my $fh, "echo '$html' | $prog $opts |" ) { + @result = <$fh>; + close $fh; + } + } + else { + my $name = "$tempdir/foobar.html"; + open my $fh, ">$name"; + printf $fh "%s", $html; + close $fh; + + $opts .= " $name"; + if ( open my $fh, "$prog $opts |" ) { + @result = <$fh>; + close $fh; + } + } + for my $n ( 0 .. $#result ) { + chomp $result[$n]; + } + + if ( open my $fh, "echo '$html' | $prog $opts |" ) { + @result = <$fh>; + close $fh; + for my $n ( 0 .. $#result ) { + chomp $result[$n]; + } + } + $result[0] = "OOPS" unless ( $#result >= 0 ); + return @result; +} + +sub header($) { + my @cols = @{ $_[0] }; + my $text = ""; + for my $c ( 0 .. $#cols ) { + $text .= sprintf "%-8s", $cols[$c]; + } + printf "\t %s\n", $text; +} + +sub doit() { + my $length = 1; + my $state = -1; + + my @tokens; + $tokens[0] = " "; + $tokens[1] = "X"; + $tokens[2] = "
"; + my $tokens = $#tokens + 1; + + my @progs; + $progs[ $#progs + 1 ] = "lynx"; + + $progs[ $#progs + 1 ] = "w3m" if ($opt_w); + $progs[ $#progs + 1 ] = "elinks" if ($opt_e); + $progs[ $#progs + 1 ] = "./lynx" if ( -f "./lynx" ); + + while ( $length <= $opt_l ) { + my $bits = ""; + my $html = ""; + my $value = ++$state; + $length = 0; + while ( $value >= 0 ) { + my $digit = $value % $tokens; + my $update = ( $value - $digit ) / $tokens; + last if ( ( $update <= 0 ) and ( $value <= 0 ) and $length > 0 ); + $bits .= sprintf "%d", $digit; + $length++; + $html .= $tokens[$digit]; + $value = $update; + } + + # skip the non-interesting cases + next if ( $bits =~ /00/ ); + next if ( $bits =~ /11/ ); + next unless ( $bits =~ /2/ ); + printf "%-*s '%s'\n", $opt_l, $bits, $html; + my @listing; + for my $p ( 0 .. $#progs ) { + my @q = &dumpit( $progs[$p], $html ); + my $l = $p * 8; + for my $r ( 0 .. $#q ) { + + $listing[$r] = "" unless ( $listing[$r] ); + $listing[$r] = sprintf "%-*s", $l, $listing[$r] if ( $l > 0 ); + $listing[$r] .= sprintf "|%s", + substr( $q[$r] . "........", 0, 7 ); + } + } + &header( \@progs ); + for my $r ( 0 .. $#listing ) { + printf "\t%2d %s|\n", $r, $listing[$r]; + } + } +} + +sub main::HELP_MESSAGE() { + printf STDERR <"$test" + cp "$test" "$name" + chmod u+w "$name" + ${INDENT_PROG-indent} -npro $OPTS "$name" + sed \ + -e '/MODULE_ID(/s/);$/)/' \ + -e 's,;[ ]*//GCC_UNUSED;, GCC_UNUSED;,' \ + -e 's,;[ ]*//GCC_NORETURN;, GCC_NORETURN;,' \ + -e 's,);[ ]*//GCC_PRINTFLIKE,) GCC_PRINTFLIKE,' \ + "$name" >"$test" + mv "$test" "$name" + rm -f "${name}~" + if test $NOOP = yes ; then + if ! ( cmp -s "$name" "$save" ) + then + diff -u "$save" "$name" + fi + mv "$save" "$name" + rm -f "${name}~" + else + if ( cmp -s "$name" "$save" ) + then + echo "** unchanged $name" + rm -f "${name}" "${name}~" + mv "$save" "$name" + else + echo "** updated $name" + rm -f "$save" + fi + fi + ;; + *) + echo "** ignored: $name" + ;; + esac +done diff --git a/scripts/install-cfg.sh b/scripts/install-cfg.sh new file mode 100755 index 0000000..6faa21a --- /dev/null +++ b/scripts/install-cfg.sh @@ -0,0 +1,89 @@ +#!/bin/sh +# $LynxId: install-cfg.sh,v 1.5 2021/01/07 00:31:20 tom Exp $ +# install lynx.cfg, ensuring the old config-file is saved to a unique file, +# and prepending customizations to the newly-installed file. +# +# $1 = install program +# $2 = file to install +# $3 = where to install it +PRG="$1" +SRC=$2 +DST=$3 + +LANG=C; export LANG +LC_ALL=C; export LC_ALL +LC_CTYPE=C; export LC_CTYPE +LANGUAGE=C; export LANGUAGE + +if test -f "$DST" ; then + echo "** checking if you have customized $DST" + OLD=lynx-cfg.old + NEW=lynx-cfg.new + TST=lynx-cfg.tst + TMP=lynx-cfg.tmp + trap 'rm -f $OLD $NEW $TST $TMP; exit 9' INT QUIT TERM HUP + rm -f $OLD $NEW $TST $TMP + + # avoid propagating obsolete URLs into new installs + { + echo lynx.isc.org; + echo lynx.browser.org; + echo www.trill-home.com; + echo www.cc.ukans.edu; + echo www.ukans.edu; + echo www.slcc.edu; + echo sol.slcc.edu; + }>$TMP + + # Make a list of the settings which are in the original lynx.cfg + # Do not keep the user's HELPFILE setting since we modify that in + # a different makefile rule. + ${EGREP-egrep} '^[ ]*[A-Za-z]' "$SRC" |sed -e 's/^[ ]*HELPFILE:.*/HELPFILE:/' >>$TMP + ${EGREP-egrep} '^[ ]*[A-Za-z]' "$SRC" |${FGREP-fgrep} -v -f $TMP >$OLD + ${EGREP-egrep} '^[ ]*[A-Za-z]' "$DST" |${FGREP-fgrep} -v -f $TMP >$TST + + if test -s $TST ; then + cat >$TMP <>$TMP + cat "$SRC" >$NEW + cat "$TMP" >>$NEW + + # See if we have saved this information before (ignoring the + # HELPFILE line). + if cmp -s $NEW $OLD + then + echo "... installed $DST would not be changed" + else + NUM=1 + while test -f "${DST}-${NUM}" + do + if cmp -s "$NEW" "${DST}-${NUM}" + then + break + fi + NUM=`expr "$NUM" + 1` + done + if test ! -f "${DST}-${NUM}" + then + echo "... saving old config as ${DST}-${NUM}" + mv "$DST" "${DST}-${NUM}" || exit 1 + fi + echo "** installing $NEW as $DST" + eval "$PRG" "$NEW" "$DST" || exit 1 + fi + else + echo "... no customizations found" + echo "** installing $SRC as $DST" + eval "$PRG" "$SRC" "$DST" || exit 1 + fi + rm -f "$SKIP" "$OLD" "$NEW" "$TST" "$TMP" +elif cmp -s "$SRC" "$DST" +then + echo "... installed $DST would not be changed" +else + echo "** installing $SRC as $DST" + eval "$PRG" "$SRC" "$DST" || exit 1 +fi diff --git a/scripts/install-lss.sh b/scripts/install-lss.sh new file mode 100755 index 0000000..eb9694a --- /dev/null +++ b/scripts/install-lss.sh @@ -0,0 +1,38 @@ +#!/bin/sh +# $LynxId: install-lss.sh,v 1.2 2021/01/07 00:32:39 tom Exp $ +# install lynx.lss, ensuring the old config-file is saved to a backup file. +# +# $1 = install program +# $2 = file to install +# $3 = where to install it +PRG="$1" +SRC=$2 +DST=$3 + +if test -f "$DST" ; then + # See if we have saved this information before + if cmp -s "$SRC" "$DST" + then + echo "... installed $DST would not be changed" + else + NUM=1 + while test -f "${DST}-${NUM}" + do + if cmp -s "$SRC" "${DST}-${NUM}" + then + break + fi + NUM=`expr "$NUM" + 1` + done + if test ! -f "${DST}-${NUM}" + then + echo "... saving old config as ${DST}-${NUM}" + mv "$DST" "${DST}-${NUM}" || exit 1 + fi + echo "** installing $SRC as $DST" + eval "$PRG" "$SRC" "$DST" || exit 1 + fi +else + echo "** installing $SRC as $DST" + eval "$PRG" "$SRC" "$DST" || exit 1 +fi diff --git a/scripts/man2hlp.sh b/scripts/man2hlp.sh new file mode 100755 index 0000000..cc1653c --- /dev/null +++ b/scripts/man2hlp.sh @@ -0,0 +1,22 @@ +#!/bin/sh +# $LynxId: man2hlp.sh,v 1.4 2021/01/07 00:34:48 tom Exp $ +# This script uses rman (Rosetta Man), which complements TkMan, to strip +# nroff headers from a manpage file, and format the result into a VMS help +# file. + +LANG=C; export LANG +LC_ALL=C; export LC_ALL +LC_CTYPE=C; export LC_CTYPE +LANGUAGE=C; export LANGUAGE + +for name in "$@" +do + NAME=`echo "$name" |sed -e 's/\.man$/.1/'` + (echo 1 "`echo \"$NAME\" | sed -e 's/^.*\///' -e 's/\..*$//' | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'`" ; \ + (echo '.hy 0'; cat "$name") |\ + nroff -Tascii -man |\ + rman -n"$NAME" |\ + sed -e 's/^[1-9].*$//' \ + -e 's/^\([A-Z]\)/2 \1/' |\ + uniq) +done diff --git a/scripts/relpath.sh b/scripts/relpath.sh new file mode 100755 index 0000000..cee5a21 --- /dev/null +++ b/scripts/relpath.sh @@ -0,0 +1,43 @@ +#!/bin/sh +# Given two pathnames, print the first relative to the second. +# +# Adapted from +# https://unix.stackexchange.com/questions/573047/how-to-get-the-relative-path-between-two-directories + +usage() { + echo "usage: $0 source target" >&2 + exit 1 +} + +check_abs() { + case "$1" in + /*) + ;; + *) + echo "? not an absolute pathname: $1" + usage + ;; + esac +} + +[ $# = 2 ] || usage +check_abs "$1" +check_abs "$2" + +source="$1" +target="$2" +prefix="" + +source="`echo "$source" | sed -e 's%/$%%'`/" +target="`echo "$target" | sed -e 's%/$%%'`/" +remain=`echo "$source" | sed -e 's%^'$target'%%'` +while [ -n "$target" ] && [ "$source" = "$remain" ] +do + prefix="../$prefix" + target=`echo "$target" | sed -e 's%/[^/]*/$%/%'` + remain=`echo "$source" | sed -e 's%^'$target'%%'` +done +result="${prefix}${remain}" +[ -n "$result" ] || result="." + +echo "$result" diff --git a/scripts/tbl2html.pl b/scripts/tbl2html.pl new file mode 100755 index 0000000..ba8ae09 --- /dev/null +++ b/scripts/tbl2html.pl @@ -0,0 +1,364 @@ +#!/usr/bin/perl -w +# $LynxId: tbl2html.pl,v 1.5 2011/05/21 15:18:16 tom Exp $ +# +# Translate one or more ".tbl" files into ".html" files which can be used to +# test the charset support in lynx. Each of the ".html" files will use the +# charset that corresponds to the input ".tbl" file. + +use strict; + +use Getopt::Std; +use File::Basename; +use POSIX qw(strtod); + +sub field($$) { + my $value = $_[0]; + my $count = $_[1]; + + while ( $count > 0 ) { + $count -= 1; + $value =~ s/^\S*\s*//; + } + $value =~ s/\s.*//; + return $value; +} + +sub notes($) { + my $value = $_[0]; + + $value =~ s/^[^#]*//; + $value =~ s/^#//; + $value =~ s/^\s+//; + + return $value; +} + +sub make_header($$$) { + my $source = $_[0]; + my $charset = $_[1]; + my $official = $_[2]; + + printf FP "\n"; + printf FP "\n"; + printf FP "\n"; + printf FP "\n"; + printf FP "%s table\n", &escaped($official); + printf FP "\n", &escaped($charset); + printf FP "\n"; + printf FP "\n"; + printf FP " \n"; + printf FP "\n"; + printf FP "

%s table

\n", &escaped($charset); + printf FP "\n"; + printf FP "
\n";
+	printf FP "Code  Char  Entity   Render          Description\n";
+}
+
+sub make_mark() {
+	printf FP "----  ----  ------   ------          -----------------------------------\n";
+}
+
+sub escaped($) {
+	my $result = $_[0];
+	$result =~ s/&/&/g;
+	$result =~ s//>/g;
+	return $result;
+}
+
+sub make_row($$$) {
+	my $old_code = $_[0];
+	my $new_code = $_[1];
+	my $comments = $_[2];
+
+	# printf "# make_row %d %d %s\n", $old_code, $new_code, $comments;
+	my $visible = sprintf("&#%d;      ", $new_code);
+	if ($old_code < 256) {
+		printf FP "%4x    %c   %.13s  &#%d;             %s\n",
+			$old_code, $old_code,
+			$visible, $new_code,
+			&escaped($comments);
+	} else {
+		printf FP "%4x    .   %.13s  &#%d;             %s\n",
+			$old_code,
+			$visible, $new_code,
+			&escaped($comments);
+	}
+}
+
+sub null_row($$) {
+	my $old_code = $_[0];
+	my $comments = $_[1];
+
+	if ($old_code < 256) {
+		printf FP "%4x    %c                     %s\n",
+			$old_code, $old_code,
+			&escaped($comments);
+	} else {
+		printf FP "%4x    .                     %s\n",
+			$old_code,
+			&escaped($comments);
+	}
+}
+
+sub make_footer() {
+	printf FP "
\n"; + printf FP "\n"; + printf FP "\n"; +} + +# return true if the string describes a range +sub is_range($) { + return ($_[0] =~ /.*-.*/); +} + +# convert the U+'s to 0x's so strtod() can convert them. +sub zeroxes($) { + my $result = $_[0]; + $result =~ s/^U\+/0x/; + $result =~ s/-U\+/-0x/; + return $result; +} + +# convert a string to a number (-1's are outside the range of Unicode). +sub value_of($) { + my ($result, $oops) = strtod($_[0]); + $result = -1 if ($oops ne 0); + return $result; +} + +# return the first number in a range +sub first_of($) { + my $range = &zeroxes($_[0]); + $range =~ s/-.*//; + return &value_of($range); +} + +# return the last number in a range +sub last_of($) { + my $range = &zeroxes($_[0]); + $range =~ s/^.*-//; + return &value_of($range); +} + +sub one_many($$$) { + my $oldcode = $_[0]; + my $newcode = &zeroxes($_[1]); + my $comment = $_[2]; + + my $old_code = &value_of($oldcode); + if ( $old_code lt 0 ) { + printf "? Problem with number \"%s\"\n", $oldcode; + } else { + &make_mark if (( $old_code % 8 ) == 0 ); + + if ( $newcode =~ /^#.*/ ) { + &null_row($old_code, $comment); + } elsif ( &is_range($newcode) ) { + my $first_item = &first_of($newcode); + my $last_item = &last_of($newcode); + my $item; + + if ( $first_item lt 0 or $last_item lt 0 ) { + printf "? Problem with one:many numbers \"%s\"\n", $newcode; + } else { + if ( $comment =~ /^$/ ) { + $comment = sprintf("mapped: %#x to %#x..%#x", $old_code, $first_item, $last_item); + } else { + $comment = $comment . " (range)"; + } + for $item ( $first_item..$last_item) { + &make_row($old_code, $item, $comment); + } + } + } else { + my $new_code = &value_of($newcode); + if ( $new_code lt 0 ) { + printf "? Problem with number \"%s\"\n", $newcode; + } else { + if ( $comment =~ /^$/ ) { + $comment = sprintf("mapped: %#x to %#x", $old_code, $new_code); + } + &make_row($old_code, $new_code, $comment); + } + } + } +} + +sub many_many($$$) { + my $oldcode = $_[0]; + my $newcode = $_[1]; + my $comment = $_[2]; + + my $first_old = &first_of($oldcode); + my $last_old = &last_of($oldcode); + my $item; + + if (&is_range($newcode)) { + my $first_new = &first_of($newcode); + my $last_new = &last_of($newcode); + for $item ( $first_old..$last_old) { + &one_many($item, $first_new, $comment); + $first_new += 1; + } + } else { + for $item ( $first_old..$last_old) { + &one_many($item, $newcode, $comment); + } + } +} + +sub approximate($$$) { + my $values = $_[0]; + my $expect = sprintf("%-8s", $_[1]); + my $comment = $_[2]; + my $escaped = &escaped($expect); + my $left; + my $this; + my $next; + + $escaped =~ s/\\134/\\/g; + $escaped =~ s/\\015/\ \;/g; + $escaped =~ s/\\012/\ \;/g; + + while ( $escaped =~ /^.*\\[0-7]{3}.*$/ ) { + $left = $escaped; + $left =~ s/\\[0-7]{3}.*//; + $this = substr $escaped,length($left)+1,3; + $next = substr $escaped,length($left)+4; + $escaped = sprintf("%s&#%d;%s", $left, oct $this, $next); + } + + my $visible = sprintf("&#%d; ", $values); + if ($values < 256) { + printf FP "%4x %c %.13s &#%d; approx: %s\n", + $values, $values, + $visible, + $values, + $escaped; + } else { + printf FP "%4x . %.13s &#%d; approx: %s\n", + $values, + $visible, + $values, + $escaped; + } +} + +sub doit($) { + my $source = $_[0]; + + printf "** %s\n", $source; + + my $target = basename($source, ".tbl"); + + # Read the file into an array in memory. + open(FP,$source) || do { + print STDERR "Can't open input $source: $!\n"; + return; + }; + my (@input) = ; + chomp @input; + close(FP); + + my $n; + my $charset = ""; + my $official = ""; + my $empty = 1; + + for $n (0..$#input) { + $input[$n] =~ s/\s*$//; # trim trailing blanks + $input[$n] =~ s/^\s*//; # trim leading blanks + $input[$n] =~ s/^#0x/0x/; # uncomment redundant stuff + + next if $input[$n] =~ /^$/; + next if $input[$n] =~ /^#.*$/; + + if ( $empty + and ( $input[$n] =~ /^\d/ + or $input[$n] =~ /^U\+/ ) ) { + $target = $charset . ".html"; + printf "=> %s\n", $target; + open(FP,">$target") || do { + print STDERR "Can't open output $target: $!\n"; + return; + }; + &make_header($source, $charset, $official); + $empty = 0; + } + + if ( $input[$n] =~ /^M.*/ ) { + $charset = $input[$n]; + $charset =~ s/^.//; + } elsif ( $input[$n] =~ /^O.*/ ) { + $official = $input[$n]; + $official =~ s/^.//; + } elsif ( $input[$n] =~ /^\d/ ) { + + my $newcode = &field($input[$n], 1); + + next if ( $newcode eq "idem" ); + next if ( $newcode eq "" ); + + my $oldcode = &field($input[$n], 0); + if ( &is_range($oldcode) ) { + &many_many($oldcode, $newcode, ¬es($input[$n])); + } else { + &one_many($oldcode, $newcode, ¬es($input[$n])); + } + } elsif ( $input[$n] =~ /^U\+/ ) { + if ( $input[$n] =~ /^U\+\w+:/ ) { + my $values = $input[$n]; + my $expect = $input[$n]; + + $values =~ s/:.*//; + $values = &zeroxes($values); + $expect =~ s/^[^:]+://; + + if ( &is_range($values) ) { + printf "fixme:%s(%s)(%s)\n", $input[$n], $values, $expect; + } else { + &approximate(&value_of($values), $expect, ¬es($input[$n])); + } + } else { + my $value = $input[$n]; + $value =~ s/\s*".*//; + $value = &value_of(&zeroxes($value)); + if ($value gt 0) { + my $quote = $input[$n]; + my $comment = ¬es($input[$n]); + $quote =~ s/^[^"]*"//; + $quote =~ s/".*//; + &approximate($value, $quote, $comment); + } else { + printf "fixme:%d(%s)\n", $n, $input[$n]; + } + } + } else { + # printf "skipping line %d:%s\n", $n + 1, $input[$n]; + } + } + if ( ! $empty ) { + &make_footer(); + } + close FP; +} + +sub usage() { + print <= 0 ) { + &doit ( shift @ARGV ); + } +} +exit (0); diff --git a/src/AttrList.h b/src/AttrList.h new file mode 100644 index 0000000..cf38261 --- /dev/null +++ b/src/AttrList.h @@ -0,0 +1,62 @@ +/* + * $LynxId: AttrList.h,v 1.17 2013/05/03 20:54:09 tom Exp $ + */ +#if !defined(__ATTRLIST_H) +#define __ATTRLIST_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + enum { + ABS_OFF = 0, + STACK_OFF = 0, + STACK_ON, + ABS_ON + }; + +#define STARTAT 8 + + enum { + DSTYLE_LINK = HTML_A + STARTAT, + DSTYLE_STATUS = HTML_ELEMENTS + STARTAT, + DSTYLE_ALINK, /* active link */ + DSTYLE_NORMAL, /* default attributes */ + DSTYLE_OPTION, /* option on the option screen */ + DSTYLE_VALUE, /* value on the option screen */ + DSTYLE_CANDY, /* possibly going to vanish */ + DSTYLE_WHEREIS, /* whereis search target */ + DSTYLE_ELEMENTS + }; + + typedef struct { + int color; /* color highlighting to be done */ + int mono; /* mono highlighting to be done */ + int cattr; /* attributes to go with the color */ + } HTCharStyle; + +#if 0 +#define HText_characterStyle CTRACE((tfp,"HTC called from %s/%d\n",__FILE__,__LINE__));_internal_HTC +#else +#define HText_characterStyle _internal_HTC +#endif + +#if defined(USE_COLOR_STYLE) + extern void _internal_HTC(HText *text, int style, int dir); + +#define TEMPSTRINGSIZE 256 + extern char class_string[TEMPSTRINGSIZE + 1]; + +/* stack of attributes during page rendering */ +#define MAX_LAST_STYLES 128 + extern int last_styles[MAX_LAST_STYLES + 1]; + extern int last_colorattr_ptr; + +#endif + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/DefaultStyle.c b/src/DefaultStyle.c new file mode 100644 index 0000000..3dc17f7 --- /dev/null +++ b/src/DefaultStyle.c @@ -0,0 +1,504 @@ +/* + * $LynxId: DefaultStyle.c,v 1.20 2009/11/27 13:04:27 tom Exp $ + * + * A real style sheet for the Character Grid browser + * + * The dimensions are all in characters! + */ + +#include +#include +#include + +#include +#include + +/* Tab arrays: +*/ +static const HTTabStop tabs_8[] = +{ + {0, 8}, + {0, 16}, + {0, 24}, + {0, 32}, + {0, 40}, + {0, 48}, + {0, 56}, + {0, 64}, + {0, 72}, + {0, 80}, + {0, 88}, + {0, 96}, + {0, 104}, + {0, 112}, + {0, 120}, + {0, 128}, + {0, 136}, + {0, 144}, + {0, 152}, + {0, 160}, + {0, 168}, + {0, 176}, + {0, 0} /* Terminate */ +}; + +/* Template: + * link to next, name, name id (enum), tag, + * font, size, colour, superscript, anchor id, + * indents: 1st, left, right, alignment lineheight, descent, tabs, + * word wrap, free format, space: before, after, flags. + */ + +static HTStyle HTStyleNormal = +HTStyleInit( + 0, Normal, "P", + HT_FONT, 1, HT_BLACK, 0, 0, + 3, 3, 6, HT_LEFT, 1, 0, tabs_8, + YES, YES, 1, 0, 0); + +static HTStyle HTStyleDivCenter = +HTStyleInit( + &HTStyleNormal, DivCenter, "DCENTER", + HT_FONT, 1, HT_BLACK, 0, 0, + 3, 3, 6, HT_CENTER, 1, 0, tabs_8, + YES, YES, 1, 0, 0); + +static HTStyle HTStyleDivLeft = +HTStyleInit( + &HTStyleDivCenter, DivLeft, "DLEFT", + HT_FONT, 1, HT_BLACK, 0, 0, + 3, 3, 6, HT_LEFT, 1, 0, tabs_8, + YES, YES, 1, 0, 0); + +static HTStyle HTStyleDivRight = +HTStyleInit( + &HTStyleDivLeft, DivRight, "DRIGHT", + HT_FONT, 1, HT_BLACK, 0, 0, + 3, 3, 6, HT_RIGHT, 1, 0, tabs_8, + YES, YES, 1, 0, 0); + +static HTStyle HTStyleBanner = +HTStyleInit( + &HTStyleDivRight, Banner, "BANNER", + HT_FONT, 1, HT_BLACK, 0, 0, + 3, 3, 6, HT_LEFT, 1, 0, tabs_8, + YES, YES, 1, 0, 0); + +static HTStyle HTStyleBlockquote = +HTStyleInit( + &HTStyleBanner, Blockquote, "BLOCKQUOTE", + HT_FONT, 1, HT_BLACK, 0, 0, + 5, 5, 7, HT_LEFT, 1, 0, tabs_8, + YES, YES, 1, 0, 0); + +static HTStyle HTStyleBq = +HTStyleInit( /* HTML 3.0 BLOCKQUOTE - FM */ + &HTStyleBlockquote, Bq, "BQ", + HT_FONT, 1, HT_BLACK, 0, 0, + 5, 5, 7, HT_LEFT, 1, 0, tabs_8, + YES, YES, 1, 0, 0); + +static HTStyle HTStyleFootnote = +HTStyleInit( /* HTML 3.0 FN - FM */ + &HTStyleBq, Footnote, "FN", + HT_FONT, 1, HT_BLACK, 0, 0, + 5, 5, 7, HT_LEFT, 1, 0, tabs_8, + YES, YES, 1, 0, 0); + +static HTStyle HTStyleList = +HTStyleInit( + &HTStyleFootnote, List, "UL", + HT_FONT, 1, HT_BLACK, 0, 0, + 3, 7, 6, HT_LEFT, 1, 0, 0, + YES, YES, 0, 0, 0); + +static HTStyle HTStyleList1 = +HTStyleInit( + &HTStyleList, List1, "UL", + HT_FONT, 1, HT_BLACK, 0, 0, + 8, 12, 6, HT_LEFT, 1, 0, 0, + YES, YES, 0, 0, 0); + +static HTStyle HTStyleList2 = +HTStyleInit( + &HTStyleList1, List2, "UL", + HT_FONT, 1, HT_BLACK, 0, 0, + 13, 17, 6, HT_LEFT, 1, 0, 0, + YES, YES, 0, 0, 0); + +static HTStyle HTStyleList3 = +HTStyleInit( + &HTStyleList2, List3, "UL", + HT_FONT, 1, HT_BLACK, 0, 0, + 18, 22, 6, HT_LEFT, 1, 0, 0, + YES, YES, 0, 0, 0); + +static HTStyle HTStyleList4 = +HTStyleInit( + &HTStyleList3, List4, "UL", + HT_FONT, 1, HT_BLACK, 0, 0, + 23, 27, 6, HT_LEFT, 1, 0, 0, + YES, YES, 0, 0, 0); + +static HTStyle HTStyleList5 = +HTStyleInit( + &HTStyleList4, List5, "UL", + HT_FONT, 1, HT_BLACK, 0, 0, + 28, 32, 6, HT_LEFT, 1, 0, 0, + YES, YES, 0, 0, 0); + +static HTStyle HTStyleList6 = +HTStyleInit( + &HTStyleList5, List6, "UL", + HT_FONT, 1, HT_BLACK, 0, 0, + 33, 37, 6, HT_LEFT, 1, 0, 0, + YES, YES, 0, 0, 0); + +static HTStyle HTStyleMenu = +HTStyleInit( + &HTStyleList6, Menu, "MENU", + HT_FONT, 1, HT_BLACK, 0, 0, + 3, 7, 6, HT_LEFT, 1, 0, 0, + YES, YES, 0, 0, 0 +); + +static HTStyle HTStyleMenu1 = +HTStyleInit( + &HTStyleMenu, Menu1, "MENU", + HT_FONT, 1, HT_BLACK, 0, 0, + 8, 12, 6, HT_LEFT, 1, 0, 0, + YES, YES, 0, 0, 0 +); + +static HTStyle HTStyleMenu2 = +HTStyleInit( + &HTStyleMenu1, Menu2, "MENU", + HT_FONT, 1, HT_BLACK, 0, 0, + 13, 17, 6, HT_LEFT, 1, 0, 0, + YES, YES, 0, 0, 0 +); + +static HTStyle HTStyleMenu3 = +HTStyleInit( + &HTStyleMenu2, Menu3, "MENU", + HT_FONT, 1, HT_BLACK, 0, 0, + 18, 22, 6, HT_LEFT, 1, 0, 0, + YES, YES, 0, 0, 0 +); + +static HTStyle HTStyleMenu4 = +HTStyleInit( + &HTStyleMenu3, Menu4, "MENU", + HT_FONT, 1, HT_BLACK, 0, 0, + 23, 27, 6, HT_LEFT, 1, 0, 0, + YES, YES, 0, 0, 0 +); + +static HTStyle HTStyleMenu5 = +HTStyleInit( + &HTStyleMenu4, Menu5, "MENU", + HT_FONT, 1, HT_BLACK, 0, 0, + 28, 33, 6, HT_LEFT, 1, 0, 0, + YES, YES, 0, 0, 0 +); + +static HTStyle HTStyleMenu6 = +HTStyleInit( + &HTStyleMenu5, Menu6, "MENU", + HT_FONT, 1, HT_BLACK, 0, 0, + 33, 38, 6, HT_LEFT, 1, 0, 0, + YES, YES, 0, 0, 0 +); + +static HTStyle HTStyleGlossary = +HTStyleInit( + &HTStyleMenu6, Glossary, "DL", + HT_FONT, 1, HT_BLACK, 0, 0, + 3, 10, 6, HT_LEFT, 1, 0, 0, + YES, YES, 1, 1, 0 +); + +static HTStyle HTStyleGlossary1 = +HTStyleInit( + &HTStyleGlossary, Glossary1, "DL", + HT_FONT, 1, HT_BLACK, 0, 0, + 8, 16, 6, HT_LEFT, 1, 0, 0, + YES, YES, 1, 1, 0 +); + +static HTStyle HTStyleGlossary2 = +HTStyleInit( + &HTStyleGlossary1, Glossary2, "DL", + HT_FONT, 1, HT_BLACK, 0, 0, + 14, 22, 6, HT_LEFT, 1, 0, 0, + YES, YES, 1, 1, 0 +); + +static HTStyle HTStyleGlossary3 = +HTStyleInit( + &HTStyleGlossary2, Glossary3, "DL", + HT_FONT, 1, HT_BLACK, 0, 0, + 20, 28, 6, HT_LEFT, 1, 0, 0, + YES, YES, 1, 1, 0 +); + +static HTStyle HTStyleGlossary4 = +HTStyleInit( + &HTStyleGlossary3, Glossary4, "DL", + HT_FONT, 1, HT_BLACK, 0, 0, + 26, 34, 6, HT_LEFT, 1, 0, 0, + YES, YES, 1, 1, 0 +); + +static HTStyle HTStyleGlossary5 = +HTStyleInit( + &HTStyleGlossary4, Glossary5, "DL", + HT_FONT, 1, HT_BLACK, 0, 0, + 32, 40, 6, HT_LEFT, 1, 0, 0, + YES, YES, 1, 1, 0 +); + +static HTStyle HTStyleGlossary6 = +HTStyleInit( + &HTStyleGlossary5, Glossary6, "DL", + HT_FONT, 1, HT_BLACK, 0, 0, + 38, 46, 6, HT_LEFT, 1, 0, 0, + YES, YES, 1, 1, 0 +); + +static HTStyle HTStyleGlossaryCompact = +HTStyleInit( + &HTStyleGlossary6, GlossaryCompact, "DLC", + HT_FONT, 1, HT_BLACK, 0, 0, + 3, 10, 6, HT_LEFT, 1, 0, 0, + YES, YES, 0, 0, 0 +); + +static HTStyle HTStyleGlossaryCompact1 = +HTStyleInit( + &HTStyleGlossaryCompact, + GlossaryCompact1, "DLC", + HT_FONT, 1, HT_BLACK, 0, 0, + 8, 15, 6, HT_LEFT, 1, 0, 0, + YES, YES, 0, 0, 0 +); + +static HTStyle HTStyleGlossaryCompact2 = +HTStyleInit( + &HTStyleGlossaryCompact1, + GlossaryCompact2, "DLC", + HT_FONT, 1, HT_BLACK, 0, 0, + 13, 20, 6, HT_LEFT, 1, 0, 0, + YES, YES, 0, 0, 0 +); + +static HTStyle HTStyleGlossaryCompact3 = +HTStyleInit( + &HTStyleGlossaryCompact2, + GlossaryCompact3, "DLC", + HT_FONT, 1, HT_BLACK, 0, 0, + 18, 25, 6, HT_LEFT, 1, 0, 0, + YES, YES, 0, 0, 0 +); + +static HTStyle HTStyleGlossaryCompact4 = +HTStyleInit( + &HTStyleGlossaryCompact3, + GlossaryCompact4, "DLC", + HT_FONT, 1, HT_BLACK, 0, 0, + 23, 30, 6, HT_LEFT, 1, 0, 0, + YES, YES, 0, 0, 0 +); + +static HTStyle HTStyleGlossaryCompact5 = +HTStyleInit( + &HTStyleGlossaryCompact4, + GlossaryCompact5, "DLC", + HT_FONT, 1, HT_BLACK, 0, 0, + 28, 35, 6, HT_LEFT, 1, 0, 0, + YES, YES, 0, 0, 0 +); + +static HTStyle HTStyleGlossaryCompact6 = +HTStyleInit( + &HTStyleGlossaryCompact5, + GlossaryCompact6, "DLC", + HT_FONT, 1, HT_BLACK, 0, 0, + 33, 40, 6, HT_LEFT, 1, 0, 0, + YES, YES, 0, 0, 0 +); + +static HTStyle HTStyleExample = +HTStyleInit( + &HTStyleGlossaryCompact6, + Example, "XMP", + HT_FONT, 1, HT_BLACK, 0, 0, + 0, 0, 0, HT_LEFT, 1, 0, tabs_8, + NO, NO, 0, 0, 0 +); + +static HTStyle HTStylePreformatted = +HTStyleInit( + &HTStyleExample, + Preformatted, "PRE", + HT_FONT, 1, HT_BLACK, 0, 0, + 0, 0, 0, HT_LEFT, 1, 0, tabs_8, + NO, NO, 0, 0, 0 +); + +static HTStyle HTStyleListing = +HTStyleInit( + &HTStylePreformatted, Listing, "LISTING", + HT_FONT, 1, HT_BLACK, 0, 0, + 0, 0, 0, HT_LEFT, 1, 0, tabs_8, + NO, NO, 0, 0, 0); + +static HTStyle HTStyleAddress = +HTStyleInit( + &HTStyleListing, Address, "ADDRESS", + HT_FONT, 1, HT_BLACK, 0, 0, + 4, 4, 7, HT_LEFT, 1, 0, tabs_8, + YES, YES, 2, 0, 0); + +static HTStyle HTStyleNote = +HTStyleInit( /* HTML 3.0 NOTE - FM */ + &HTStyleAddress, Note, "NOTE", + HT_FONT, 1, HT_BLACK, 0, 0, + 5, 5, 7, HT_LEFT, 1, 0, tabs_8, + YES, YES, 1, 0, 0); + +static HTStyle HTStyleHeading1 = +HTStyleInit( + &HTStyleNote, Heading1, "H1", + HT_FONT + HT_BOLD, 1, HT_BLACK, 0, 0, + 0, 0, 0, HT_CENTER, 1, 0, 0, + YES, YES, 1, 1, 0); + +static HTStyle HTStyleHeading2 = +HTStyleInit( + &HTStyleHeading1, Heading2, "H2", + HT_FONT + HT_BOLD, 1, HT_BLACK, 0, 0, + 0, 0, 0, HT_LEFT, 1, 0, 0, + YES, YES, 1, 1, 0); + +static HTStyle HTStyleHeading3 = +HTStyleInit( + &HTStyleHeading2, Heading3, "H3", + HT_FONT + HT_BOLD, 1, HT_BLACK, 0, 0, + 2, 2, 0, HT_LEFT, 1, 0, 0, + YES, YES, 1, 0, 0); + +static HTStyle HTStyleHeading4 = +HTStyleInit( + &HTStyleHeading3, Heading4, "H4", + HT_FONT + HT_BOLD, 1, HT_BLACK, 0, 0, + 4, 4, 0, HT_LEFT, 1, 0, 0, + YES, YES, 1, 0, 0); + +static HTStyle HTStyleHeading5 = +HTStyleInit( + &HTStyleHeading4, Heading5, "H5", + HT_FONT + HT_BOLD, 1, HT_BLACK, 0, 0, + 6, 6, 0, HT_LEFT, 1, 0, 0, + YES, YES, 1, 0, 0); + +static HTStyle HTStyleHeading6 = +HTStyleInit( + &HTStyleHeading5, Heading6, "H6", + HT_FONT + HT_BOLD, 1, HT_BLACK, 0, 0, + 8, 8, 0, HT_LEFT, 1, 0, 0, + YES, YES, 1, 0, 0); + +static HTStyle HTStyleHeadingCenter = +HTStyleInit( + &HTStyleHeading6, HeadingCenter, "HCENTER", + HT_FONT + HT_BOLD, 1, HT_BLACK, 0, 0, + 0, 0, 3, HT_CENTER, 1, 0, tabs_8, + YES, YES, 1, 0, 0); + +static HTStyle HTStyleHeadingLeft = +HTStyleInit( + &HTStyleHeadingCenter, HeadingLeft, "HLEFT", + HT_FONT + HT_BOLD, 1, HT_BLACK, 0, 0, + 0, 0, 3, HT_LEFT, 1, 0, tabs_8, + YES, YES, 1, 0, 0); + +static HTStyle HTStyleHeadingRight = +HTStyleInit( + &HTStyleHeadingLeft, HeadingRight, "HRIGHT", + HT_FONT + HT_BOLD, 1, HT_BLACK, 0, 0, + 0, 0, 3, HT_RIGHT, 1, 0, tabs_8, + YES, YES, 1, 0, 0); + +/* Style sheet points to the last in the list: +*/ +static HTStyleSheet sheet = +{"default.style", + &HTStyleHeadingRight}; /* sheet */ + +static HTStyle *st_array[ST_HeadingRight + 1] = +{NULL}; + +static HTStyleSheet *result = NULL; + +#ifdef LY_FIND_LEAKS +static void FreeDefaultStyle(void) +{ + HTStyle *style; + + while ((style = result->styles) != 0) { + result->styles = style->next; + FREE(style); + } + FREE(result); +} +#endif /* LY_FIND_LEAKS */ + +HTStyleSheet *DefaultStyle(HTStyle ***result_array) +{ + HTStyle *p, *q; + + /* + * The first time we're called, allocate a copy of the 'sheet' linked + * list. Thereafter, simply copy the data from 'sheet' into our copy + * (preserving the copy's linked-list pointers). We do this to reset the + * parameters of a style that might be altered while processing a page. + */ + if (result == 0) { /* allocate & copy */ + result = HTStyleSheetNew(); + *result = sheet; + result->styles = 0; +#ifdef LY_FIND_LEAKS + atexit(FreeDefaultStyle); +#endif + for (p = sheet.styles; p != 0; p = p->next) { + q = HTStyleNew(); + *q = *p; + if (no_margins) { + q->indent1st = 0; + q->leftIndent = 0; + q->rightIndent = 0; + } + st_array[q->id] = q; + q->next = result->styles; + result->styles = q; + } + } else { /* recopy the data */ + for (q = result->styles, p = sheet.styles; + p != 0 && q != 0; + p = p->next, q = q->next) { + HTStyle *r = q->next; + + *q = *p; + if (no_margins) { + q->indent1st = 0; + q->leftIndent = 0; + q->rightIndent = 0; + } + st_array[q->id] = q; + q->next = r; + } + } + *result_array = st_array; + return result; +} diff --git a/src/GridText.c b/src/GridText.c new file mode 100644 index 0000000..419f6ee --- /dev/null +++ b/src/GridText.c @@ -0,0 +1,15052 @@ +/* + * $LynxId: GridText.c,v 1.349 2024/04/11 20:24:32 tom Exp $ + * + * Character grid hypertext object + * =============================== + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* LYUCTranslateBack... */ +#include +#include +#include +#include +#include +#include +#include +#ifdef EXP_CHARTRANS_AUTOSWITCH +#include +#endif /* EXP_CHARTRANS_AUTOSWITCH */ + +#include +#include + +#ifdef USE_COLOR_STYLE +#include +#include +#include +#endif + +#ifdef EXP_WCWIDTH_SUPPORT +# ifdef HAVE_WCWIDTH +# ifdef HAVE_WCHAR_H +# include +# endif +# else +# include +# define wcwidth(n) mk_wcwidth(n) +# endif +#endif + +#include + +#define is_CJK2(b) (IS_CJK_TTY && is8bits(UCH(b))) + +#ifdef USE_CURSES_PADS +# define DISPLAY_COLS (LYwideLines ? MAX_COLS : LYcols) +# define WRAP_COLS(text) ((text)->stbl ? \ + (LYtableCols <= 0 \ + ? DISPLAY_COLS \ + : (LYtableCols * LYcols)/12) - LYbarWidth \ + : LYcolLimit) +#else +# define DISPLAY_COLS LYcols +# define WRAP_COLS(text) LYcolLimit +#endif + +#define FirstHTLine(text) ((text)->last_line->next) +#define LastHTLine(text) ((text)->last_line) + +static void HText_trimHightext(HText *text, int final, int stop_before); + +#define IS_UTF_FIRST(ch) (text->T.output_utf8 && \ + (UCH((ch))&0xc0) == 0xc0) + +#define IS_UTF_EXTRA(ch) (text->T.output_utf8 && \ + (UCH((ch))&0xc0) == 0x80) + +#define IS_UTF8_EXTRA(ch) (!(text && text->T.output_utf8) || \ + !is8bits(ch) || \ + (UCH(line->data[i] & 0xc0) == 0xc0)) + +/* a test in compact form: how many extra UTF-8 chars after initial? - kw */ +#define UTF8_XNEGLEN(c) (c&0xC0? 0 :c&32? 1 :c&16? 2 :c&8? 3 :c&4? 4 :c&2? 5:0) +#define UTF_XLEN(c) UTF8_XNEGLEN(((char)~(c))) + +#ifdef KANJI_CODE_OVERRIDE +HTkcode last_kcode = NOKANJI; /* 1997/11/14 (Fri) 09:09:26 */ +#endif + +#undef CHAR_WIDTH + +#ifdef CJK_EX +#define CHAR_WIDTH 6 +#else +#define CHAR_WIDTH 1 +#endif + +/* Exports +*/ +HText *HTMainText = NULL; /* Equivalent of main window */ +HTParentAnchor *HTMainAnchor = NULL; /* Anchor for HTMainText */ + +const char *HTAppName = LYNX_NAME; /* Application name */ +const char *HTAppVersion = LYNX_VERSION; /* Application version */ + +static int HTFormNumber = 0; +static int HTFormFields = 0; +static char *HTCurSelectGroup = NULL; /* Form select group name */ +static int HTCurSelectGroupCharset = -1; /* ... and name's charset */ +int HTCurSelectGroupType = F_RADIO_TYPE; /* Group type */ +char *HTCurSelectGroupSize = NULL; /* Length of select */ +static char *HTCurSelectedOptionValue = NULL; /* Select choice */ + +const char *checked_box = "[X]"; +const char *unchecked_box = "[ ]"; +const char *checked_radio = "(*)"; +const char *unchecked_radio = "( )"; + +static BOOLEAN underline_on = FALSE; +static BOOLEAN bold_on = FALSE; + +#ifdef USE_SOURCE_CACHE +int LYCacheSource = SOURCE_CACHE_NONE; +int LYCacheSourceForAborted = SOURCE_CACHE_FOR_ABORTED_DROP; +#endif + +#ifdef USE_SCROLLBAR +BOOLEAN LYShowScrollbar = FALSE; +BOOLEAN LYsb_arrow = TRUE; +int LYsb_begin = -1; +int LYsb_end = -1; +#endif + +#ifndef VMS /* VMS has a better way - right? - kw */ +#define CHECK_FREE_MEM +#endif + +#ifdef CHECK_FREE_MEM +static void *LY_check_calloc(size_t nmemb, size_t size); + +#define LY_CALLOC LY_check_calloc +#else + /* using the regular calloc */ +#define LY_CALLOC calloc +#endif + +/* + * The HTPool.data[] array has to align the same as malloc() would, to make the + * ALLOC_POOL scheme portable. For many platforms, that is the same as the + * number of bytes in a pointer. It may be larger, e.g., on machines which + * have more stringent requirements for floating point. 32-bits are plenty for + * representing styles, but we may need 64-bit or 128-bit alignment. + * + * The real issue is that performance is degraded if the alignment is not met, + * and some platforms such as Tru64 generate lots of warning messages. + */ +#ifndef ALIGN_SIZE +#define ALIGN_SIZE sizeof(double) +#endif + +#define BITS_DIR 2 +#define BITS_POS 14 + +#define MASK_DIR ((1U << BITS_DIR) - 1) +#define CAST_DIR(n) ((MASK_DIR) & (unsigned)(n)) + +#define MASK_POS ((1U << BITS_POS) - 1) +#define CAST_POS(n) ((MASK_POS) & (unsigned)(n)) + +typedef struct { + unsigned sc_direction:BITS_DIR; /* on or off */ + unsigned sc_horizpos:BITS_POS; /* horizontal position of this change */ + unsigned sc_style:16; /* which style to change to */ +} HTStyleChange; + +#if defined(USE_COLOR_STYLE) +#define MAX_STYLES_ON_LINE 64 + /* buffers used when current line is being aggregated, in split_line() */ +static HTStyleChange stylechanges_buffers[2][MAX_STYLES_ON_LINE]; +#endif + +typedef HTStyleChange pool_data; + +enum { + POOL_SIZE = ((8192 + - 4 * sizeof(void *) + - sizeof(struct _HTPool *) + - sizeof(int)) + / sizeof(pool_data)) +}; + +typedef struct _HTPool { + pool_data data[POOL_SIZE]; + struct _HTPool *prev; + unsigned used; +} HTPool; + +/************************************************************************ +These are generic macros for any pools (provided those structures have the +same members as HTPool). Pools are used for allocation of groups of +objects of the same type T. Pools are represented as a list of structures of +type P (called pool chunks here). Structure P has an array of N objects of +type T named 'data' (the number N in the array can be chosen arbitrary), +pointer to the previous pool chunk named 'prev', and the number of used items +in that pool chunk named 'used'. Here is a definition of the structure P: + struct P + { + T data[N]; + struct P* prev; + int used; + }; + It's recommended that sizeof(P) be memory page size minus 32 in order malloc'd +chunks to fit in machine page size. + Allocation of 'n' items in the pool is implemented by incrementing member +'used' by 'n' if (used+n <= N), or malloc a new pool chunk and +allocating 'n' items in that new chunk. It's the task of the programmer to +assert that 'n' is <= N. Only entire pool may be freed - this limitation makes +allocation algorithms trivial and fast - so the use of pools is limited to +objects that are freed in batch, that are not deallocated not in the batch, and +not reallocated. + Pools greatly reduce memory fragmentation and memory allocation/deallocation +speed due to the simple algorithms used. Due to the fact that memory is +'allocated' in array, alignment overhead is minimal. Allocating strings in a +pool provided their length will never exceed N and is much smaller than N seems +to be very efficient. + [Several types of memory-hungry objects are stored in the pool now: styles, +lines, anchors, and FormInfo. Arrays of HTStyleChange are stored as is, +other objects are stored using a cast.] + + Pool is referenced by the pointer to the last chunk that contains free slots. +Functions that allocate memory in the pool update that pointer if needed. +There are 3 functions - POOL_NEW, POOL_FREE, and ALLOC_IN_POOL. + + - VH + +*************************************************************************/ + +#define POOLallocstyles(ptr, n) ptr = ALLOC_IN_POOL(&HTMainText->pool, (unsigned) ((n) * sizeof(pool_data))) +#define POOLallocHTLine(ptr, size) ptr = (HTLine*) ALLOC_IN_POOL(&HTMainText->pool, (unsigned) LINE_SIZE(size)) +#define POOLallocstring(ptr, len) ptr = (char*) ALLOC_IN_POOL(&HTMainText->pool, (unsigned) ((len) + 1)) +#define POOLtypecalloc(T, ptr) ptr = (T*) ALLOC_IN_POOL(&HTMainText->pool, (unsigned) sizeof(T)) + +/**************************************************************************/ +/* + * Allocates 'n' items in the pool of type 'HTPool' pointed by 'poolptr'. + * Returns a pointer to the "allocated" memory if successful. + * Updates 'poolptr' if necessary. + */ +static void *ALLOC_IN_POOL(HTPool ** ppoolptr, unsigned request) +{ + HTPool *pool = *ppoolptr; + pool_data *ptr; + unsigned n; + unsigned j; + + if (!pool) { + outofmem(__FILE__, "ALLOC_IN_POOL"); + } else { + n = request; + if (n == 0) + n = 1; + j = (n % ALIGN_SIZE); + if (j != 0) + n += (unsigned) (ALIGN_SIZE - j); + n /= sizeof(pool_data); + + if (POOL_SIZE >= (pool->used + n)) { + ptr = pool->data + pool->used; + pool->used += n; + } else { + HTPool *newpool = (HTPool *) LY_CALLOC((size_t) 1, sizeof(HTPool)); + + if (!newpool) { + outofmem(__FILE__, "ALLOC_IN_POOL"); + } else { + newpool->prev = pool; + newpool->used = n; + ptr = newpool->data; + *ppoolptr = newpool; + } + } + } + return ptr; +} + +/* + * Returns a pointer to initialized pool of type 'HTPool', or NULL if fails. + */ +static HTPool *POOL_NEW(void) +{ + HTPool *poolptr = (HTPool *) LY_CALLOC((size_t) 1, sizeof(HTPool)); + + if (poolptr) { + poolptr->prev = NULL; + poolptr->used = 0; + } + return poolptr; +} + +/* + * Frees a pool of type 'HTPool' pointed by poolptr. + */ +static void POOL_FREE(HTPool * poolptr) +{ + HTPool *cur = poolptr; + HTPool *prev; + + while (cur) { + prev = cur->prev; + free(cur); + cur = prev; + } +} + +/**************************************************************************/ +/**************************************************************************/ + +typedef struct _line { + struct _line *next; + struct _line *prev; + unsigned short offset; /* Implicit initial spaces */ + unsigned short size; /* Number of characters */ +#if defined(USE_COLOR_STYLE) + HTStyleChange *styles; + unsigned short numstyles; +#endif + char data[1]; /* Space for terminator at least! */ +} HTLine; + + /* Allow for terminator */ +#define LINE_SIZE(size) (sizeof(HTLine) + (size_t)(size)) + +#ifndef HTLINE_NOT_IN_POOL +#define HTLINE_NOT_IN_POOL 0 /* debug with this set to 1 */ +#endif + +#if HTLINE_NOT_IN_POOL +#define allocHTLine(ptr, size) { ptr = (HTLine *)calloc(1, LINE_SIZE(size)); } +#define freeHTLine(self, ptr) { \ + if (ptr && ptr != TEMP_LINE(self, 0) && ptr != TEMP_LINE(self, 1)) \ + FREE(ptr); \ + } +#else +#define allocHTLine(ptr, size) POOLallocHTLine(ptr, size) +#define freeHTLine(self, ptr) {} +#endif + +/* + * Last line buffer; the second is used in split_line(). Not in pool! + * We cannot wrap in middle of multibyte sequences, so allocate 2 extra + * for a workspace. This is stored in the HText, to prevent confusion + * between different documents. Note also that it is declared with an + * HTLine at the beginning so pointers will be properly aligned. + */ +typedef struct { + HTLine base; + char data[MAX_LINE + 2]; +} HTLineTemp; + +#define TEMP_LINE(p,n) ((HTLine *)&(p->temp_line[n])) + +typedef struct _TextAnchor { + struct _TextAnchor *next; + struct _TextAnchor *prev; /* www_user_search only! */ + int sgml_offset; /* used for updating position after reparsing */ + int number; /* For user interface */ + int show_number; /* For user interface (unique-urls) */ + int line_num; /* Place in document */ + short line_pos; /* Bytes/chars - extent too */ + short extent; /* (see HText_trimHightext) */ + BOOL show_anchor; /* Show the anchor? */ + BOOL inUnderline; /* context is underlined */ + BOOL expansion_anch; /* TEXTAREA edit new anchor */ + char link_type; /* Normal, internal, or form? */ + FormInfo *input_field; /* Info for form links */ + HiliteList lites; + + HTChildAnchor *anchor; +} TextAnchor; + +typedef struct { + char *name; /* ID value of TAB */ + int column; /* Zero-based column value */ +} HTTabID; + +typedef enum { + S_text, + S_esc, + S_dollar, + S_paren, + S_nonascii_text, + S_dollar_paren, + S_jisx0201_text +} eGridState; /* Escape sequence? */ + +#ifdef USE_TH_JP_AUTO_DETECT +typedef enum { /* Detected Kanji code */ + DET_SJIS, + DET_EUC, + DET_NOTYET, + DET_MIXED +} eDetectedKCode; + +typedef enum { + SJIS_state_neutral, + SJIS_state_in_kanji, + SJIS_state_has_bad_code +} eSJIS_status; + +typedef enum { + EUC_state_neutral, + EUC_state_in_kanji, + EUC_state_in_kana, + EUC_state_has_bad_code +} eEUC_status; +#endif + +/* Notes on struct _HText: + * next_line is valid if stale is false. + * top_of_screen line means the line at the top of the screen + * or just under the title if there is one. + */ +struct _HText { + HTParentAnchor *node_anchor; + + HTLine *last_line; + HTLineTemp temp_line[2]; + int Lines; /* Number of them */ + TextAnchor *first_anchor; /* double-linked on demand */ + TextAnchor *last_anchor; + TextAnchor *last_anchor_before_stbl; + TextAnchor *last_anchor_before_split; + HTList *forms; /* also linked internally */ + int last_anchor_number; /* user number */ + BOOL source; /* Is the text source? */ + BOOL toolbar; /* Toolbar set? */ + HTList *tabs; /* TAB IDs */ + HTList *hidden_links; /* Content-less links ... */ + int hiddenlinkflag; /* ... and how to treat them */ + BOOL no_cache; /* Always refresh? */ +#ifdef EXP_JAPANESE_SPACES + char LastChars[7]; /* utf-8 buffer */ +#else + char LastChar; /* For absorbing white space */ +#endif + +/* For Internal use: */ + HTStyle *style; /* Current style */ + int display_on_the_fly; /* Lines left */ + int top_of_screen; /* Line number */ + HTLine *top_of_screen_line; /* Top */ + HTLine *next_line; /* Bottom + 1 */ + unsigned permissible_split; /* in last line */ + BOOL in_line_1; /* of paragraph */ + BOOL stale; /* Must refresh */ + BOOL page_has_target; /* has target on screen */ + BOOL has_utf8; /* has utf-8 on screen or line */ + BOOL had_utf8; /* had utf-8 when last displayed */ + int next_number; /* next a->number value */ +#ifdef DISP_PARTIAL + int first_lineno_last_disp_partial; + int last_lineno_last_disp_partial; +#endif + STable_info *stbl; + HTList *enclosed_stbl; + + HTkcode kcode; /* Kanji code? */ + HTkcode specified_kcode; /* Specified Kanji code */ +#ifdef USE_TH_JP_AUTO_DETECT + eDetectedKCode detected_kcode; + eSJIS_status SJIS_status; + eEUC_status EUC_status; +#endif + eGridState state; /* Escape sequence? */ + int kanji_buf; /* Lead multibyte */ + int in_sjis; /* SJIS flag */ + int halted; /* emergency halt */ + + BOOL have_8bit_chars; /* Any non-ASCII chars? */ + LYUCcharset *UCI; /* node_anchor UCInfo */ + int UCLYhndl; /* charset we are fed */ + UCTransParams T; + + HTStream *target; /* Output stream */ + HTStreamClass targetClass; /* Output routines */ + + HTPool *pool; /* this HText memory pool */ + +#ifdef USE_SOURCE_CACHE + /* + * Parse settings when this HText was generated. + */ + BOOL clickable_images; + BOOL pseudo_inline_alts; + BOOL verbose_img; + BOOL raw_mode; + BOOL historical_comments; + BOOL minimal_comments; + BOOL soft_dquotes; + int old_dtd; + int keypad_mode; + int disp_lines; /* Screen size */ + int disp_cols; /* Used for reports only */ +#endif +}; + +/* exported */ +void *HText_pool_calloc(HText *text, unsigned size) +{ + return (void *) ALLOC_IN_POOL(&text->pool, size); +} + +static void HText_AddHiddenLink(HText *text, TextAnchor *textanchor); + +#ifdef USE_JUSTIFY_ELTS +BOOL can_justify_here; +BOOL can_justify_here_saved; + +BOOL can_justify_this_line; /* =FALSE if line contains form objects */ +int wait_for_this_stacked_elt; /* -1 if can justify contents of the + + element on the op of stack. If positive - specifies minimal stack depth + plus 1 at which we can justify element (can be MAX_LINE+2 if + ok_justify ==FALSE or in psrcview. */ +BOOL form_in_htext; /*to indicate that we are in form (since HTML_FORM is + + not stacked in the HTML.c */ +BOOL in_DT = FALSE; + +#ifdef DEBUG_JUSTIFY +BOOL can_justify_stack_depth; /* can be 0 or 1 if all code is correct */ +#endif + +typedef struct { + int byte_len; /*length in bytes */ + int cell_len; /*length in cells */ +} ht_run_info; + +static int justify_start_position; /* this is an index of char from which + + justification can start (eg after "* " preceding
  • text) */ + +static int ht_num_runs; /*the number of runs filled */ +static ht_run_info ht_runs[MAX_LINE]; +static BOOL this_line_was_split; +static TextAnchor *last_anchor_of_previous_line; +static BOOL have_raw_nbsps = FALSE; + +void ht_justify_cleanup(void) +{ + wait_for_this_stacked_elt = !ok_justify +# ifdef USE_PRETTYSRC + || psrc_view +# endif + ? 30000 /*MAX_NESTING */ + 2 /*some unreachable value */ + : -1; + can_justify_here = TRUE; + can_justify_this_line = TRUE; + form_in_htext = FALSE; + + last_anchor_of_previous_line = NULL; + this_line_was_split = FALSE; + in_DT = FALSE; + have_raw_nbsps = FALSE; +} + +void mark_justify_start_position(void *text) +{ + if (text && ((HText *) text)->last_line) + justify_start_position = ((HText *) text)->last_line->size; +} + +#define REALLY_CAN_JUSTIFY(text) ( (wait_for_this_stacked_elt<0) && \ + ( text->style->alignment == HT_LEFT || \ + text->style->alignment == HT_JUSTIFY) && \ + !IS_CJK_TTY && !in_DT && \ + can_justify_here && can_justify_this_line && !form_in_htext ) + +#endif /* USE_JUSTIFY_ELTS */ + +/* + * Boring static variable used for moving cursor across + */ +#define UNDERSCORES(n) \ + ((n) >= MAX_LINE ? underscore_string : &underscore_string[(MAX_LINE-1)] - (n)) + +static char underscore_string[MAX_LINE + 1]; +char star_string[MAX_LINE + 1]; + +static int ctrl_chars_on_this_line = 0; /* num of ctrl chars in current line */ +static int utfxtra_on_this_line = 0; /* num of UTF-8 extra bytes in line, + they *also* count as ctrl chars. */ +#ifdef EXP_WCWIDTH_SUPPORT +static int utfxtracells_on_this_line = 0; /* num of UTF-8 extra cells in line */ +#endif + +#ifdef WIDEC_CURSES +# ifdef EXP_WCWIDTH_SUPPORT /* TODO: support for !WIDEC_CURSES */ +#define UTFXTRA_ON_THIS_LINE utfxtracells_on_this_line +# else +#define UTFXTRA_ON_THIS_LINE 0 +# endif +#else +#define UTFXTRA_ON_THIS_LINE utfxtra_on_this_line +#endif + +static HTStyle default_style = +{0, NULL, "(Unstyled)", 0, NULL, "", + (HTFont) 0, 1, HT_BLACK, 0, 0, + 0, 0, 0, HT_LEFT, 1, 0, 0, + NO, NO, 0, 0, 0}; + +static HTList *loaded_texts = NULL; /* A list of all those in memory */ +HTList *search_queries = NULL; /* isindex and whereis queries */ + +#ifdef LY_FIND_LEAKS +static void free_all_texts(void); +#endif + +static BOOL HText_TrueEmptyLine(HTLine *line, HText *text, int IgnoreSpaces); + +static int HText_TrueLineSize(HTLine *line, HText *text, int IgnoreSpaces); + +#ifdef CHECK_FREE_MEM + +/* + * text->halted = 1: have set fake 'Z' and output a message + * 2: next time when HText_appendCharacter is called + * it will append *** MEMORY EXHAUSTED ***, then set + * to 3. + * 3: normal text output will be suppressed (but not anchors, + * form fields etc.) + */ +static void HText_halt(void) +{ + if (HTFormNumber > 0) + HText_DisableCurrentForm(); + if (!HTMainText) + return; + if (HTMainText->halted < 2) + HTMainText->halted = 2; +} + +#define MIN_NEEDED_MEM 5000 + +/* + * Check whether factor*min(bytes,MIN_NEEDED_MEM) is available, + * or bytes if factor is 0. + * MIN_NEEDED_MEM and factor together represent a security margin, + * to take account of all the memory allocations where we don't check + * and of buffers which may be emptied before HTCheckForInterupt() + * is (maybe) called and other things happening, with some chance of + * success. + * This just tries to malloc() the to-be-checked-for amount of memory, + * which might make the situation worse depending how allocation works. + * There should be a better way... - kw + */ +static BOOL mem_is_avail(int factor, size_t bytes) +{ + void *p; + + if (bytes < MIN_NEEDED_MEM && factor > 0) + bytes = MIN_NEEDED_MEM; + if (factor == 0) + factor = 1; + p = malloc((size_t) factor * bytes); + if (p) { + FREE(p); + return YES; + } else { + return NO; + } +} + +/* + * Replacement for calloc which checks for "enough" free memory + * (with some security margins) and tries various recovery actions + * if deemed necessary. - kw + */ +static void *LY_check_calloc(size_t nmemb, size_t size) +{ + int i, n; + + if (mem_is_avail(4, nmemb * size)) { + return (calloc(nmemb, size)); + } + n = HTList_count(loaded_texts); + for (i = n - 1; i > 0; i--) { + HText *t = (HText *) HTList_objectAt(loaded_texts, i); + + CTRACE((tfp, + "\nBUG *** Emergency freeing document %d/%d for '%s'%s!\n", + i + 1, n, + ((t && t->node_anchor && + t->node_anchor->address) ? + t->node_anchor->address : "unknown anchor"), + ((t && t->node_anchor && + t->node_anchor->post_data) ? + " with POST data" : ""))); + HTList_removeObjectAt(loaded_texts, i); + HText_free(t); + if (mem_is_avail(4, nmemb * size)) { + return (calloc(nmemb, size)); + } + } + LYFakeZap(YES); + if (!HTMainText || HTMainText->halted <= 1) { + if (!mem_is_avail(2, nmemb * size)) { + HText_halt(); + if (mem_is_avail(0, (size_t) 700)) { + HTAlert(gettext("Memory exhausted, display interrupted!")); + } + } else { + if ((!HTMainText || HTMainText->halted == 0) && + mem_is_avail(0, (size_t) 700)) { + HTAlert(gettext("Memory exhausted, will interrupt transfer!")); + if (HTMainText) + HTMainText->halted = 1; + } + } + } + return (calloc(nmemb, size)); +} + +#endif /* CHECK_FREE_MEM */ + +#ifdef EXP_WCWIDTH_SUPPORT +static int utfextracells(const char *s) +{ + UCode_t ucs = UCGetUniFromUtf8String(&s); + int result = 0; + + if (ucs > 0) { + int cells = wcwidth((wchar_t) ucs); + + if (cells > 1) + result = (cells - 1); + } + return result; +} + +static void permit_split_after_CJchar(HText *text, const char *s, unsigned short pos) +{ + /* Can split after almost any CJ char (Korean uses space) */ + /* TODO: UAX#14 Unicode Line Breaking Algorithm (use ICU4C?) */ + if (isUTF8CJChar(s)) + text->permissible_split = pos; +} +#endif /* EXP_WCWIDTH_SUPPORT */ + +#if defined(EXP_WCWIDTH_SUPPORT) || defined(EXP_JAPANESE_SPACES) +BOOL isUTF8CJChar(const char *s) +{ + UCode_t u = UCGetUniFromUtf8String(&s); + + if ((u >= 0x4e00 && u <= 0x9fff) || /* CJK Unified Ideographs */ + (u >= 0x3000 && u <= 0x30ff) || /* CJK Symbols and Punctuation, Hiragana, Katakana */ + (u >= 0xff00 && u <= 0xffef) || /* Halfwidth and Fullwidth Forms. Fullwidth ?! are often used */ + /* rare characters */ + (u >= 0x3400 && u <= 0x4dbf) || /* CJK Unified Ideographs Extension A */ + (u >= 0xf900 && u <= 0xfaff) || /* CJK Compatibility Ideographs */ + (u >= 0x20000 && u <= 0x3ffff)) /* {Supplementary,Tertiary} Ideographic Plane */ + return YES; + return NO; +} +#endif /* EXP_WCWIDTH_SUPPORT || EXP_JAPANESE_SPACES */ + +#ifdef USE_COLOR_STYLE +/* + * Color style information is stored with the multibyte-character offset into + * the string at which the style would apply. Compute the corresponding column + * so we can compare it with the updated column value after writing strings + * with curses. + * + * The offsets count multibyte characters. Other parts of the code assume each + * character uses one cell, but some CJK (or UTF-8) codes use two cells. We + * need to know the number of cells. + */ +static int StyleToCols(HText *text, HTLine *line, int nstyle) +{ + int result = line->offset; /* this much is spaces one byte/cell */ + int nchars = line->styles[nstyle].sc_horizpos; + char *data = line->data; + char *last = line->size + data; + int utf_extra; + + while (nchars > 0 && data < last) { + if (IsSpecialAttrChar(*data) && *data != LY_SOFT_NEWLINE) { + ++data; + } else { + utf_extra = (int) utf8_length(text->T.output_utf8, data); + if (utf_extra++) { + result += LYstrExtent(data, utf_extra, 2); + data += utf_extra; + } else if (is_CJK2(*data)) { + data += 2; + result += 2; + } else { + ++data; + ++result; + } + --nchars; + } + } + + return result; +} +#endif + +/* + * Clear highlight information for a given anchor + * (text was allocated in the pool). + */ +static void LYClearHiText(TextAnchor *a) +{ + a->lites.hl_info = NULL; + a->lites.hl_base.hl_text = NULL; + a->lites.hl_len = 0; +} + +#define LYFreeHiText(a) FREE((a)->lites.hl_info) + +/* + * Set the initial highlight information for a given anchor. + */ +static void LYSetHiText(TextAnchor *a, + const char *text, + unsigned len) +{ + if (text != NULL) { + POOLallocstring(a->lites.hl_base.hl_text, len + 1); + memcpy(a->lites.hl_base.hl_text, text, (size_t) len); + *(a->lites.hl_base.hl_text + len) = '\0'; + + a->lites.hl_len = 1; + } +} + +/* + * Add highlight information for the next line of a anchor. + */ +static void LYAddHiText(TextAnchor *a, + const char *text, + int x) +{ + HiliteInfo *have = a->lites.hl_info; + size_t need = (unsigned) (a->lites.hl_len - 1); + size_t want; + + a->lites.hl_len = (short) (a->lites.hl_len + 1); + want = (size_t) (a->lites.hl_len) * sizeof(HiliteInfo); + if (have != NULL) { + have = (HiliteInfo *) realloc(have, want); + } else { + have = (HiliteInfo *) malloc(want); + } + a->lites.hl_info = have; + + POOLallocstring(have[need].hl_text, strlen(text) + 1); + strcpy(have[need].hl_text, text); + have[need].hl_x = (short) x; +} + +/* + * Return an offset to skip leading blanks in the highlighted link. That is + * needed to avoid having the color-style paint the leading blanks. + */ +#ifdef USE_COLOR_STYLE +static int LYAdjHiTextPos(TextAnchor *a, int count) +{ + char *result; + + if (count >= a->lites.hl_len) + result = NULL; + else if (count > 0) + result = a->lites.hl_info[count - 1].hl_text; + else + result = a->lites.hl_base.hl_text; + + return (result != NULL) ? (int) (LYSkipBlanks(result) - result) : 0; +} + +#else +#define LYAdjHiTextPos(a,count) 0 +#endif + +/* + * Get the highlight text, counting from zero. + */ +static char *LYGetHiTextStr(TextAnchor *a, int count) +{ + char *result; + + if (count >= a->lites.hl_len) + result = NULL; + else if (count > 0) + result = a->lites.hl_info[count - 1].hl_text; + else + result = a->lites.hl_base.hl_text; + if (result) + result += LYAdjHiTextPos(a, count); + return result; +} + +/* + * Get the X-ordinate at which to draw the corresponding highlight-text + */ +static int LYGetHiTextPos(TextAnchor *a, int count) +{ + int result; + + if (count >= a->lites.hl_len) + result = -1; + else if (count > 0) + result = a->lites.hl_info[count - 1].hl_x; + else + result = a->line_pos; + result += LYAdjHiTextPos(a, count); + return result; +} + +/* + * Copy highlighting information from anchor 'b' to 'a'. + */ +static void LYCopyHiText(TextAnchor *a, TextAnchor *b) +{ + int count; + char *s; + + LYClearHiText(a); + for (count = 0;; ++count) { + if ((s = LYGetHiTextStr(b, count)) == NULL) + break; + if (count == 0) { + LYSetHiText(a, s, (unsigned) strlen(s)); + } else { + LYAddHiText(a, s, LYGetHiTextPos(b, count)); + } + } +} + +static void HText_getChartransInfo(HText *me) +{ + me->UCLYhndl = HTAnchor_getUCLYhndl(me->node_anchor, UCT_STAGE_HTEXT); + if (me->UCLYhndl < 0) { + int chndl = current_char_set; + + HTAnchor_setUCInfoStage(me->node_anchor, chndl, + UCT_STAGE_HTEXT, UCT_SETBY_STRUCTURED); + me->UCLYhndl = HTAnchor_getUCLYhndl(me->node_anchor, + UCT_STAGE_HTEXT); + } + me->UCI = HTAnchor_getUCInfoStage(me->node_anchor, UCT_STAGE_HTEXT); +} + +static void PerFormInfo_free(PerFormInfo * form) +{ + if (form) { + FREE(form->data.submit_action); + FREE(form->data.submit_enctype); + FREE(form->data.submit_title); + FREE(form->accept_cs); + FREE(form->thisacceptcs); + FREE(form); + } +} + +static void free_form_fields(FormInfo * input_field) +{ + /* + * Free form fields. + */ + if (input_field->type == F_OPTION_LIST_TYPE && + input_field->select_list != NULL) { + /* + * Free off option lists if present. + * It should always be present for F_OPTION_LIST_TYPE + * unless we had invalid markup which prevented + * HText_setLastOptionValue from finishing its job + * and left the input field in an insane state. - kw + */ + OptionType *optptr = input_field->select_list; + OptionType *tmp; + + while (optptr) { + tmp = optptr; + optptr = tmp->next; + FREE(tmp->name); + FREE(tmp->cp_submit_value); + FREE(tmp); + } + input_field->select_list = NULL; + /* + * Don't free the value field on option + * lists since it points to a option value + * same for orig value. + */ + input_field->value = NULL; + input_field->orig_value = NULL; + input_field->cp_submit_value = NULL; + input_field->orig_submit_value = NULL; + } else { + FREE(input_field->value); + FREE(input_field->orig_value); + FREE(input_field->cp_submit_value); + FREE(input_field->orig_submit_value); + } + FREE(input_field->name); + FREE(input_field->submit_action); + FREE(input_field->submit_enctype); + FREE(input_field->submit_title); + + FREE(input_field->accept_cs); +} + +static void FormList_delete(HTList *forms) +{ + HTList *cur = forms; + PerFormInfo *form; + + while ((form = (PerFormInfo *) HTList_nextObject(cur)) != NULL) + PerFormInfo_free(form); + HTList_delete(forms); +} + +#ifdef DISP_PARTIAL +static void ResetPartialLinenos(HText *text) +{ + if (text != 0) { + text->first_lineno_last_disp_partial = -1; + text->last_lineno_last_disp_partial = -1; + } +} +#endif + +/* Creation Method + * --------------- + */ +HText *HText_new(HTParentAnchor *anchor) +{ +#if defined(VMS) && defined(VAXC) && !defined(__DECC) +#include + int status, VMType = 3, VMTotal; +#endif /* VMS && VAXC && !__DECC */ + HTLine *line = NULL; + HText *self = typecalloc(HText); + + if (!self) + outofmem(__FILE__, "HText_New"); + + CTRACE((tfp, "GridText: start HText_new\n")); + +#if defined(VMS) && defined (VAXC) && !defined(__DECC) + status = lib$stat_vm(&VMType, &VMTotal); + CTRACE((tfp, "GridText: VMTotal = %d\n", VMTotal)); +#endif /* VMS && VAXC && !__DECC */ + + /* + * If the previously shown text had UTF-8 characters on screen, + * remember this in the newly created object. Do this now, before + * the previous object may become invalid. - kw + */ + if (HTMainText) { + self->had_utf8 = HTMainText->has_utf8; + HTMainText->has_utf8 = NO; + } + + if (!loaded_texts) { + loaded_texts = HTList_new(); +#ifdef LY_FIND_LEAKS + atexit(free_all_texts); +#endif + } + + /* + * Links between anchors & documents are a 1-1 relationship. If + * an anchor is already linked to a document we didn't call + * HTuncache_current_document(), so we'll check now + * and free it before reloading. - Dick Wesseling (ftu@fi.ruu.nl) + */ + if (anchor->document) { + HTList_removeObject(loaded_texts, anchor->document); + CTRACE((tfp, "GridText: Auto-uncaching\n")); + + HTAnchor_delete_links(anchor); + ((HText *) anchor->document)->node_anchor = NULL; + HText_free((HText *) anchor->document); + anchor->document = NULL; + } + + HTList_addObject(loaded_texts, self); +#if defined(VMS) && defined(VAXC) && !defined(__DECC) + while (HTList_count(loaded_texts) > HTCacheSize && + VMTotal > HTVirtualMemorySize) +#else + if (HTList_count(loaded_texts) > HTCacheSize) +#endif /* VMS && VAXC && !__DECC */ + { + CTRACE((tfp, "GridText: Freeing off cached doc.\n")); + HText_free((HText *) HTList_removeFirstObject(loaded_texts)); +#if defined(VMS) && defined (VAXC) && !defined(__DECC) + status = lib$stat_vm(&VMType, &VMTotal); + CTRACE((tfp, "GridText: VMTotal reduced to %d\n", VMTotal)); +#endif /* VMS && VAXC && !__DECC */ + } + + self->pool = POOL_NEW(); + if (!self->pool) + outofmem(__FILE__, "HText_New"); + + line = self->last_line = TEMP_LINE(self, 0); + line->next = line->prev = line; + line->offset = line->size = 0; + line->data[line->size] = '\0'; +#ifdef USE_COLOR_STYLE + line->numstyles = 0; + line->styles = stylechanges_buffers[0]; +#endif + self->Lines = 0; + self->first_anchor = self->last_anchor = NULL; + self->last_anchor_before_split = NULL; + self->style = &default_style; + self->top_of_screen = 0; + self->node_anchor = anchor; + self->last_anchor_number = 0; /* Numbering of them for references */ + self->stale = YES; + self->toolbar = NO; + self->tabs = NULL; + self->next_number = 1; +#ifdef USE_SOURCE_CACHE + /* + * Remember the parse settings. + */ + /* *INDENT-EQLS* */ + self->clickable_images = clickable_images; + self->pseudo_inline_alts = pseudo_inline_alts; + self->verbose_img = verbose_img; + self->raw_mode = LYUseDefaultRawMode; + self->historical_comments = historical_comments; + self->minimal_comments = minimal_comments; + self->soft_dquotes = soft_dquotes; + self->old_dtd = Old_DTD; + self->keypad_mode = keypad_mode; + self->disp_lines = LYlines; + self->disp_cols = DISPLAY_COLS; +#endif + + /* + * If we are going to render the List Page, always merge in hidden + * links to get the numbering consistent if form fields are numbered + * and show up as hidden links in the list of links. + * If we are going to render a bookmark file, also always merge in + * hidden links, to get the link numbers consistent with the counting + * in remove_bookmark_link(). Normally a bookmark file shouldn't + * contain any entries with empty titles, but it might happen. - kw + */ + if (anchor->bookmark || + LYIsUIPage3(anchor->address, UIP_LIST_PAGE, 0) || + LYIsUIPage3(anchor->address, UIP_ADDRLIST_PAGE, 0)) + self->hiddenlinkflag = HIDDENLINKS_MERGE; + else + self->hiddenlinkflag = LYHiddenLinks; + self->hidden_links = NULL; + self->no_cache = (BOOLEAN) ((anchor->no_cache || + anchor->post_data) + ? YES + : NO); +#ifdef EXP_JAPANESE_SPACES + memset(self->LastChars, 0, sizeof(self->LastChars)); +#else + self->LastChar = '\0'; +#endif + +#ifndef USE_PRETTYSRC + if (HTOutputFormat == WWW_SOURCE) + self->source = YES; + else + self->source = NO; +#else + /* mark_htext_as_source == TRUE if we are parsing html file (and psrc_view + * is set temporary to false at creation time) + * + * psrc_view == TRUE if source of the text produced by some lynx module + * (like ftp browsers) is requested). - VH + */ + self->source = (BOOL) (LYpsrc + ? mark_htext_as_source || psrc_view + : HTOutputFormat == WWW_SOURCE); + mark_htext_as_source = FALSE; +#endif + HTAnchor_setDocument(anchor, (HyperDoc *) self); + HTFormNumber = 0; /* no forms started yet */ + HTMainText = self; + HTMainAnchor = anchor; + self->display_on_the_fly = 0; + self->kcode = NOKANJI; + self->specified_kcode = NOKANJI; +#ifdef USE_TH_JP_AUTO_DETECT + self->detected_kcode = DET_NOTYET; + self->SJIS_status = SJIS_state_neutral; + self->EUC_status = EUC_state_neutral; +#endif + self->state = S_text; + self->kanji_buf = '\0'; + self->in_sjis = 0; + self->have_8bit_chars = NO; + HText_getChartransInfo(self); + UCSetTransParams(&self->T, + self->UCLYhndl, self->UCI, + current_char_set, + &LYCharSet_UC[current_char_set]); + + /* + * Check the kcode setting if the anchor has a charset element. -FM + */ + HText_setKcode(self, anchor->charset, + HTAnchor_getUCInfoStage(anchor, UCT_STAGE_HTEXT)); + + /* + * Check to see if our underline and star_string need initialization + * if the underline is not filled with dots. + */ + if (underscore_string[0] != '.') { + /* + * Create an array of dots for the UNDERSCORES macro. -FM + */ + memset(underscore_string, '.', (size_t) (MAX_LINE - 1)); + underscore_string[(MAX_LINE - 1)] = '\0'; + underscore_string[MAX_LINE] = '\0'; + /* + * Create an array of underscores for the STARS macro. -FM + */ + memset(star_string, '_', (size_t) (MAX_LINE - 1)); + star_string[(MAX_LINE - 1)] = '\0'; + star_string[MAX_LINE] = '\0'; + } + + underline_on = FALSE; /* reset */ + bold_on = FALSE; + +#ifdef DISP_PARTIAL + /* + * By this function we create HText object + * so we may start displaying the document while downloading. - LP + */ + if (display_partial_flag) { + display_partial = TRUE; /* enable HTDisplayPartial() */ + NumOfLines_partial = 0; /* initialize */ + } + + /* + * These two fields should only be set to valid line numbers + * by calls of display_page during partial displaying. This + * is just so that the FIRST display_page AFTER that can avoid + * repainting the same lines on the screen. - kw + */ + ResetPartialLinenos(self); +#endif + +#ifdef USE_JUSTIFY_ELTS + ht_justify_cleanup(); +#endif + return self; +} + +/* Creation Method 2 + * --------------- + * + * Stream is assumed open and left open. + */ +HText *HText_new2(HTParentAnchor *anchor, + HTStream *stream) +{ + HText *result = HText_new(anchor); + + if (stream) { + result->target = stream; + result->targetClass = *stream->isa; /* copy action procedures */ + } + return result; +} + +/* Free Entire Text + * ---------------- + */ +void HText_free(HText *self) +{ + if (!self) + return; + +#if HTLINE_NOT_IN_POOL + { + HTLine *f = FirstHTLine(self); + HTLine *l = self->last_line; + + while (l != f) { /* Free off line array */ + self->last_line = l->prev; + freeHTLine(self, l); + l = self->last_line; + } + freeHTLine(self, f); + } +#endif + + while (self->first_anchor) { /* Free off anchor array */ + TextAnchor *l = self->first_anchor; + + self->first_anchor = l->next; + + if (l->link_type == INPUT_ANCHOR && l->input_field) { + free_form_fields(l->input_field); + } + + LYFreeHiText(l); + } + FormList_delete(self->forms); + + /* + * Free the tabs list. -FM + */ + if (self->tabs) { + HTTabID *Tab = NULL; + HTList *cur = self->tabs; + + while (NULL != (Tab = (HTTabID *) HTList_nextObject(cur))) { + FREE(Tab->name); + FREE(Tab); + } + HTList_delete(self->tabs); + self->tabs = NULL; + } + + /* + * Free the hidden links list. -FM + */ + if (self->hidden_links) { + LYFreeStringList(self->hidden_links); + self->hidden_links = NULL; + } + + /* + * Invoke HTAnchor_delete() to free the node_anchor + * if it is not a destination of other links. -FM + */ + if (self->node_anchor) { + HTAnchor_resetUCInfoStage(self->node_anchor, -1, UCT_STAGE_STRUCTURED, + UCT_SETBY_NONE); + HTAnchor_resetUCInfoStage(self->node_anchor, -1, UCT_STAGE_HTEXT, + UCT_SETBY_NONE); +#ifdef USE_SOURCE_CACHE + /* Remove source cache files and chunks always, even if the + * HTAnchor_delete call does not actually remove the anchor. + * Keeping them would just be a waste of space - they won't + * be used any more after the anchor has been disassociated + * from a HText structure. - kw + */ + HTAnchor_clearSourceCache(self->node_anchor); +#endif + + HTAnchor_delete_links(self->node_anchor); + + HTAnchor_setDocument(self->node_anchor, (HyperDoc *) 0); + + if (HTAnchor_delete(self->node_anchor->parent)) + /* + * Make sure HTMainAnchor won't point + * to an invalid structure. - KW + */ + HTMainAnchor = NULL; + } + + POOL_FREE(self->pool); + FREE(self); +} + +/* Display Methods + * --------------- + */ + +/* Output a line + * ------------- + */ +static int display_line(HTLine *line, + HText *text, + int scrline GCC_UNUSED, + const char *target GCC_UNUSED) +{ + register int i, j; + char buffer[7]; + char *data; + size_t utf_extra = 0; + char LastDisplayChar = ' '; + +#ifdef USE_COLOR_STYLE + int current_style = 0; + +#define inunderline NO +#define inbold NO +#else + BOOL inbold = NO, inunderline = NO; +#endif +#if defined(SHOW_WHEREIS_TARGETS) && !defined(USE_COLOR_STYLE) + const char *cp_tgt; + int i_start_tgt = 0, i_after_tgt; + int HitOffset, LenNeeded; + BOOL intarget = NO; + +#else +#define intarget NO +#endif /* SHOW_WHEREIS_TARGETS && !USE_COLOR_STYLE */ + +#if !(defined(NCURSES_VERSION) || defined(WIDEC_CURSES)) + text->has_utf8 = NO; /* use as per-line flag, except with ncurses */ +#endif + +#if defined(WIDEC_CURSES) + /* + * FIXME: this should not be necessary, but in some wide-character pages + * the output line wraps, foiling our attempt to just use newlines to + * advance to the next page. + */ + LYmove(scrline + (no_title ? 0 : TITLE_LINES) - 1, 0); +#endif + + /* + * Set up the multibyte character buffer, + * and clear the line to which we will be + * writing. + */ + buffer[0] = buffer[1] = buffer[2] = '\0'; + LYclrtoeol(); + + /* + * Add offset, making sure that we do not + * go over the COLS limit on the display. + */ + j = (int) line->offset; + if (j >= DISPLAY_COLS) + j = DISPLAY_COLS - 1; +#ifdef USE_SLANG + SLsmg_forward(j); + i = j; +#else +#ifdef USE_COLOR_STYLE + if (line->size == 0) + i = j; + else +#endif + for (i = 0; i < j; i++) + LYaddch(' '); +#endif /* USE_SLANG */ + + /* + * Add the data, making sure that we do not + * go over the COLS limit on the display. + */ + data = line->data; + i++; + +#ifndef USE_COLOR_STYLE +#if defined(SHOW_WHEREIS_TARGETS) + /* + * If the target is on this line, it will be emphasized. + */ + i_after_tgt = i; + if (target) { + cp_tgt = LYno_attr_mb_strstr(data, + target, + text->T.output_utf8, YES, + &HitOffset, + &LenNeeded); + if (cp_tgt) { + if (((int) line->offset + LenNeeded) >= DISPLAY_COLS) { + cp_tgt = NULL; + } else { + text->page_has_target = YES; + i_start_tgt = i + HitOffset; + i_after_tgt = i + LenNeeded; + } + } + } else { + cp_tgt = NULL; + } +#endif /* SHOW_WHEREIS_TARGETS */ +#endif /* USE_COLOR_STYLE */ + + while ((i <= DISPLAY_COLS) && ((buffer[0] = *data) != '\0')) { + +#ifndef USE_COLOR_STYLE +#if defined(SHOW_WHEREIS_TARGETS) + if (cp_tgt && i >= i_after_tgt) { + if (intarget) { + cp_tgt = LYno_attr_mb_strstr(data, + target, + text->T.output_utf8, YES, + &HitOffset, + &LenNeeded); + if (cp_tgt) { + i_start_tgt = i + HitOffset; + i_after_tgt = i + LenNeeded; + } + if (!cp_tgt || i_start_tgt != i) { + LYstopTargetEmphasis(); + intarget = NO; + if (inbold) + lynx_start_bold(); + if (inunderline) + lynx_start_underline(); + } + } + } +#endif /* SHOW_WHEREIS_TARGETS */ +#endif /* USE_COLOR_STYLE */ + + data++; + +#if defined(USE_COLOR_STYLE) +#define CStyle line->styles[current_style] + + while (current_style < line->numstyles && + i >= (int) (CStyle.sc_horizpos + line->offset + 1)) { + LynxChangeStyle(CStyle.sc_style, CStyle.sc_direction); + current_style++; + } +#endif + switch (buffer[0]) { + +#ifndef USE_COLOR_STYLE + case LY_UNDERLINE_START_CHAR: + if (dump_output_immediately && use_underscore) { + LYaddch('_'); + i++; + } else { + inunderline = YES; + if (!intarget) { +#if defined(PDCURSES) + if (LYShowColor == SHOW_COLOR_NEVER) + lynx_start_bold(); + else + lynx_start_underline(); +#else + lynx_start_underline(); +#endif /* PDCURSES */ + } + } + break; + + case LY_UNDERLINE_END_CHAR: + if (dump_output_immediately && use_underscore) { + LYaddch('_'); + i++; + } else { + inunderline = NO; + if (!intarget) { +#if defined(PDCURSES) + if (LYShowColor == SHOW_COLOR_NEVER) + lynx_stop_bold(); + else + lynx_stop_underline(); +#else + lynx_stop_underline(); +#endif /* PDCURSES */ + } + } + break; + + case LY_BOLD_START_CHAR: + inbold = YES; + if (!intarget) + lynx_start_bold(); + break; + + case LY_BOLD_END_CHAR: + inbold = NO; + if (!intarget) + lynx_stop_bold(); + break; + +#endif /* !USE_COLOR_STYLE */ + case LY_SOFT_NEWLINE: + if (!dump_output_immediately) { + LYaddch('+'); + i++; +#if defined(SHOW_WHEREIS_TARGETS) && !defined(USE_COLOR_STYLE) + i_after_tgt++; +#endif + } + break; + + case LY_SOFT_HYPHEN: + if (*data != '\0' || + isspace(UCH(LastDisplayChar)) || + LastDisplayChar == '-') { + /* + * Ignore the soft hyphen if it is not the last character in + * the line. Also ignore it if is first character following + * the margin, or if it is preceded by a white character (we + * loaded 'M' into LastDisplayChar if it was a multibyte + * character) or hyphen, though it should have been excluded by + * HText_appendCharacter() or by split_line() in those cases. + * -FM + */ + break; + } else { + /* + * Make it a hard hyphen and fall through. -FM + */ + buffer[0] = '-'; + } + /* FALLTHRU */ + + default: +#ifndef USE_COLOR_STYLE +#if defined(SHOW_WHEREIS_TARGETS) + if (!intarget && cp_tgt && i >= i_start_tgt) { + /* + * Start the emphasis. + */ + if (data > cp_tgt) { + LYstartTargetEmphasis(); + intarget = YES; + } + } +#endif /* SHOW_WHEREIS_TARGETS */ +#endif /* USE_COLOR_STYLE */ + if (text->T.output_utf8 && is8bits(buffer[0])) { + text->has_utf8 = YES; + utf_extra = utf8_length(text->T.output_utf8, data - 1); + LastDisplayChar = 'M'; + } + if (utf_extra) { + LYStrNCpy(&buffer[1], data, utf_extra); + LYaddstr(buffer); + buffer[1] = '\0'; + data += utf_extra; + utf_extra = 0; + } else if (is_CJK2(buffer[0])) { + /* + * For CJK strings, by Masanobu Kimura. + */ + if (i <= DISPLAY_COLS) { + buffer[1] = *data; + buffer[2] = '\0'; + data++; + i++; + LYaddstr(buffer); + buffer[1] = '\0'; + /* + * For now, load 'M' into LastDisplayChar, but we should + * check whether it's white and if so, use ' '. I don't + * know if there actually are white CJK characters, and + * we're loading ' ' for multibyte spacing characters in + * this code set, but this will become an issue when the + * development code set's multibyte character handling is + * used. -FM + */ + LastDisplayChar = 'M'; +#ifndef USE_SLANG + { + int y, x; + + getyx(LYwin, y, x); + (void) y; + if (x >= DISPLAY_COLS || x == 0) + break; + } +#endif + } + } else { + LYaddstr(buffer); + LastDisplayChar = buffer[0]; + } + i++; + } /* end of switch */ + } /* end of while */ + +#if !(defined(NCURSES_VERSION) || defined(WIDEC_CURSES)) + if (text->has_utf8) { + LYtouchline(scrline); + text->has_utf8 = NO; /* we had some, but have dealt with it. */ + } +#endif + /* + * Add the return. + */ + LYaddch('\n'); + +#if defined(SHOW_WHEREIS_TARGETS) && !defined(USE_COLOR_STYLE) + if (intarget) + LYstopTargetEmphasis(); +#else +#undef intarget +#endif /* SHOW_WHEREIS_TARGETS && !USE_COLOR_STYLE */ +#ifndef USE_COLOR_STYLE + lynx_stop_underline(); + lynx_stop_bold(); +#else + while (current_style < line->numstyles) { + LynxChangeStyle(CStyle.sc_style, CStyle.sc_direction); + current_style++; + } +#undef CStyle +#endif + return (0); +} + +/* Output the title line + * --------------------- + */ +static void display_title(HText *text) +{ + char *title = NULL; + char percent[40]; + unsigned char *tmp = NULL; + int i = 0, j = 0; + int limit; + +#ifdef USE_COLOR_STYLE + int toolbar = 0; +#endif + + /* + * Make sure we have a text structure. -FM + */ + if (!text) + return; + + lynx_start_title_color(); +#ifdef USE_COLOR_STYLE +/* turn the TITLE style on */ + if (last_colorattr_ptr > 0) { + LynxChangeStyle(s_title, STACK_ON); + } else { + LynxChangeStyle(s_title, ABS_ON); + } +#endif /* USE_COLOR_STYLE */ + + /* + * Load the title field. -FM + */ + StrAllocCopy(title, + (HTAnchor_title(text->node_anchor) ? + HTAnchor_title(text->node_anchor) : " ")); /* "" -> " " */ + LYReduceBlanks(title); + + /* + * Generate the page indicator (percent) string. + */ + limit = LYscreenWidth(); + if (limit < 10) { + percent[0] = '\0'; + } else if ((display_lines) <= 0 && LYlines > 0 && + text->top_of_screen <= 99999 && text->Lines <= 999999) { + sprintf(percent, gettext(" (l%d of %d)"), + text->top_of_screen, text->Lines); + } else if ((text->Lines >= display_lines) && (display_lines > 0)) { + int total_pages = ((text->Lines + display_lines) + / display_lines); + int start_of_last_page = ((text->Lines <= display_lines) + ? 0 + : (text->Lines - display_lines)); + + sprintf(percent, gettext(" (p%d of %d)"), + ((text->top_of_screen > start_of_last_page) + ? total_pages + : ((text->top_of_screen + display_lines) / (display_lines))), + total_pages); + } else { + percent[0] = '\0'; + } + + /* Update the terminal-emulator title */ + if (update_term_title) { + CTRACE((tfp, "update_term_title:%s\n", title)); + fprintf(stderr, "\033]0;%s%sLynx\007", title, *title ? " - " : ""); + fflush(stderr); + } + + /* + * Generate and display the title string, with page indicator + * if appropriate, preceded by the toolbar token if appropriate, + * and truncated if necessary. -FM & KW + */ + if (IS_CJK_TTY) { + if (*title && + (tmp = typecallocn(unsigned char, (strlen(title) * 2 + 256)))) { + if (kanji_code == EUC) { + TO_EUC((unsigned char *) title, tmp); + } else if (kanji_code == SJIS) { + TO_SJIS((unsigned char *) title, tmp); + } else { + for (i = 0, j = 0; title[i]; i++) { + if (title[i] != CH_ESC) { /* S/390 -- gil -- 1487 */ + tmp[j++] = UCH(title[i]); + } + } + tmp[j] = '\0'; + } + StrAllocCopy(title, (const char *) tmp); + FREE(tmp); + } + } + LYmove(0, 0); + LYclrtoeol(); +#if defined(SH_EX) && defined(KANJI_CODE_OVERRIDE) + LYaddstr(str_kcode(last_kcode)); +#endif + if (HText_hasToolbar(text)) { + LYaddch('#'); +#ifdef USE_COLOR_STYLE + toolbar = 1; +#endif + } +#ifdef USE_COLOR_STYLE + if (s_forw_backw != NOSTYLE && user_mode != MINIMAL_MODE && + (nhist || nhist_extra > 1)) { + chtype c = nhist ? ACS_LARROW : ' '; + + /* turn the FORWBACKW.ARROW style on */ + LynxChangeStyle(s_forw_backw, STACK_ON); + if (nhist) { + LYaddch(c); + LYaddch(c); + LYaddch(c); + } else + LYmove(0, 3 + toolbar); + if (nhist_extra > 1) { + LYaddch(ACS_RARROW); + LYaddch(ACS_RARROW); + LYaddch(ACS_RARROW); + } + LynxChangeStyle(s_forw_backw, STACK_OFF); + } +#endif /* USE_COLOR_STYLE */ +#ifdef WIDEC_CURSES + i = limit - LYbarWidth - (int) strlen(percent) - LYstrCells(title); + if (i <= 0) { /* title is truncated */ + i = limit - LYbarWidth - (int) strlen(percent) - 3; + if (i <= 0) { /* no room at all */ + title[0] = '\0'; + } else { + strcpy(title + LYstrFittable(title, i), "..."); + } + i = 0; + } + LYmove(0, i); +#else + i = (limit - 1) - (int) (strlen(percent) + strlen(title)); + if (i >= CHAR_WIDTH) { + LYmove(0, i); + } else { + /* + * Truncation takes into account the possibility that + * multibyte characters might be present. -HS (H. Senshu) + */ + int last; + + last = (int) strlen(percent) + CHAR_WIDTH; + if (limit - 3 >= last) { + title[(limit - 3) - last] = '.'; + title[(limit - 2) - last] = '.'; + title[(limit - 1) - last] = '\0'; + } else { + title[(limit - 1) - last] = '\0'; + } + LYmove(0, CHAR_WIDTH); + } +#endif + LYaddstr(title); + if (percent[0] != '\0') + LYaddstr(percent); + LYaddch('\n'); + FREE(title); + +#if defined(USE_COLOR_STYLE) && defined(CAN_CUT_AND_PASTE) + if (s_hot_paste != NOSTYLE) { /* Only if the user set the style */ + LYmove(0, LYcolLimit); + LynxChangeStyle(s_hot_paste, STACK_ON); + LYaddch(ACS_RARROW); + LynxChangeStyle(s_hot_paste, STACK_OFF); + LYmove(1, 0); /* As after \n */ + } +#endif /* USE_COLOR_STYLE */ + +#ifdef USE_COLOR_STYLE +/* turn the TITLE style off */ + LynxChangeStyle(s_title, STACK_OFF); +#endif /* USE_COLOR_STYLE */ + lynx_stop_title_color(); + + return; +} + +/* Output the scrollbar + * --------------------- + */ +#ifdef USE_SCROLLBAR +static void display_scrollbar(HText *text) +{ + int i; + int h = display_lines - 2 * (LYsb_arrow != 0); /* Height of the scrollbar */ + int off = (LYsb_arrow != 0); /* Start of the scrollbar */ + int top_skip, bot_skip, sh, shown; + + LYsb_begin = LYsb_end = -1; + if (!LYShowScrollbar || !text || h <= 2 + || text->Lines <= display_lines) + return; + + if (text->top_of_screen >= text->Lines - display_lines) { + /* Only part of the screen shows actual text */ + shown = text->Lines - text->top_of_screen; + + if (shown <= 0) + shown = 1; + } else + shown = display_lines; + /* Each cell of scrollbar represents text->Lines/h lines of text. */ + /* Always smaller than h */ + sh = (shown * h + text->Lines / 2) / text->Lines; + if (sh <= 0) + sh = 1; + if (sh >= h - 1) + sh = h - 2; /* Position at ends indicates BEG and END */ + + if (text->top_of_screen == 0) + top_skip = 0; + else if (text->Lines - (text->top_of_screen + display_lines - 1) <= 0) + top_skip = h - sh; + else { + /* text->top_of_screen between 1 and text->Lines - display_lines + corresponds to top_skip between 1 and h - sh - 1 */ + /* Use rounding to get as many positions into top_skip==h - sh - 1 + as into top_skip == 1: + 1--->1, text->Lines - display_lines + 1--->h - sh. */ + top_skip = (int) (1 + + 1. * (h - sh - 1) * text->top_of_screen + / (text->Lines - display_lines + 1)); + } + bot_skip = h - sh - top_skip; + + LYsb_begin = top_skip; + LYsb_end = h - bot_skip; + + if (LYsb_arrow) { +#ifdef USE_COLOR_STYLE + int s = top_skip ? s_sb_aa : s_sb_naa; + + if (last_colorattr_ptr > 0) { + LynxChangeStyle(s, STACK_ON); + } else { + LynxChangeStyle(s, ABS_ON); + } +#endif /* USE_COLOR_STYLE */ + LYmove(1, LYcolLimit + LYshiftWin); + addch_raw(ACS_UARROW); +#ifdef USE_COLOR_STYLE + LynxChangeStyle(s, STACK_OFF); +#endif /* USE_COLOR_STYLE */ + } +#ifdef USE_COLOR_STYLE + if (last_colorattr_ptr > 0) { + LynxChangeStyle(s_sb_bg, STACK_ON); + } else { + LynxChangeStyle(s_sb_bg, ABS_ON); + } +#endif /* USE_COLOR_STYLE */ + + for (i = 1; i <= h; i++) { +#ifdef USE_COLOR_STYLE + if (i - 1 <= top_skip && i > top_skip) + LynxChangeStyle(s_sb_bar, STACK_ON); + if (i - 1 <= h - bot_skip && i > h - bot_skip) + LynxChangeStyle(s_sb_bar, STACK_OFF); +#endif /* USE_COLOR_STYLE */ + LYmove(i + off, LYcolLimit + LYshiftWin); + if (i > top_skip && i <= h - bot_skip) { + LYaddch(ACS_BLOCK); + } else { + LYaddch(ACS_CKBOARD); + } + } +#ifdef USE_COLOR_STYLE + LynxChangeStyle(s_sb_bg, STACK_OFF); +#endif /* USE_COLOR_STYLE */ + + if (LYsb_arrow) { +#ifdef USE_COLOR_STYLE + int s = bot_skip ? s_sb_aa : s_sb_naa; + + if (last_colorattr_ptr > 0) { + LynxChangeStyle(s, STACK_ON); + } else { + LynxChangeStyle(s, ABS_ON); + } +#endif /* USE_COLOR_STYLE */ + LYmove(h + 2, LYcolLimit + LYshiftWin); + addch_raw(ACS_DARROW); +#ifdef USE_COLOR_STYLE + LynxChangeStyle(s, STACK_OFF); +#endif /* USE_COLOR_STYLE */ + } + return; +} +#else +#define display_scrollbar(text) /*nothing */ +#endif /* USE_SCROLLBAR */ + +/* Output a page + * ------------- + */ +static void display_page(HText *text, + int line_number, + const char *target) +{ + HTLine *line = NULL; + int i; + int title_lines = TITLE_LINES; + +#if defined(USE_COLOR_STYLE) && defined(SHOW_WHEREIS_TARGETS) + const char *cp; +#endif + char tmp[7]; + TextAnchor *Anchor_ptr = NULL; + int stop_before_for_anchors; + FormInfo *FormInfo_ptr; + BOOL display_flag = FALSE; + HTAnchor *link_dest; + HTAnchor *link_dest_intl = NULL; + static int last_nlinks = 0; + static int charset_last_displayed = -1; + +#ifdef DISP_PARTIAL + int last_disp_partial = -1; +#endif + + lynx_mode = NORMAL_LYNX_MODE; + + if (text == NULL) { + /* + * Check whether to force a screen clear to enable scrollback, + * or as a hack to fix a reverse clear screen problem for some + * curses packages. - shf@access.digex.net & seldon@eskimo.com + */ + if (enable_scrollback) { + LYaddch('*'); + LYrefresh(); + LYclear(); + } + LYaddstr("\n\nError accessing document!\nNo data available!\n"); + LYrefresh(); + nlinks = 0; /* set number of links to 0 */ + return; + } +#ifdef DISP_PARTIAL + CheckScreenSize(); + if (display_partial || recent_sizechange || text->stale) { + /* Reset them, will be set near end if all is okay. - kw */ + ResetPartialLinenos(text); + } +#endif /* DISP_PARTIAL */ + + tmp[0] = tmp[1] = tmp[2] = '\0'; + if (target && *target == '\0') + target = NULL; + text->page_has_target = NO; + if (display_lines <= 0) { + /* No screen space to display anything! + * returning here makes it more likely we will survive if + * an xterm is temporarily made very small. - kw */ + return; + } + + line_number = HText_getPreferredTopLine(text, line_number); + + for (i = 0, line = FirstHTLine(text); /* Find line */ + i < line_number && (line != text->last_line); + i++, line = line->next) { /* Loop */ +#ifndef VMS + if (!LYNoCore) { + assert(line->next != NULL); + } else if (line->next == NULL) { + if (enable_scrollback) { + LYaddch('*'); + LYrefresh(); + LYclear(); + } + LYaddstr("\n\nError drawing page!\nBad HText structure!\n"); + LYrefresh(); + nlinks = 0; /* set number of links to 0 */ + return; + } +#else + assert(line->next != NULL); +#endif /* !VMS */ + } /* Loop */ + + if (LYlowest_eightbit[current_char_set] <= 255 && + (current_char_set != charset_last_displayed) && + /* + * current_char_set has changed since last invocation, + * and it's not just 7-bit. + * Also we don't want to do this for -dump and -source etc. + */ + LYCursesON) { +#ifdef EXP_CHARTRANS_AUTOSWITCH + UCChangeTerminalCodepage(current_char_set, + &LYCharSet_UC[current_char_set]); +#endif /* EXP_CHARTRANS_AUTOSWITCH */ + charset_last_displayed = current_char_set; + } + + /* + * Check whether to force a screen clear to enable scrollback, + * or as a hack to fix a reverse clear screen problem for some + * curses packages. - shf@access.digex.net & seldon@eskimo.com + */ + if (enable_scrollback) { + LYaddch('*'); + LYrefresh(); + LYclear(); + } +#ifdef USE_COLOR_STYLE + /* + * Reset stack of color attribute changes to avoid color leaking, + * except if what we last displayed from this text was the previous + * screenful, in which case carrying over the state might be beneficial + * (although it shouldn't generally be needed any more). - kw + */ + if (text->stale || + line_number != text->top_of_screen + (display_lines)) { + last_colorattr_ptr = 0; + } +#endif + + text->top_of_screen = line_number; + text->top_of_screen_line = line; + if (no_title) { + LYmove(0, 0); + title_lines = 0; + } else { + display_title(text); /* will move cursor to top of screen */ + } + display_flag = TRUE; + +#ifdef USE_COLOR_STYLE +#ifdef DISP_PARTIAL + if (display_partial || + line_number != text->first_lineno_last_disp_partial || + line_number > text->last_lineno_last_disp_partial) +#endif /* DISP_PARTIAL */ + ResetCachedStyles(); +#endif /* USE_COLOR_STYLE */ + +#ifdef DISP_PARTIAL + if (display_partial && text->stbl) { + stop_before_for_anchors = Stbl_getStartLineDeep(text->stbl); + if (stop_before_for_anchors > line_number + (display_lines)) + stop_before_for_anchors = line_number + (display_lines); + } else +#endif + stop_before_for_anchors = line_number + (display_lines); + + /* + * Output the page. + */ + if (line) { +#if defined(USE_COLOR_STYLE) && defined(SHOW_WHEREIS_TARGETS) + char *data; + int offset, LenNeeded; +#endif +#ifdef DISP_PARTIAL + if (display_partial || + line_number != text->first_lineno_last_disp_partial) + text->has_utf8 = NO; +#else + text->has_utf8 = NO; +#endif + for (i = 0; i < (display_lines); i++) { + /* + * Verify and display each line. + */ +#ifndef VMS + if (!LYNoCore) { + assert(line != NULL); + } else if (line == NULL) { + if (enable_scrollback) { + LYaddch('*'); + LYrefresh(); + LYclear(); + } + LYaddstr("\n\nError drawing page!\nBad HText structure!\n"); + LYrefresh(); + nlinks = 0; /* set number of links to 0 */ + return; + } +#else + assert(line != NULL); +#endif /* !VMS */ + +#ifdef DISP_PARTIAL + if (!display_partial && + line_number == text->first_lineno_last_disp_partial && + i + line_number <= text->last_lineno_last_disp_partial) + LYmove((i + title_lines + 1), 0); + else +#endif + display_line(line, text, i + 1, target); + +#if defined(SHOW_WHEREIS_TARGETS) +#ifdef USE_COLOR_STYLE /* otherwise done in display_line - kw */ + /* + * If the target is on this line, recursively + * seek and emphasize it. -FM + */ + data = (char *) line->data; + offset = (int) line->offset; + while (non_empty(target) && + (cp = LYno_attr_mb_strstr(data, + target, + text->T.output_utf8, YES, + NULL, + &LenNeeded)) != NULL && + ((int) line->offset + LenNeeded) <= DISPLAY_COLS) { + size_t itmp = 0; + size_t written = 0; + int x_off = offset + (int) (cp - data); + size_t len = strlen(target); + size_t utf_extra = 0; + + text->page_has_target = YES; + + /* + * Start the emphasis. + */ + LYstartTargetEmphasis(); + + /* + * Output the target characters. + */ + for (; + written < len && (tmp[0] = data[itmp]) != '\0'; + itmp++) { + if (IsSpecialAttrChar(tmp[0]) && tmp[0] != LY_SOFT_NEWLINE) { + /* + * Ignore special characters. + */ + x_off--; + + } else if (&data[itmp] >= cp) { + if (cp == &data[itmp]) { + /* + * First printable character of target. + */ + LYmove((i + title_lines), + line->offset + LYstrExtent2(line->data, + x_off - line->offset)); + } + /* + * Output all the printable target chars. + */ + utf_extra = utf8_length(text->T.output_utf8, data + itmp); + if (utf_extra) { + LYStrNCpy(&tmp[1], &line->data[itmp + 1], utf_extra); + itmp += utf_extra; + LYaddstr(tmp); + tmp[1] = '\0'; + written += (utf_extra + 1); + } else if (IS_CJK_TTY && is8bits(tmp[0])) { + /* + * For CJK strings, by Masanobu Kimura. + */ + tmp[1] = data[++itmp]; + LYaddstr(tmp); + tmp[1] = '\0'; + written += 2; + } else { + LYaddstr(tmp); + written++; + } + } + } + + /* + * Stop the emphasis, and reset the offset and + * data pointer for our current position in the + * line. -FM + */ + LYstopTargetEmphasis(); + data = (char *) &data[itmp]; + offset = (int) (data - line->data + line->offset); + + } /* end while */ + LYmove((i + title_lines + 1), 0); +#endif /* USE_COLOR_STYLE */ +#endif /* SHOW_WHEREIS_TARGETS */ + + /* + * Stop if this is the last line. Otherwise, make sure + * display_flag is set and process the next line. -FM + */ + if (line == text->last_line) { + /* + * Clear remaining lines of display. + */ + for (i++; i < (display_lines); i++) { + LYmove((i + title_lines), 0); + LYclrtoeol(); + } + break; + } +#ifdef DISP_PARTIAL + if (display_partial) { + /* + * Remember as fully shown during last partial display, + * if it was not the last text line. - kw + */ + last_disp_partial = i + line_number; + } +#endif /* DISP_PARTIAL */ + display_flag = TRUE; + line = line->next; + } /* end of "Verify and display each line." loop */ + } + /* end "Output the page." */ + text->next_line = line; /* Line after screen */ + text->stale = NO; /* Display is up-to-date */ + + /* + * Add the anchors to Lynx structures. + */ + nlinks = 0; + for (Anchor_ptr = text->first_anchor; + Anchor_ptr != NULL && Anchor_ptr->line_num <= stop_before_for_anchors; + Anchor_ptr = Anchor_ptr->next) { + + if (Anchor_ptr->line_num >= line_number + && Anchor_ptr->line_num < stop_before_for_anchors) { + char *hi_string = LYGetHiTextStr(Anchor_ptr, 0); + + /* + * Load normal hypertext anchors. + */ + if (Anchor_ptr->show_anchor + && non_empty(hi_string) + && (Anchor_ptr->link_type & HYPERTEXT_ANCHOR)) { + int count; + char *s; + + for (count = 0;; ++count) { + s = LYGetHiTextStr(Anchor_ptr, count); + if (count == 0) + LYSetHilite(nlinks, s); + if (s == NULL) + break; + if (count != 0) { + LYAddHilite(nlinks, s, LYGetHiTextPos(Anchor_ptr, count)); + } + } + + links[nlinks].inUnderline = Anchor_ptr->inUnderline; + + links[nlinks].sgml_offset = Anchor_ptr->sgml_offset; + links[nlinks].anchor_number = Anchor_ptr->number; + links[nlinks].anchor_line_num = Anchor_ptr->line_num; + + link_dest = HTAnchor_followLink(Anchor_ptr->anchor); + { + auto char *cp_AnchorAddress = NULL; + + if (traversal) { + cp_AnchorAddress = stub_HTAnchor_address(link_dest); + } else if (track_internal_links) { + if (Anchor_ptr->link_type == INTERNAL_LINK_ANCHOR) { + link_dest_intl = HTAnchor_followTypedLink(Anchor_ptr->anchor, + HTInternalLink); + if (link_dest_intl && link_dest_intl != link_dest) { + + CTRACE((tfp, + "GridText: display_page: unexpected typed link to %s!\n", + link_dest_intl->parent->address)); + link_dest_intl = NULL; + } + } else { + link_dest_intl = NULL; + } + if (link_dest_intl) { + char *cp2 = HTAnchor_address(link_dest_intl); + + cp_AnchorAddress = cp2; + } else { + cp_AnchorAddress = HTAnchor_address(link_dest); + } + } else { + cp_AnchorAddress = HTAnchor_address(link_dest); + } + FREE(links[nlinks].lname); + + if (cp_AnchorAddress != NULL) + links[nlinks].lname = cp_AnchorAddress; + else + StrAllocCopy(links[nlinks].lname, empty_string); + } + + links[nlinks].lx = Anchor_ptr->line_pos; + links[nlinks].ly = ((Anchor_ptr->line_num + 1) - line_number); + if (link_dest_intl) + links[nlinks].type = WWW_INTERN_LINK_TYPE; + else + links[nlinks].type = WWW_LINK_TYPE; + links[nlinks].target = empty_string; + links[nlinks].l_form = NULL; + + nlinks++; + display_flag = TRUE; + + } else if (Anchor_ptr->link_type == INPUT_ANCHOR + && Anchor_ptr->input_field->type != F_HIDDEN_TYPE) { + /* + * Handle form fields. + */ + lynx_mode = FORMS_LYNX_MODE; + + FormInfo_ptr = Anchor_ptr->input_field; + + links[nlinks].sgml_offset = Anchor_ptr->sgml_offset; + links[nlinks].anchor_number = Anchor_ptr->number; + links[nlinks].anchor_line_num = Anchor_ptr->line_num; + + links[nlinks].l_form = FormInfo_ptr; + links[nlinks].lx = Anchor_ptr->line_pos; + links[nlinks].ly = ((Anchor_ptr->line_num + 1) - line_number); + links[nlinks].type = WWW_FORM_LINK_TYPE; + links[nlinks].inUnderline = Anchor_ptr->inUnderline; + links[nlinks].target = empty_string; + StrAllocCopy(links[nlinks].lname, empty_string); + + if (FormInfo_ptr->type == F_RADIO_TYPE) { + LYSetHilite(nlinks, + FormInfo_ptr->num_value + ? checked_radio + : unchecked_radio); + } else if (FormInfo_ptr->type == F_CHECKBOX_TYPE) { + LYSetHilite(nlinks, + FormInfo_ptr->num_value + ? checked_box + : unchecked_box); + } else if (FormInfo_ptr->type == F_PASSWORD_TYPE) { + LYSetHilite(nlinks, + STARS(LYstrCells(FormInfo_ptr->value))); + } else { /* TEXT type */ + LYSetHilite(nlinks, + FormInfo_ptr->value); + } + + nlinks++; + /* + * Bold the link after incrementing nlinks. + */ + LYhighlight(FALSE, (nlinks - 1), target); + + display_flag = TRUE; + + } else { + /* + * Not showing anchor. + */ + if (non_empty(hi_string)) + CTRACE((tfp, + "\nGridText: Not showing link, hightext=%s\n", + hi_string)); + } + } + + if (nlinks == MAXLINKS) { + /* + * Links array is full. If interactive, tell user + * to use half-page or two-line scrolling. -FM + */ + if (LYCursesON) { + HTAlert(MAXLINKS_REACHED); + } + CTRACE((tfp, "\ndisplay_page: MAXLINKS reached.\n")); + break; + } + } /* end of loop "Add the anchors to Lynx structures." */ + + /* + * Free any un-reallocated links[] entries + * from the previous page draw. -FM + */ + LYFreeHilites(nlinks, last_nlinks); + last_nlinks = nlinks; + + /* + * If Anchor_ptr is not NULL and is not pointing to the last + * anchor, then there are anchors farther down in the document, + * and we need to flag this for traversals. + */ + more_links = FALSE; + if (traversal && Anchor_ptr) { + if (Anchor_ptr->next) + more_links = TRUE; + } + + if (!display_flag) { + /* + * Nothing on the page. + */ + LYaddstr("\n Document is empty"); + } + display_scrollbar(text); + +#ifdef DISP_PARTIAL + if (display_partial && display_flag && + last_disp_partial >= text->top_of_screen && + !enable_scrollback && + !recent_sizechange) { /* really remember them if ok - kw */ + text->first_lineno_last_disp_partial = text->top_of_screen; + text->last_lineno_last_disp_partial = last_disp_partial; + } else { + ResetPartialLinenos(text); + } +#endif /* DISP_PARTIAL */ + +#if !defined(WIDEC_CURSES) + if (text->has_utf8 || text->had_utf8) { + /* + * For other than ncurses, repainting is taken care of + * by touching lines in display_line and highlight. - kw 1999-10-07 + */ + text->had_utf8 = text->has_utf8; + clearok(curscr, TRUE); + } else if (IS_CJK_TTY) { + /* + * For non-multibyte curses. + * + * Full repainting is necessary, otherwise only part of a multibyte + * character sequence might be written because of curses output + * optimizations. + */ + clearok(curscr, TRUE); + } +#endif /* WIDEC_CURSES */ + + LYrefresh(); + return; +} + +/* Object Building methods + * ----------------------- + * + * These are used by a parser to build the text in an object + */ +void HText_beginAppend(HText *text) +{ + text->permissible_split = 0; + text->in_line_1 = YES; + +} + +/* + * LYcols_cu is the notion that the display library has of the screen width. + * Checks of the line length (as the non-UTF-8-aware display library would see + * it) against LYcols_cu are used to try to prevent lines with UTF-8 chars from + * being wrapped by the library when they shouldn't. If there is no display + * library involved, i.e., dump_output_immediately, no such limit should be + * imposed. MAX_COLS should be just as good as any other large value. (But + * don't use INT_MAX or something close to it to, avoid over/underflow.) - kw + */ +#ifdef USE_SLANG +#define LYcols_cu(text) (dump_output_immediately ? MAX_COLS : SLtt_Screen_Cols) +#else +#ifdef WIDEC_CURSES +#define LYcols_cu(text) WRAP_COLS(text) +#else +#define LYcols_cu(text) (dump_output_immediately ? MAX_COLS : DISPLAY_COLS) +#endif +#endif + +/* Add a new line of text + * ---------------------- + * + * On entry, + * + * split is zero for newline function, else number of characters + * before split. + * text->display_on_the_fly + * may be set to indicate direct output of the finished line. + * On exit, + * A new line has been made, justified according to the + * current style. Text after the split (if split nonzero) + * is taken over onto the next line. + * + * If display_on_the_fly is set, then it is decremented and + * the finished line is displayed. + */ + +static int set_style_by_embedded_chars(char *s, + char *e, + unsigned start_c, + unsigned end_c) +{ + int ret = NO; + + while (--e >= s) { + if (UCH(*e) == UCH(end_c)) + break; + if (UCH(*e) == UCH(start_c)) { + ret = YES; + break; + } + } + return ret; +} + +static void move_anchors_in_region(HTLine *line, int line_number, + TextAnchor **prev_anchor, /*updates++ */ + int *prev_head_processed, + int sbyte, + int ebyte, + int shift) /* Likewise */ +{ + /* + * Update anchor positions for anchors that start on this line. Note: we + * rely on a->line_pos counting bytes, not characters. That's one reason + * why HText_trimHightext has to be prevented from acting on these anchors + * in partial display mode before we get a chance to deal with them here. + */ + TextAnchor *a; + int head_processed = *prev_head_processed; + + /* + * We need to know whether (*prev_anchor)->line_pos is "in new coordinates" + * or in old ones. If prev_anchor' head was touched on the previous + * iteration, we set head_processed. The tail may need to be treated now. + */ + for (a = *prev_anchor; + a && a->line_num <= line_number; + a = a->next, head_processed = 0) { + /* extent==0 needs to be special-cased; happens if no text for + the anchor was processed yet. */ + /* Subtract one so that the space is not inserted at the end + of the anchor... */ + int last = a->line_pos + (a->extent ? a->extent - 1 : 0); + + /* Include the anchors started on the previous line */ + if (a->line_num < line_number - 1) + continue; + if (a->line_num == line_number - 1) + last -= line->prev->size + 1; /* Fake "\n" "between" lines counted too */ + if (last < sbyte) /* Completely before the start */ + continue; + + if (!head_processed /* a->line_pos is not edited yet */ + && a->line_num == line_number + && a->line_pos >= ebyte) /* Completely after the end */ + break; + /* Now we know that the anchor context intersects the chunk */ + + /* Fix the start */ + if (!head_processed && a->line_num == line_number + && a->line_pos >= sbyte) { + a->line_pos = (short) (a->line_pos + shift); + a->extent = (short) (a->extent - shift); + head_processed = 1; + } + /* Fix the end */ + if (last < ebyte) { + a->extent = (short) (a->extent + shift); + } else { + break; /* Keep this `a' for the next step */ + } + } + *prev_anchor = a; + *prev_head_processed = head_processed; +} + +/* + * Given a line and two int arrays of old/now position, this function + * creates a new line where spaces have been inserted/removed + * in appropriate places - so that characters at/after the old + * position end up at/after the new position, for each pair, if possible. + * Some necessary changes for anchors starting on this line are also done + * here if needed. Updates 'prev_anchor' internally. + * Returns a newly allocated HTLine* if changes were made + * (caller has to free the old one). + * Returns NULL if no changes needed. (Remove-spaces code may be buggy...) + * - kw + */ +static HTLine *insert_blanks_in_line(HTLine *line, int line_number, + HText *text, + TextAnchor **prev_anchor, /*updates++ */ + int ninserts, + int *oldpos, /* Measured in cells */ + int *newpos) /* Likewise */ +{ + int ioldc = 0; /* count visible characters */ + int ip; /* count insertion pairs */ + +#if defined(USE_COLOR_STYLE) + int istyle = 0; +#endif + int added_chars = 0; + int shift = 0; + int head_processed; + HTLine *mod_line; + char *newdata; + char *s = line->data; + char *pre = s; + char *copied = line->data, *t; + + if (!(line && line->size && ninserts)) + return NULL; + for (ip = 0; ip < ninserts; ip++) + if (newpos[ip] > oldpos[ip] && + (newpos[ip] - oldpos[ip]) > added_chars) + added_chars = newpos[ip] - oldpos[ip]; + if (line->size + added_chars > MAX_LINE - 2) + return NULL; + if (line == text->last_line) { + if (line == TEMP_LINE(text, 0)) + mod_line = TEMP_LINE(text, 1); + else + mod_line = TEMP_LINE(text, 0); + } else { + allocHTLine(mod_line, (unsigned) (line->size + added_chars)); + } + if (!mod_line) + return NULL; + if (!*prev_anchor) + *prev_anchor = text->first_anchor; + head_processed = (*prev_anchor && (*prev_anchor)->line_num < line_number); + memcpy(mod_line, line, LINE_SIZE(0)); + t = newdata = mod_line->data; + ip = 0; + while (ip <= ninserts) { + /* line->size is in bytes, so it may be larger than needed... */ + int curlim = (ip < ninserts + ? oldpos[ip] + : ((int) line->size <= MAX_LINE + ? MAX_LINE + 1 + : (int) line->size + 1)); + + pre = s; + + /* Fast forward to char==curlim or EOL. Stop *before* the + style-change chars. */ + while (*s) { + if (text && text->T.output_utf8 + && UCH(*s) >= 0x80 && UCH(*s) < 0xC0) { + pre = s + 1; + } else if (!IsSpecialAttrChar(*s)) { /* At a "displayed" char */ + if (ioldc >= curlim) + break; + ioldc++; + pre = s + 1; +#ifdef EXP_WCWIDTH_SUPPORT + if (text && text->T.output_utf8 && IS_UTF_FIRST(*s)) + ioldc += utfextracells(s); +#endif + } + s++; + } + + /* Now s is at the "displayed" char, pre is before the style change */ + if (ip) /* Fix anchor positions */ + move_anchors_in_region(line, line_number, prev_anchor /*updates++ */ , + &head_processed, + (int) (copied - line->data), (int) (pre - line->data), + shift); +#if defined(USE_COLOR_STYLE) /* Move styles too */ +#define NStyle mod_line->styles[istyle] + for (; + istyle < line->numstyles && (int) NStyle.sc_horizpos < curlim; + istyle++) + /* Should not we include OFF-styles at curlim? */ + NStyle.sc_horizpos = CAST_POS(NStyle.sc_horizpos + shift); +#endif + while (copied < pre) /* Copy verbatim to byte == pre */ + *t++ = *copied++; + if (ip < ninserts) { /* Insert spaces */ + int delta = newpos[ip] - oldpos[ip] - shift; + + if (delta < 0) { /* Not used yet? */ + while (delta++ < 0 && t > newdata && t[-1] == ' ') + t--, shift--; + } else + shift = newpos[ip] - oldpos[ip]; + while (delta-- > 0) + *t++ = ' '; + } + ip++; + } + while (pre < s) /* Copy remaining style-codes */ + *t++ = *pre++; + /* Check whether the last anchor continues on the next line */ + if (head_processed + && *prev_anchor + && (*prev_anchor)->line_num == line_number) { + (*prev_anchor)->extent = (short) ((*prev_anchor)->extent + shift); + } + *t = '\0'; + mod_line->size = (unsigned short) (t - newdata); + return mod_line; +} + +#if defined(USE_COLOR_STYLE) +#define direction2s(d) ((d) == STACK_OFF \ + ? "OFF" \ + : ((d) == STACK_ON \ + ? "ON" \ + : "*ON")) + +/* + * Found an OFF change not part of an adjacent matched pair. + * + * Walk backward looking for the corresponding ON change. + * Move everything after split_pos to be at split_pos. + * + * This can only work correctly if all changes are correctly nested! If this + * fails, assume it is safer to leave whatever comes before the OFF on the + * previous line alone. + */ +static HTStyleChange *skip_matched_and_correct_offsets(HTStyleChange *end, + HTStyleChange *start, + unsigned split_pos) +{ + HTStyleChange *result = 0; + int level = 0; + HTStyleChange *tmp = end; + + CTRACE_STYLE((tfp, "SKIP Style %d %d (%s), split %u\n", + tmp->sc_horizpos, + tmp->sc_style, + direction2s(tmp->sc_direction), + split_pos)); + for (; tmp >= start; tmp--) { + CTRACE_STYLE((tfp, "... %d %d (%s)\n", + tmp->sc_horizpos, + tmp->sc_style, + direction2s(tmp->sc_direction))); + if (tmp->sc_style == end->sc_style) { + if (tmp->sc_direction == STACK_OFF) { + level--; + } else if (tmp->sc_direction == STACK_ON) { + if (++level == 0) { + result = tmp; + break; + } + } else { + break; + } + } + if (tmp->sc_horizpos > split_pos) { + tmp->sc_horizpos = CAST_POS(split_pos); + } + } + return result; +} +#endif /* USE_COLOR_STYLE */ + +#define reset_horizpos(value) value = 0, value ^= MASK_POS + +static void split_line(HText *text, unsigned split) +{ + HTStyle *style = text->style; + int spare; + int indent = (text->in_line_1 + ? text->style->indent1st + : text->style->leftIndent); + int new_offset; + short alignment; + TextAnchor *a; + int CurLine = text->Lines; + int HeadTrim = 0; + int SpecialAttrChars = 0; + int TailTrim = 0; + int s, s_post, s_pre, t_underline = underline_on, t_bold = bold_on; + char *p; + char *cp; + int ctrl_chars_on_previous_line = 0; + +#ifndef WIDEC_CURSES + int utfxtra_on_previous_line = UTFXTRA_ON_THIS_LINE; +#endif + + HTLine *previous = text->last_line; + HTLine *line; + + /* + * Set new line. + */ + if (previous == TEMP_LINE(text, 0)) + line = TEMP_LINE(text, 1); + else + line = TEMP_LINE(text, 0); + if (line == NULL) + return; + memset(line, 0, (size_t) LINE_SIZE(0)); + + ctrl_chars_on_this_line = 0; /*reset since we are going to a new line */ + utfxtra_on_this_line = 0; /*reset too, we'll count them */ +#ifdef EXP_WCWIDTH_SUPPORT + utfxtracells_on_this_line = 0; +#endif + HText_setLastChar(text, ' '); + +#ifdef DEBUG_APPCH + CTRACE((tfp, "GridText: split_line(%p,%d) called\n", text, split)); + CTRACE((tfp, " previous=%s\n", previous->data)); + CTRACE((tfp, " bold_on=%d, underline_on=%d\n", bold_on, underline_on)); +#endif + + cp = previous->data; + + /* Float LY_SOFT_NEWLINE to the start */ + if (cp[0] == LY_BOLD_START_CHAR + || cp[0] == LY_UNDERLINE_START_CHAR) { + switch (cp[1]) { + case LY_SOFT_NEWLINE: + cp[1] = cp[0]; + cp[0] = LY_SOFT_NEWLINE; + break; + case LY_BOLD_START_CHAR: + case LY_UNDERLINE_START_CHAR: + if (cp[2] == LY_SOFT_NEWLINE) { + cp[2] = cp[1]; + cp[1] = cp[0]; + cp[0] = LY_SOFT_NEWLINE; + } + break; + } + } + if (split > previous->size) { + CTRACE((tfp, + "*** split_line: split==%u greater than last_line->size==%d !\n", + split, previous->size)); + if (split > MAX_LINE) { + split = previous->size; + if ((cp = strrchr(previous->data, ' ')) && + cp - previous->data > 1) + split = (unsigned) (cp - previous->data); + CTRACE((tfp, " split adjusted to %u.\n", split)); + } + } + + text->Lines++; + + previous->next->prev = line; + line->prev = previous; + line->next = previous->next; + previous->next = line; + text->last_line = line; + line->size = 0; + line->offset = 0; + text->permissible_split = 0; /* 12/13/93 */ + line->data[0] = '\0'; + + alignment = style->alignment; + + if (split > 0) { /* Restore flags to the value at the splitting point */ + if (!(dump_output_immediately && use_underscore)) + t_underline = set_style_by_embedded_chars(previous->data, + previous->data + split, + LY_UNDERLINE_START_CHAR, LY_UNDERLINE_END_CHAR); + + t_bold = set_style_by_embedded_chars(previous->data, + previous->data + split, + LY_BOLD_START_CHAR, LY_BOLD_END_CHAR); + + } + + if (!(dump_output_immediately && use_underscore) && t_underline) { + line->data[line->size++] = LY_UNDERLINE_START_CHAR; + line->data[line->size] = '\0'; + ctrl_chars_on_this_line++; + SpecialAttrChars++; + } + if (t_bold) { + line->data[line->size++] = LY_BOLD_START_CHAR; + line->data[line->size] = '\0'; + ctrl_chars_on_this_line++; + SpecialAttrChars++; + } + + /* + * Split at required point + */ + if (split > 0) { /* Delete space at "split" splitting line */ + char *prevdata = previous->data, *linedata = line->data; + unsigned plen; + int i, j; + + /* Split the line. -FM */ + prevdata[previous->size] = '\0'; + previous->size = (unsigned short) split; + + /* + * Trim any spaces or soft hyphens from the beginning + * of our new line. -FM + */ + p = prevdata + split; + while (((*p == ' ' +#ifdef USE_JUSTIFY_ELTS + /* if justification is allowed for prev line, then raw + * HT_NON_BREAK_SPACE are still present in data[] (they'll be + * substituted at the end of this function with ' ') - VH + */ + || *p == HT_NON_BREAK_SPACE +#endif + ) + && (HeadTrim || text->first_anchor || + underline_on || bold_on || + alignment != HT_LEFT || + style->wordWrap || style->freeFormat || + style->spaceBefore || style->spaceAfter)) || + *p == LY_SOFT_HYPHEN) { + p++; + HeadTrim++; + } + + plen = (unsigned) strlen(p); + if (plen) { /* Count funny characters */ + for (i = (int) (plen - 1); i >= 0; i--) { + if (p[i] == LY_UNDERLINE_START_CHAR || + p[i] == LY_UNDERLINE_END_CHAR || + p[i] == LY_BOLD_START_CHAR || + p[i] == LY_BOLD_END_CHAR || + p[i] == LY_SOFT_HYPHEN) { + ctrl_chars_on_this_line++; + } else if (IS_UTF_EXTRA(p[i])) { + utfxtra_on_this_line++; +#ifdef EXP_WCWIDTH_SUPPORT + } else if (IS_UTF_FIRST(p[i])) { + utfxtracells_on_this_line += utfextracells(&p[i]); +#endif + } + if (p[i] == LY_SOFT_HYPHEN && + (int) text->permissible_split < i) + text->permissible_split = (unsigned) (i + 1); + } + ctrl_chars_on_this_line += utfxtra_on_this_line; + + /* Add the data to the new line. -FM */ + for (i = 0, j = (int) strlen(linedata); + (linedata[j++] = p[i++]) != '\0'; + ) ; + line->size = (unsigned short) (line->size + plen); + } + } + + /* + * Economize on space. + */ + p = previous->data + previous->size - 1; + while (p >= previous->data + && (*p == ' ' +#ifdef USE_JUSTIFY_ELTS + /* if justification is allowed for prev line, then raw + * HT_NON_BREAK_SPACE are still present in data[] (they'll be + * substituted at the end of this function with ' ') - VH + */ + || *p == HT_NON_BREAK_SPACE +#endif + ) +#ifdef USE_PRETTYSRC + && !psrc_view /*don't strip trailing whites - since next line can + start with LY_SOFT_NEWLINE - so we don't lose spaces when + 'p'rinting this text to file -VH */ +#endif + && (ctrl_chars_on_this_line || HeadTrim || text->first_anchor || + underline_on || bold_on || + alignment != HT_LEFT || + style->wordWrap || style->freeFormat || + style->spaceBefore || style->spaceAfter)) { + p--; /* Strip trailers. */ + } + /* Strip trailers. */ + TailTrim = (int) (previous->data + previous->size - 1 - p); + previous->size = (unsigned short) (previous->size - TailTrim); + p[1] = '\0'; + + /* + * s is the effective split position, given by either a non-zero + * value of split or by the size of the previous line before + * trimming. - kw + */ + if (split == 0) { + s = previous->size + TailTrim; /* the original size */ + } else { + s = (int) split; + } + s_post = s + HeadTrim; + s_pre = s - TailTrim; + +#ifdef DEBUG_SPLITLINE +#ifdef DEBUG_APPCH + if (s != (int) split) +#endif + CTRACE((tfp, "GridText: split_line(%u [now:%d]) called\n", split, s)); +#endif + +#if defined(USE_COLOR_STYLE) + if (previous->styles == stylechanges_buffers[0]) + line->styles = stylechanges_buffers[1]; + else + line->styles = stylechanges_buffers[0]; + line->numstyles = 0; + { + HTStyleChange *from = previous->styles + previous->numstyles - 1; + HTStyleChange *to = line->styles + MAX_STYLES_ON_LINE - 1; + HTStyleChange *scan, *at_end; + + /* Color style changes after the split position + * are transferred to the new line. Ditto for changes + * in the trimming region, but we stop when we reach an OFF change. + * The second loop below may then handle remaining changes. - kw */ + while (from >= previous->styles && to >= line->styles) { + *to = *from; + if ((int) to->sc_horizpos > s_post) { + to->sc_horizpos = CAST_POS(to->sc_horizpos + + SpecialAttrChars + - s_post); + } else if ((int) to->sc_horizpos > s_pre && + (to->sc_direction == STACK_ON || + to->sc_direction == ABS_ON)) { + if ((int) to->sc_horizpos < s) + to->sc_horizpos = 0; + else + to->sc_horizpos = CAST_POS(SpecialAttrChars); + } else { + break; + } + to--; + from--; + } + /* FROM may be invalid, otherwise it is either an ON change at or + before s_pre, or is an OFF change at or before s_post. */ + + scan = from; + at_end = from; + /* Now on the previous line we have a correctly nested but + possibly non-terminated sequence of style changes. + Terminate it, and duplicate unterminated changes at the + beginning of the new line. */ + while (scan >= previous->styles && at_end >= previous->styles) { + /* The algorithm: scan back though the styles on the previous line. + a) If OFF, skip the matched group. + Report a bug on failure. + b) If ON, (try to) cancel the corresponding ON at at_end, + and the corresponding OFF at to; + If not, put the corresponding OFF at at_end, and copy to to; + */ + if (scan->sc_direction == STACK_OFF) { + scan = skip_matched_and_correct_offsets(scan, previous->styles, + (unsigned) s_pre); + if (!scan) { + CTRACE((tfp, "BUG: styles improperly nested.\n")); + break; + } + } else if (scan->sc_direction == STACK_ON) { + if (at_end->sc_direction == STACK_ON + && at_end->sc_style == scan->sc_style + && (int) at_end->sc_horizpos >= s_pre) + at_end--; + else if (at_end >= previous->styles + MAX_STYLES_ON_LINE - 1) { + CTRACE((tfp, "BUG: style overflow before split_line.\n")); + break; + } else { + at_end++; + at_end->sc_direction = STACK_OFF; + at_end->sc_style = scan->sc_style; + at_end->sc_horizpos = CAST_POS(s_pre); + CTRACE_STYLE((tfp, + "split_line, %d:style[%d] %d (dir=%d)\n", + s_pre, + (int) (at_end - from), + scan->sc_style, + at_end->sc_direction)); + } + if (to < line->styles + MAX_STYLES_ON_LINE - 1 + && to[1].sc_direction == STACK_OFF + && to[1].sc_horizpos <= (unsigned) SpecialAttrChars + && to[1].sc_style == scan->sc_style) + to++; + else if (to >= line->styles) { + *to = *scan; + to->sc_horizpos = CAST_POS(SpecialAttrChars); + to--; + } else { + CTRACE((tfp, "BUG: style overflow after split_line.\n")); + break; + } + } + if ((int) scan->sc_horizpos > s_pre) { + scan->sc_horizpos = CAST_POS(s_pre); + } + scan--; + } + line->numstyles = (unsigned short) (line->styles + + MAX_STYLES_ON_LINE + - 1 - to); + if (line->numstyles > 0 && line->numstyles < MAX_STYLES_ON_LINE) { + int n; + + for (n = 0; n < line->numstyles; n++) + line->styles[n] = to[n + 1]; + } else if (line->numstyles == 0) { + reset_horizpos(line->styles[0].sc_horizpos); + } + previous->numstyles = (unsigned short) (at_end - previous->styles + 1); + if (previous->numstyles == 0) { + reset_horizpos(previous->styles[0].sc_horizpos); + } + } +#endif /*USE_COLOR_STYLE */ + + { + HTLine *temp; + + allocHTLine(temp, previous->size); + if (!temp) + outofmem(__FILE__, "split_line_2"); + + memcpy(temp, previous, LINE_SIZE(previous->size)); +#if defined(USE_COLOR_STYLE) + POOLallocstyles(temp->styles, previous->numstyles); + if (!temp->styles) + outofmem(__FILE__, "split_line_2"); + memcpy(temp->styles, previous->styles, sizeof(HTStyleChange) * previous->numstyles); +#endif + previous = temp; + } + + previous->prev->next = previous; /* Link in new line */ + previous->next->prev = previous; /* Could be same node of course */ + + /* + * Terminate finished line for printing. + */ + previous->data[previous->size] = '\0'; + + /* + * Align left, right or center. + */ + spare = 0; + if ( +#ifdef USE_JUSTIFY_ELTS + this_line_was_split || +#endif + (alignment == HT_CENTER || + alignment == HT_RIGHT) || text->stbl) { + /* Calculate spare character positions if needed */ + for (cp = previous->data; *cp; cp++) { + if (*cp == LY_UNDERLINE_START_CHAR || + *cp == LY_UNDERLINE_END_CHAR || + *cp == LY_BOLD_START_CHAR || + *cp == LY_BOLD_END_CHAR || +#ifndef WIDEC_CURSES + IS_UTF_EXTRA(*cp) || +#endif + *cp == LY_SOFT_HYPHEN) { + ctrl_chars_on_previous_line++; + } + } + if ((previous->size > 0) && + (int) (previous->data[previous->size - 1] == LY_SOFT_HYPHEN)) + ctrl_chars_on_previous_line--; + + /* @@ first line indent */ +#ifdef WIDEC_CURSES + spare = WRAP_COLS(text) + - (int) style->rightIndent + - indent + + ctrl_chars_on_previous_line + - LYstrExtent2(previous->data, previous->size); + if (spare < 0 && LYwideLines) /* Can be wider than screen */ + spare = 0; +#else + spare = WRAP_COLS(text) + - (int) style->rightIndent + - indent + + ctrl_chars_on_previous_line + - previous->size; + if (spare < 0 && LYwideLines) /* Can be wider than screen */ + spare = 0; + + if (spare > 0 && !dump_output_immediately && + text->T.output_utf8 && ctrl_chars_on_previous_line) { + utfxtra_on_previous_line -= UTFXTRA_ON_THIS_LINE; + if (utfxtra_on_previous_line) { + int spare_cu = (LYcols_cu(text) - + utfxtra_on_previous_line - indent + + ctrl_chars_on_previous_line - previous->size); + + /* + * Shift non-leftaligned UTF-8 lines that would be + * mishandled by the display library towards the left + * if this would make them fit. The resulting display + * will not be as intended, but this is better than + * having them split by curses. (Curses cursor movement + * optimization may still cause wrong positioning within + * the line, in particular after a sequence of spaces). + * - kw + */ + if (spare_cu < spare) { + if (spare_cu >= 0) { + if (alignment == HT_CENTER && + (int) (previous->offset + indent + spare / 2 + + previous->size) + - ctrl_chars_on_previous_line + + utfxtra_on_previous_line <= LYcols_cu(text)) + /* do nothing - it still fits - kw */ ; + else { + spare = spare_cu; + } + } else if (indent + (int) previous->offset + spare_cu >= 0) { /* subtract overdraft from effective indentation */ + indent += (int) previous->offset + spare_cu; + previous->offset = 0; + spare = 0; + } + } + } + } +#endif + } + + new_offset = previous->offset; + switch (style->alignment) { + case HT_CENTER: + new_offset += indent + spare / 2; + break; + case HT_RIGHT: + new_offset += indent + spare; + break; + case HT_LEFT: + case HT_JUSTIFY: /* Not implemented */ + default: + new_offset += indent; + break; + } /* switch */ + previous->offset = (unsigned short) ((new_offset < 0) ? 0 : new_offset); + + if (text->stbl) { + /* + * Notify simple table stuff of line split, so that it can + * set the last cell's length. The last cell should and + * its row should really end here, or on one of the following + * lines with no more characters added after the break. + * We don't know whether a cell has been started, so ignore + * errors here. + * This call is down here because we need the + * ctrl_chars_on_previous_line, which have just been re- + * counted above. - kw + */ + Stbl_lineBreak(text->stbl, + text->Lines - 1, + previous->offset, + previous->size - ctrl_chars_on_previous_line); + } + + text->in_line_1 = NO; /* unless caller sets it otherwise */ + + /* + * If we split the line, adjust the anchor + * structure values for the new line. -FM + */ + + if (s > 0) { /* if not completely empty */ + int moved = 0; + + /* In the algorithm below we move or not move anchors between + lines using some heuristic criteria. However, it is + desirable not to have two consequent anchors on different + lines *in a wrong order*! (How can this happen?) + So when the "reasonable choice" is not unique, we use the + MOVED flag to choose one. + */ + /* Our operations can make a non-empty all-whitespace link + empty. So what? */ + if ((a = text->last_anchor_before_split) == 0) + a = text->first_anchor; + + for (; a; a = a->next) { + if (a->line_num == CurLine) { + int len = a->extent, n = a->number, start = a->line_pos; + int end = start + len; + + text->last_anchor_before_split = a; + + /* Which anchors do we leave on the previous line? + a) empty finished (We need a cut-off value. + "Just because": those before s; + this is the only case when we use s, not s_pre/s_post); + b) Those which start before s_pre; + */ + if (start < s_pre) { + if (end <= s_pre) + continue; /* No problem */ + + CTRACE_SPLITLINE((tfp, "anchor %d: no relocation", n)); + if (end > s_post) { + CTRACE_SPLITLINE((tfp, " of the start.\n")); + a->extent = (short) (a->extent + - (TailTrim + HeadTrim) + + SpecialAttrChars); + } else { + CTRACE_SPLITLINE((tfp, ", cut the end.\n")); + a->extent = (short) (s_pre - start); + } + continue; + } else if (start < s && !len + && (!n || (a->show_anchor && !moved))) { + CTRACE_SPLITLINE((tfp, + "anchor %d: no relocation, empty-finished", + n)); + a->line_pos = (short) s_pre; /* Leave at the end of line */ + continue; + } + + /* The rest we relocate */ + moved = 1; + a->line_num++; + CTRACE_SPLITLINE((tfp, + "anchor %d: (T,H,S)=(%d,%d,%d); (line,pos,ext):(%d,%d,%d), ", + n, TailTrim, HeadTrim, SpecialAttrChars, + a->line_num, a->line_pos, a->extent)); + if (end < s_post) { /* Move the end to s_post */ + CTRACE_SPLITLINE((tfp, "Move end +%d, ", s_post - end)); + len += s_post - end; + } + if (start < s_post) { /* Move the start to s_post */ + CTRACE_SPLITLINE((tfp, "Move start +%d, ", s_post - start)); + len -= s_post - start; + start = s_post; + } + a->line_pos = (short) (start - s_post + SpecialAttrChars); + a->extent = (short) len; + + CTRACE_SPLITLINE((tfp, "->(%d,%d,%d)\n", + a->line_num, a->line_pos, a->extent)); + } else if (a->line_num > CurLine) + break; + } + } +#ifdef USE_JUSTIFY_ELTS + /* now perform justification - by VH */ + + if (this_line_was_split + && spare > 0 + && !text->stbl /* We don't inform TRST on the cell width change yet */ + && justify_max_void_percent > 0 + && justify_max_void_percent <= 100 + && justify_max_void_percent >= ((100 * spare) + / (WRAP_COLS(text) + - (int) style->rightIndent + - indent + + ctrl_chars_on_previous_line))) { + /* this is the only case when we need justification */ + char *jp = previous->data + justify_start_position; + ht_run_info *r = ht_runs; + char c; + int d_, r_; + HTLine *jline; + + ht_num_runs = 0; + r->byte_len = r->cell_len = 0; + + for (; (c = *jp) != 0; ++jp) { + if (c == ' ') { + ++r; + ++ht_num_runs; + r->byte_len = r->cell_len = 0; + continue; + } + ++r->byte_len; + if (IsSpecialAttrChar(c)) + continue; + + ++r->cell_len; + if (c == HT_NON_BREAK_SPACE) { + *jp = ' '; /* substitute it */ + continue; + } + if (text->T.output_utf8 && is8bits(c)) { + int utf_extra = (int) utf8_length(text->T.output_utf8, jp); + + r->byte_len += utf_extra; + jp += utf_extra; + } + } + ++ht_num_runs; + + if (ht_num_runs != 1) { + int *oldpos = (int *) malloc(sizeof(int) + * 2 * (size_t) (ht_num_runs - 1)); + int *newpos = oldpos + ht_num_runs - 1; + int i = 1; + + if (oldpos == NULL) + outofmem(__FILE__, "split_line_3"); + + d_ = spare / (ht_num_runs - 1); + r_ = spare % (ht_num_runs - 1); + + /* The first run is not moved, proceed to the second one */ + oldpos[0] = justify_start_position + ht_runs[0].cell_len + 1; + newpos[0] = oldpos[0] + (d_ + (r_-- > 0)); + while (i < ht_num_runs - 1) { + int delta = ht_runs[i].cell_len + 1; + + oldpos[i] = oldpos[i - 1] + delta; + newpos[i] = newpos[i - 1] + delta + (d_ + (r_-- > 0)); + i++; + } + jline = insert_blanks_in_line(previous, CurLine, text, + &last_anchor_of_previous_line /*updates++ */ , + ht_num_runs - 1, oldpos, newpos); + free(oldpos); + if (jline == NULL) + outofmem(__FILE__, "split_line_4"); + previous->next->prev = jline; + previous->prev->next = jline; + + freeHTLine(text, previous); + + previous = jline; + } + if (justify_start_position) { + char *p2 = previous->data; + + for (; p2 < previous->data + justify_start_position; ++p2) + *p2 = (char) (*p2 == HT_NON_BREAK_SPACE ? ' ' : *p2); + } + } else { + if (REALLY_CAN_JUSTIFY(text)) { + char *p2; + + /* it was permitted to justify line, but this function was called + * to end paragraph - we must substitute HT_NON_BREAK_SPACEs with + * spaces in previous line + */ + if (line->size && !text->stbl) { + CTRACE((tfp, + "BUG: justification: shouldn't happen - new line is not empty!\n\t'%.*s'\n", + line->size, line->data)); + } + + for (p2 = previous->data; *p2; ++p2) + if (*p2 == HT_NON_BREAK_SPACE) + *p2 = ' '; + } else if (have_raw_nbsps) { + /* this is very rare case, that can happen in forms placed in + table cells */ + unsigned i; + + for (i = 0; i < previous->size; ++i) + if (previous->data[i] == HT_NON_BREAK_SPACE) + previous->data[i] = ' '; + + /*next line won't be justified, so substitute nbsps in it too */ + for (i = 0; i < line->size; ++i) + if (line->data[i] == HT_NON_BREAK_SPACE) + line->data[i] = ' '; + } + + /* else HT_NON_BREAK_SPACEs were substituted with spaces in + HText_appendCharacter */ + } + /* cleanup */ + can_justify_this_line = TRUE; + justify_start_position = 0; + this_line_was_split = FALSE; + have_raw_nbsps = FALSE; +#endif /* USE_JUSTIFY_ELTS */ + return; +} /* split_line */ + +#ifdef DEBUG_SPLITLINE +static void do_new_line(HText *text, const char *fn, int ln) +{ + CTRACE_SPLITLINE((tfp, "new_line %s@%d\n", fn, ln)); + split_line(text, 0); +} + +#define new_line(text) do_new_line(text, __FILE__, __LINE__) +#else +#define new_line(text) split_line(text, 0) +#endif + +/* Allow vertical blank space + * -------------------------- + */ +static void blank_lines(HText *text, int newlines) +{ + if (HText_TrueEmptyLine(text->last_line, text, FALSE)) { /* No text on current line */ + HTLine *line = text->last_line->prev; + BOOL first = (BOOL) (line == text->last_line); + + if (no_title && first) + return; + +#ifdef USE_COLOR_STYLE + /* Style-change petty requests at the start of the document: */ + if (first && newlines == 1) + return; /* Do not add a blank line at start */ +#endif + + while (line != NULL && + line != text->last_line && + HText_TrueEmptyLine(line, text, FALSE)) { + if (newlines == 0) + break; + newlines--; /* Don't bother: already blank */ + line = line->prev; + } + } else { + newlines++; /* Need also to finish this line */ + } + + for (; newlines; newlines--) { + new_line(text); + } + text->in_line_1 = YES; +} + +/* New paragraph in current style + * ------------------------------ + * See also: setStyle. + */ +void HText_appendParagraph(HText *text) +{ + int after = text->style->spaceAfter; + int before = text->style->spaceBefore; + + blank_lines(text, ((after > before) ? after : before)); +} + +/* Set Style + * --------- + * + * Does not filter unnecessary style changes. + */ +void HText_setStyle(HText *text, HTStyle *style) +{ + int after, before; + + if (!style) + return; /* Safety */ + after = text->style->spaceAfter; + before = style->spaceBefore; + + CTRACE((tfp, "GridText: Change to style %s\n", GetHTStyleName(style))); + + blank_lines(text, ((after > before) ? after : before)); + + text->style = style; +} + +/* Append a character to the text object + * ------------------------------------- + */ +void HText_appendCharacter(HText *text, int ch) +{ + HTLine *line; + HTStyle *style; + int indent; + int actual; + +#ifdef DEBUG_APPCH +#ifdef CJK_EX + static unsigned char save_ch = 0; +#endif + + if (TRACE) { + char *special = NULL; /* make trace a little more readable */ + + switch (ch) { + case HT_NON_BREAK_SPACE: + special = "HT_NON_BREAK_SPACE"; + break; + case HT_EN_SPACE: + special = "HT_EN_SPACE"; + break; + case LY_UNDERLINE_START_CHAR: + special = "LY_UNDERLINE_START_CHAR"; + break; + case LY_UNDERLINE_END_CHAR: + special = "LY_UNDERLINE_END_CHAR"; + break; + case LY_BOLD_START_CHAR: + special = "LY_BOLD_START_CHAR"; + break; + case LY_BOLD_END_CHAR: + special = "LY_BOLD_END_CHAR"; + break; + case LY_SOFT_HYPHEN: + special = "LY_SOFT_HYPHEN"; + break; + case LY_SOFT_NEWLINE: + special = "LY_SOFT_NEWLINE"; + break; + default: + special = NULL; + break; + } + + if (special != NULL) { + CTRACE((tfp, "add(%s %d special char) %d/%d\n", special, ch, + HTisDocumentSource(), HTOutputFormat != WWW_SOURCE)); + } else { +#ifdef CJK_EX /* 1998/08/30 (Sun) 13:26:23 */ + if (save_ch == 0) { + if (IS_SJIS_HI1(ch) || IS_SJIS_HI2(ch)) { + save_ch = ch; + } else { + CTRACE((tfp, "add(%c) %d/%d\n", ch, + HTisDocumentSource(), HTOutputFormat != WWW_SOURCE)); + } + } else { + CTRACE((tfp, "add(%c%c) %d/%d\n", save_ch, ch, + HTisDocumentSource(), HTOutputFormat != WWW_SOURCE)); + save_ch = 0; + } +#else + if (UCH(ch) < 0x80) { + CTRACE((tfp, "add(%c) %d/%d\n", UCH(ch), + HTisDocumentSource(), HTOutputFormat != WWW_SOURCE)); + } else { + CTRACE((tfp, "add(%02x) %d/%d\n", UCH(ch), + HTisDocumentSource(), HTOutputFormat != WWW_SOURCE)); + } +#endif /* CJK_EX */ + } + } /* trace only */ +#endif /* DEBUG_APPCH */ + + /* + * Make sure we don't crash on NULLs. + */ + if (!text) + return; + + if (text->halted > 1) { + /* + * We should stop outputting more text, because low memory was + * detected. - kw + */ + if (text->halted == 2) { + /* + * But if we haven't done so yet, first append a warning. + * We should still have a few bytes left for that :). + * We temporarily reset test->halted to 0 for this, since + * this function will get called recursively. - kw + */ + text->halted = 0; + text->kanji_buf = '\0'; + HText_appendText(text, gettext(" *** MEMORY EXHAUSTED ***")); + } + text->halted = 3; + return; + } +#ifdef USE_TH_JP_AUTO_DETECT + if ((HTCJK == JAPANESE) && (text->detected_kcode != DET_MIXED) && + (text->specified_kcode != SJIS) && (text->specified_kcode != EUC)) { + unsigned char c; + eDetectedKCode save_d_kcode; + + c = UCH(ch); + save_d_kcode = text->detected_kcode; + switch (text->SJIS_status) { + case SJIS_state_has_bad_code: + break; + case SJIS_state_neutral: + if (IS_SJIS_HI1(c) || IS_SJIS_HI2(c)) { + text->SJIS_status = SJIS_state_in_kanji; + } else if ((c & 0x80) && !IS_SJIS_X0201KANA(c)) { + text->SJIS_status = SJIS_state_has_bad_code; + if (text->EUC_status == EUC_state_has_bad_code) + text->detected_kcode = DET_MIXED; + else + text->detected_kcode = DET_EUC; + } + break; + case SJIS_state_in_kanji: + if (IS_SJIS_LO(c)) { + text->SJIS_status = SJIS_state_neutral; + } else { + text->SJIS_status = SJIS_state_has_bad_code; + if (text->EUC_status == EUC_state_has_bad_code) + text->detected_kcode = DET_MIXED; + else + text->detected_kcode = DET_EUC; + } + break; + } + switch (text->EUC_status) { + case EUC_state_has_bad_code: + break; + case EUC_state_neutral: + if (IS_EUC_HI(c)) { + text->EUC_status = EUC_state_in_kanji; + } else if (c == 0x8e) { + text->EUC_status = EUC_state_in_kana; + } else if (c & 0x80) { + text->EUC_status = EUC_state_has_bad_code; + if (text->SJIS_status == SJIS_state_has_bad_code) + text->detected_kcode = DET_MIXED; + else + text->detected_kcode = DET_SJIS; + } + break; + case EUC_state_in_kanji: + if (IS_EUC_LOX(c)) { + text->EUC_status = EUC_state_neutral; + } else { + text->EUC_status = EUC_state_has_bad_code; + if (text->SJIS_status == SJIS_state_has_bad_code) + text->detected_kcode = DET_MIXED; + else + text->detected_kcode = DET_SJIS; + } + break; + case EUC_state_in_kana: + if ((0xA1 <= c) && (c <= 0xDF)) { + text->EUC_status = EUC_state_neutral; + } else { + text->EUC_status = EUC_state_has_bad_code; + if (text->SJIS_status == SJIS_state_has_bad_code) + text->detected_kcode = DET_MIXED; + else + text->detected_kcode = DET_SJIS; + } + break; + } + if (save_d_kcode != text->detected_kcode) { + switch (text->detected_kcode) { + case DET_SJIS: + CTRACE((tfp, + "TH_JP_AUTO_DETECT: This document's kcode seems SJIS.\n")); + break; + case DET_EUC: + CTRACE((tfp, + "TH_JP_AUTO_DETECT: This document's kcode seems EUC.\n")); + break; + case DET_MIXED: + CTRACE((tfp, + "TH_JP_AUTO_DETECT: This document's kcode seems mixed!\n")); + break; + default: + CTRACE((tfp, + "TH_JP_AUTO_DETECT: This document's kcode is unexpected!\n")); + break; + } + } + } +#endif /* USE_TH_JP_AUTO_DETECT */ + /* + * Make sure we don't hang on escape sequences. + */ + if (ch == CH_ESC && !IS_CJK_TTY) { /* decimal 27 S/390 -- gil -- 1504 */ + return; + } +#ifndef USE_SLANG + /* + * Block 8-bit chars not allowed by the current display character + * set if they are below what LYlowest_eightbit indicates. + * Slang used its own replacements, so for USE_SLANG blocking here + * is not necessary to protect terminals from those characters. + * They should have been filtered out or translated by an earlier + * processing stage anyway. - kw + */ +#ifndef EBCDIC /* S/390 -- gil -- 1514 */ + if (is8bits(ch) && !IS_CJK_TTY && + !text->T.transp && !text->T.output_utf8 && + UCH(ch) < LYlowest_eightbit[current_char_set]) { + return; + } +#endif /* EBCDIC */ +#endif /* !USE_SLANG */ + if (UCH(ch) == 155 && !IS_CJK_TTY) { /* octal 233 */ + if (!HTPassHighCtrlRaw && + !text->T.transp && !text->T.output_utf8 && + (155 < LYlowest_eightbit[current_char_set])) { + return; + } + } + + line = text->last_line; + style = text->style; + + indent = text->in_line_1 ? (int) style->indent1st : (int) style->leftIndent; + + if (IS_CJK_TTY) { + switch (text->state) { + case S_text: + if (ch == CH_ESC) { /* S/390 -- gil -- 1536 */ + /* + * Setting up for CJK escape sequence handling (based on + * Takuya ASADA's (asada@three-a.co.jp) CJK Lynx). -FM + */ + text->state = S_esc; + text->kanji_buf = '\0'; + return; + } + break; + + case S_esc: + /* + * Expecting '$'or '(' following CJK ESC. + */ + if (ch == '$') { + text->state = S_dollar; + return; + } else if (ch == '(') { + text->state = S_paren; + return; + } else { + text->state = S_text; + } + /* FALLTHRU */ + + case S_dollar: + /* + * Expecting '@', 'B', 'A' or '(' after CJK "ESC$". + */ + if (ch == '@' || ch == 'B' || ch == 'A') { + text->state = S_nonascii_text; + if (ch == '@' || ch == 'B') + text->kcode = JIS; + return; + } else if (ch == '(') { + text->state = S_dollar_paren; + return; + } else { + text->state = S_text; + } + break; + + case S_dollar_paren: + /* + * Expecting 'C' after CJK "ESC$(". + */ + if (ch == 'C') { + text->state = S_nonascii_text; + return; + } else { + text->state = S_text; + } + break; + + case S_paren: + /* + * Expecting 'B', 'J', 'T' or 'I' after CJK "ESC(". + */ + if (ch == 'B' || ch == 'J' || ch == 'T') { + /* + * Can split here. -FM + */ + text->permissible_split = text->last_line->size; + text->state = S_text; + return; + } else if (ch == 'I') { + text->state = S_jisx0201_text; + /* + * Can split here. -FM + */ + text->permissible_split = text->last_line->size; + text->kcode = JIS; + return; + } else { + text->state = S_text; + } + break; + + case S_nonascii_text: + /* + * Expecting CJK ESC after non-ASCII text. + */ + if (ch == CH_ESC) { /* S/390 -- gil -- 1553 */ + text->state = S_esc; + text->kanji_buf = '\0'; + if (HTCJK == JAPANESE) { + text->kcode = NOKANJI; + } + return; + } else if (UCH(ch) < 32) { + text->state = S_text; + text->kanji_buf = '\0'; + if (HTCJK == JAPANESE) { + text->kcode = NOKANJI; + } + } else { + ch |= 0200; + } + break; + + /* + * JIS X0201 Kana in JIS support. - by ASATAKU + */ + case S_jisx0201_text: + if (ch == CH_ESC) { /* S/390 -- gil -- 1570 */ + text->state = S_esc; + text->kanji_buf = '\0'; + text->kcode = NOKANJI; + return; + } else { + text->kanji_buf = '\216'; + ch |= 0200; + } + break; + } /* end switch */ + + if (!text->kanji_buf) { + if ((ch & 0200) != 0) { + /* + * JIS X0201 Kana in SJIS support. - by ASATAKU + */ + if ((text->kcode != JIS) +#ifdef USE_TH_JP_AUTO_DETECT + && (text->specified_kcode != EUC) + && (text->detected_kcode != DET_EUC) +#endif + && ( +#ifdef KANJI_CODE_OVERRIDE + (last_kcode == SJIS) || + ((last_kcode == NOKANJI) && +#endif + ((text->kcode == SJIS) || +#ifdef USE_TH_JP_AUTO_DETECT + ((text->detected_kcode == DET_SJIS) && + (text->specified_kcode == NOKANJI)) || +#endif + ((text->kcode == NOKANJI) && + (text->specified_kcode == SJIS))) +#ifdef KANJI_CODE_OVERRIDE + ) +#endif + ) && + (UCH(ch) >= 0xA1) && + (UCH(ch) <= 0xDF)) { + if (conv_jisx0201kana) { + unsigned char c = UCH(ch); + unsigned char kb = UCH(text->kanji_buf); + + JISx0201TO0208_SJIS(c, + (unsigned char *) &kb, + (unsigned char *) &c); + ch = (char) c; + text->kanji_buf = kb; + } + /* 1998/01/19 (Mon) 09:06:15 */ + text->permissible_split = (int) text->last_line->size; + } else { + text->kanji_buf = ch; + /* + * Can split here. -FM + */ + text->permissible_split = text->last_line->size; + return; + } + } + } else { + goto check_WrapSource; + } + } else if (ch == CH_ESC) { /* S/390 -- gil -- 1587 */ + return; + } +#ifdef CJK_EX /* MOJI-BAKE Fix! 1997/10/12 -- 10/31 (Fri) 00:22:57 - JH7AYN */ + if (IS_CJK_TTY && /* added condition - kw */ + (ch == LY_BOLD_START_CHAR || ch == LY_BOLD_END_CHAR)) { + text->permissible_split = (int) line->size; /* Can split here */ + if (HTCJK == JAPANESE) + text->kcode = NOKANJI; + } +#endif + + if (IsSpecialAttrChar(ch) && ch != LY_SOFT_NEWLINE) { +#if !defined(USE_COLOR_STYLE) || !defined(NO_DUMP_WITH_BACKSPACES) + if (line->size >= (MAX_LINE - 1)) { + return; + } +#if defined(USE_COLOR_STYLE) && !defined(NO_DUMP_WITH_BACKSPACES) + if (with_backspaces && !IS_CJK_TTY && !text->T.output_utf8) { +#endif + if (ch == LY_UNDERLINE_START_CHAR) { + line->data[line->size++] = LY_UNDERLINE_START_CHAR; + line->data[line->size] = '\0'; + underline_on = TRUE; + if (!(dump_output_immediately && use_underscore)) + ctrl_chars_on_this_line++; + return; + } else if (ch == LY_UNDERLINE_END_CHAR) { + line->data[line->size++] = LY_UNDERLINE_END_CHAR; + line->data[line->size] = '\0'; + underline_on = FALSE; + if (!(dump_output_immediately && use_underscore)) + ctrl_chars_on_this_line++; + return; + } else if (ch == LY_BOLD_START_CHAR) { + line->data[line->size++] = LY_BOLD_START_CHAR; + line->data[line->size] = '\0'; + bold_on = TRUE; + ctrl_chars_on_this_line++; + return; + } else if (ch == LY_BOLD_END_CHAR) { + line->data[line->size++] = LY_BOLD_END_CHAR; + line->data[line->size] = '\0'; + bold_on = FALSE; + ctrl_chars_on_this_line++; + return; + } else if (ch == LY_SOFT_HYPHEN) { + int i; + + /* + * Ignore the soft hyphen if it is the first character + * on the line, or if it is preceded by a space or + * hyphen. -FM + */ + if (line->size < 1 || text->permissible_split >= line->size) { + return; + } + + for (i = (int) (text->permissible_split + 1); + line->data[i]; + i++) { + if (!IsSpecialAttrChar(UCH(line->data[i])) && + !isspace(UCH(line->data[i])) && + UCH(line->data[i]) != '-' && + UCH(line->data[i]) != HT_NON_BREAK_SPACE && + UCH(line->data[i]) != HT_EN_SPACE) { + break; + } + } + if (line->data[i] == '\0') { + return; + } + } +#if defined(USE_COLOR_STYLE) && !defined(NO_DUMP_WITH_BACKSPACES) + } else { + /* if (with_backspaces && HTCJK==HTNOCJK && !text->T.output_utf8) */ + return; + } +#endif + +#else + return; +#endif + } else if (ch == LY_SOFT_NEWLINE) { + if (line->size < MAX_LINE) { + line->data[line->size++] = LY_SOFT_NEWLINE; + line->data[line->size] = '\0'; + } + return; + } + + if (text->T.output_utf8) { + /* + * Some extra checks for UTF-8 output here to make sure + * memory is not overrun. For a non-first char, append + * to the line here and return. - kw + */ + if (IS_UTF_EXTRA(ch)) { + if ((line->size > (MAX_LINE - 1)) + || (indent + (int) (line->offset + line->size) + + UTFXTRA_ON_THIS_LINE + - ctrl_chars_on_this_line + + ((line->size > 0) && + (int) (line->data[line->size - 1] == + LY_SOFT_HYPHEN ? + 1 : 0)) >= LYcols_cu(text)) + ) { + if (!text->permissible_split || text->source) { + text->permissible_split = line->size; + while (text->permissible_split > 0 && + IS_UTF_EXTRA(line->data[text->permissible_split - 1])) + text->permissible_split--; + if (text->permissible_split && + (line->data[text->permissible_split - 1] & 0x80)) + text->permissible_split--; + if (text->permissible_split == line->size) + text->permissible_split = 0; + } + split_line(text, text->permissible_split); + line = text->last_line; + if (text->source && line->size - ctrl_chars_on_this_line + + UTFXTRA_ON_THIS_LINE == 0) + HText_appendCharacter(text, LY_SOFT_NEWLINE); + } + if (line->size < MAX_LINE) { + line->data[line->size++] = (char) ch; + line->data[line->size] = '\0'; + utfxtra_on_this_line++; + ctrl_chars_on_this_line++; + } +#ifdef EXP_WCWIDTH_SUPPORT + /* update utfxtracells_on_this_line on last byte of UTF-8 sequence */ + { + /* find start position of UTF-8 sequence */ + int utff = line->size - 2; + int utf_xlen; + + while (utff > 0 && IS_UTF_EXTRA(line->data[utff])) + utff--; + utf_xlen = UTF_XLEN(line->data[utff]); + + if (line->size - utff == utf_xlen + 1) { /* have last byte */ + utfxtracells_on_this_line += utfextracells(&(line->data[utff])); + permit_split_after_CJchar(text, &(line->data[utff]), line->size); + } + } +#endif + return; + } else if (ch & 0x80) { /* a first char of UTF-8 sequence - kw */ + if ((line->size > (MAX_LINE - 7))) { + if (!text->permissible_split || text->source) { + text->permissible_split = line->size; + while (text->permissible_split > 0 && + (line->data[text->permissible_split - 1] & 0xc0) + == 0x80) { + text->permissible_split--; + } + if (text->permissible_split == line->size) + text->permissible_split = 0; + } + split_line(text, text->permissible_split); + line = text->last_line; + if (text->source && line->size - ctrl_chars_on_this_line + + UTFXTRA_ON_THIS_LINE == 0) + HText_appendCharacter(text, LY_SOFT_NEWLINE); + } + } + } + + /* + * New Line. + */ + if (ch == '\n') { + new_line(text); + text->in_line_1 = YES; /* First line of new paragraph */ + /* + * There are some pages written in + * different kanji codes. - TA & kw + */ + if (HTCJK == JAPANESE) + text->kcode = NOKANJI; + return; + } + + /* + * Convert EN_SPACE to a space here so that it doesn't get collapsed. + */ + if (ch == HT_EN_SPACE) + ch = ' '; + +#ifdef SH_EX /* 1997/11/01 (Sat) 12:08:54 */ + if (ch == 0x0b) { /* ^K ??? */ + ch = '\r'; + } + if (ch == 0x1a) { /* ^Z ??? */ + ch = '\r'; + } +#endif + + /* + * I'm going to cheat here in a BIG way. Since I know that all + * \r's will be trapped by HTML_put_character I'm going to use + * \r to mean go down a line but don't start a new paragraph. + * i.e., use the second line indenting. + */ + if (ch == '\r') { + new_line(text); + text->in_line_1 = NO; + /* + * There are some pages written in + * different kanji codes. - TA & kw + */ + if (HTCJK == JAPANESE) + text->kcode = NOKANJI; + return; + } + + /* + * Tabs. + */ + if (ch == '\t') { + const HTTabStop *Tab; + int target, target_cu; /* Where to tab to */ + int here, here_cu; /* in _cu we try to guess what curses thinks */ + + if (line->size > 0 && line->data[line->size - 1] == LY_SOFT_HYPHEN) { + /* + * A tab shouldn't follow a soft hyphen, so + * if one does, we'll dump the soft hyphen. -FM + */ + line->data[--line->size] = '\0'; + ctrl_chars_on_this_line--; + } + here = ((int) (line->size + line->offset) + indent) + - ctrl_chars_on_this_line; /* Consider special chars GAB */ + here_cu = here + UTFXTRA_ON_THIS_LINE; + if (style->tabs) { /* Use tab table */ + for (Tab = style->tabs; + Tab->position <= here; + Tab++) { + if (!Tab->position) { + new_line(text); + return; + } + } + target = Tab->position; + } else if (text->in_line_1) { /* Use 2nd indent */ + if (here >= (int) style->leftIndent) { + new_line(text); /* wrap */ + return; + } else { + target = (int) style->leftIndent; + } + } else { /* Default tabs align with left indent mod 8 */ +#ifdef DEFAULT_TABS_8 + target = (((int) line->offset + (int) line->size + 8) & (-8)) + + (int) style->leftIndent; +#else + new_line(text); + return; +#endif + } + + if (target >= here) + target_cu = target; + else + target_cu = target + (here_cu - here); + + if (target > WRAP_COLS(text) - (int) style->rightIndent && + HTOutputFormat != WWW_SOURCE) { + new_line(text); + } else { + /* + * Can split here. -FM + */ + text->permissible_split = line->size; + if (target_cu > WRAP_COLS(text)) + target -= target_cu - WRAP_COLS(text); + if (line->size == 0) { + line->offset = (unsigned short) (line->offset + (target - here)); + } else { + for (; here < target; here++) { + /* Put character into line */ + if (line->size >= MAX_LINE) + break; + line->data[line->size++] = ' '; + line->data[line->size] = '\0'; + } + } + } + return; + } + /* if tab */ + check_WrapSource: + if ((text->source || dont_wrap_pre) && text == HTMainText) { + /* + * If we're displaying document source, wrap long lines to keep all of + * the source visible. + */ + int target = (int) (line->offset + line->size) - ctrl_chars_on_this_line; + int target_cu = target + UTFXTRA_ON_THIS_LINE; + + if (target >= WRAP_COLS(text) - style->rightIndent - + ((IS_CJK_TTY && text->kanji_buf) ? 1 : 0) || + (text->T.output_utf8 && + target_cu + UTF_XLEN(ch) >= LYcols_cu(text))) { + int saved_kanji_buf; + eGridState saved_state; + BOOL add_blank = (dont_wrap_pre + && line->size + && (line->data[line->size - 1] == ' ')); + + new_line(text); + line = text->last_line; + + saved_kanji_buf = text->kanji_buf; + saved_state = text->state; + text->kanji_buf = '\0'; + text->state = S_text; + HText_appendCharacter(text, LY_SOFT_NEWLINE); + if (add_blank) + HText_appendCharacter(text, ' '); + text->kanji_buf = saved_kanji_buf; + text->state = saved_state; + } + } + + if (ch == ' ') { + /* + * Can split here. -FM + */ + text->permissible_split = text->last_line->size; + /* + * There are some pages written in + * different kanji codes. - TA + */ + if (HTCJK == JAPANESE) + text->kcode = NOKANJI; + } + + /* + * Check for end of line. + * + * Notes: + * 1) text->permissible_split is nonzero if we found a place to split the + * line. If there is no such place, we still will wrap at the display + * limits (the comparison against LYcols_cu). Furthermore, if the + * curses-pads feature is active, we will ignore the first comparison + * (against WRAP_COLS) to allow wide preformatted text to be displayed + * without wrapping. + * 2) ctrl_chars_on_this_line are nonprintable bytes used for formatting. + */ + actual = ((indent + (int) line->offset + (int) line->size) + + ((line->size > 0) && + (int) (line->data[line->size - 1] == LY_SOFT_HYPHEN ? 1 : 0)) + - ctrl_chars_on_this_line); + + if (( +#if !defined(USE_SLANG) && !defined(PDCURSES) + (text->permissible_split +#ifdef USE_CURSES_PADS + || !LYwideLines +#endif + ) && +#endif + (actual + + (int) style->rightIndent + + ((IS_CJK_TTY && text->kanji_buf) ? 1 : 0) +#ifdef EXP_WCWIDTH_SUPPORT + + utfxtracells_on_this_line +#endif + ) >= WRAP_COLS(text)) + || (text->T.output_utf8 + && ((actual + + UTFXTRA_ON_THIS_LINE + + UTF_XLEN(ch) + ) > (LYcols_cu(text) - 1)))) { + + if (style->wordWrap && HTOutputFormat != WWW_SOURCE) { +#ifdef USE_JUSTIFY_ELTS + if (REALLY_CAN_JUSTIFY(text)) + this_line_was_split = TRUE; +#endif + split_line(text, text->permissible_split); + if (ch == ' ') { + return; /* Ignore space causing split */ + } + + } else if (HTOutputFormat == WWW_SOURCE) { + /* + * For source output we don't want to wrap this stuff + * unless absolutely necessary. - LJM + * ! + * If we don't wrap here we might get a segmentation fault. + * but let's see what happens + */ + if ((int) line->size >= (int) (MAX_LINE - 1)) { + new_line(text); /* try not to linewrap */ + } + } else { + /* + * For normal stuff like pre let's go ahead and + * wrap so the user can see all of the text. + */ + if ((dump_output_immediately || (crawl && traversal)) + && dont_wrap_pre) { + if ((int) line->size >= (int) (MAX_LINE - 1)) { + new_line(text); + } + } else { + new_line(text); + } + } + } else if ((int) line->size >= (int) (MAX_LINE - 1)) { + /* + * Never overrun memory if DISPLAY_COLS is set to a large value - KW + */ + new_line(text); + } + + /* + * Insert normal characters. + */ + if (ch == HT_NON_BREAK_SPACE +#ifdef USE_JUSTIFY_ELTS + && !REALLY_CAN_JUSTIFY(text) +#endif + ) + ch = ' '; +#ifdef USE_JUSTIFY_ELTS + else + have_raw_nbsps = TRUE; +#endif + + /* we leave raw HT_NON_BREAK_SPACE otherwise (we'll substitute it later) */ + + if (ch & 0x80) + text->have_8bit_chars = YES; + + /* + * Kanji character handling. + */ + { + HTFont font = style->font; + unsigned char hi, lo, tmp[2]; + + line = text->last_line; /* May have changed */ + + if (line->size >= MAX_LINE) { + ; + } else if (IS_CJK_TTY && text->kanji_buf) { + hi = UCH(text->kanji_buf); + lo = UCH(ch); + + if (HTCJK == JAPANESE) { + if (text->kcode != JIS) { + if (IS_SJIS_2BYTE(hi, lo)) { + if (IS_EUC(hi, lo)) { +#ifdef KANJI_CODE_OVERRIDE + if (last_kcode != NOKANJI) + text->kcode = last_kcode; + else +#endif + if (text->specified_kcode != NOKANJI) + text->kcode = text->specified_kcode; +#ifdef USE_TH_JP_AUTO_DETECT + else if (text->detected_kcode == DET_EUC) + text->kcode = EUC; + else if (text->detected_kcode == DET_SJIS) + text->kcode = SJIS; +#endif + else if (IS_EUC_X0201KANA(hi, lo) && + (text->kcode != EUC)) + text->kcode = SJIS; + } else + text->kcode = SJIS; + } else if (IS_EUC(hi, lo)) + text->kcode = EUC; + else + text->kcode = NOKANJI; + } + + switch (kanji_code) { + case EUC: + if (text->kcode == SJIS) { + SJIS_TO_EUC1(hi, lo, tmp); + line->data[line->size++] = (char) tmp[0]; + line->data[line->size++] = (char) tmp[1]; + } else if (IS_EUC(hi, lo)) { + if (conv_jisx0201kana) { + JISx0201TO0208_EUC(hi, lo, &hi, &lo); + } + line->data[line->size++] = (char) hi; + line->data[line->size++] = (char) lo; + } else { + CTRACE((tfp, + "This character (%X:%X) doesn't seem Japanese\n", + hi, lo)); + line->data[line->size++] = '='; + line->data[line->size++] = '='; + } + break; + + case SJIS: + if ((text->kcode == EUC) || (text->kcode == JIS)) { + if (!conv_jisx0201kana && IS_EUC_X0201KANA(hi, lo)) + line->data[line->size++] = (char) lo; + else { + EUC_TO_SJIS1(hi, lo, tmp); + line->data[line->size++] = (char) tmp[0]; + line->data[line->size++] = (char) tmp[1]; + } + } else if (IS_SJIS_2BYTE(hi, lo)) { + line->data[line->size++] = (char) hi; + line->data[line->size++] = (char) lo; + } else { + line->data[line->size++] = '='; + line->data[line->size++] = '='; + CTRACE((tfp, + "This character (%X:%X) doesn't seem Japanese\n", + hi, lo)); + } + break; + + default: + break; + } + } else { + line->data[line->size++] = (char) hi; + line->data[line->size++] = (char) lo; + } + text->kanji_buf = 0; + } else if (!conv_jisx0201kana + && (HTCJK == JAPANESE) + && IS_SJIS_X0201KANA(UCH((ch))) && + (kanji_code == EUC)) { + line->data[line->size++] = (char) UCH(0x8e); + line->data[line->size++] = (char) ch; + } else if (IS_CJK_TTY) { + line->data[line->size++] = (char) ((kanji_code != NOKANJI) ? + ch : + (font & HT_CAPITALS) ? + TOUPPER(ch) : ch); + } else { + line->data[line->size++] = /* Put character into line */ + (char) (font & HT_CAPITALS ? TOUPPER(ch) : ch); + } + line->data[line->size] = '\0'; + if (font & HT_DOUBLE) /* Do again if doubled */ + HText_appendCharacter(text, HT_NON_BREAK_SPACE); + /* NOT a permissible split */ + + if (ch == LY_SOFT_HYPHEN) { + ctrl_chars_on_this_line++; + /* + * Can split here. -FM + */ + text->permissible_split = text->last_line->size; + } + if (ch == LY_SOFT_NEWLINE) { + ctrl_chars_on_this_line++; + } + } + return; +} + +#ifdef USE_COLOR_STYLE +/* Insert a style change into the current line + * ------------------------------------------- + */ +void _internal_HTC(HText *text, int style, int dir) +{ + HTLine *line; + + /* can't change style if we have no text to change style with */ + if (text != 0) { + + line = text->last_line; + + if (line->numstyles > 0 && dir == 0 && + line->styles[line->numstyles - 1].sc_direction && + line->styles[line->numstyles - 1].sc_style == (unsigned) style && + (int) line->styles[line->numstyles - 1].sc_horizpos + == (int) line->size - ctrl_chars_on_this_line) { + /* + * If this is an OFF change directly preceded by an + * ON for the same style, just remove the previous one. - kw + */ + line->numstyles--; + } else if (line->numstyles < MAX_STYLES_ON_LINE) { + line->styles[line->numstyles].sc_horizpos = CAST_POS(line->size); + /* + * Special chars for bold and underlining usually don't + * occur with color style, but soft hyphen can. + * And in UTF-8 display mode all non-initial bytes are + * counted as ctrl_chars. - kw + */ + if ((int) line->styles[line->numstyles].sc_horizpos >= ctrl_chars_on_this_line) { + line->styles[line->numstyles].sc_horizpos = + CAST_POS(line->styles[line->numstyles].sc_horizpos + - ctrl_chars_on_this_line); + } + line->styles[line->numstyles].sc_style = (unsigned short) style; + line->styles[line->numstyles].sc_direction = CAST_DIR(dir); + CTRACE_STYLE((tfp, "internal_HTC %d:style[%d] %d (dir=%d)\n", + line->size, + line->numstyles, + style, + dir)); + line->numstyles++; + } + } +} +#endif + +/* Set LastChar element in the text object. + * ---------------------------------------- + */ +void HText_setLastChar(HText *text, int ch) +{ + if (!text) + return; + +#ifdef EXP_JAPANESE_SPACES + if (IS_UTF_EXTRA(ch) && IS_UTF_FIRST(text->LastChars[0])) { + int i; + + for (i = 1; + text->LastChars[i] != '\0' && i < sizeof(text->LastChars) - 1; + i++) { + ; + } + text->LastChars[i] = (char) ch; + text->LastChars[i + 1] = '\0'; + return; + } + memset(text->LastChars, 0, sizeof(text->LastChars)); + text->LastChars[0] = (char) ch; +#else + text->LastChar = (char) ch; +#endif +} + +/* Get LastChar element in the text object. + * ---------------------------------------- + */ +char HText_getLastChar(HText *text) +{ + if (!text) + return ('\0'); + +#ifdef EXP_JAPANESE_SPACES + if (IS_UTF_FIRST(text->LastChars[0])) { + int i; + + for (i = 1; + text->LastChars[i] != '\0' && i < sizeof(text->LastChars); + i++) { + ; + } + return ((char) text->LastChars[i - 1]); + } + return ((char) text->LastChars[0]); +#else + return ((char) text->LastChar); +#endif +} + +#ifdef EXP_JAPANESE_SPACES +BOOL HText_checkLastChar_needSpaceOnJoinLines(HText *text) +{ + if (!text) + return YES; + + if (IS_UTF_FIRST(text->LastChars[0]) && isUTF8CJChar(text->LastChars)) + return NO; + if ((HTCJK == CHINESE || HTCJK == JAPANESE) && is8bits(text->LastChars[0])) { + /* TODO: support 2nd byte of some SJIS kanji (!is8bits && IS_SJIS_LO) */ + return NO; + } + if (text->LastChars[0] != ' ') + return YES; + return NO; +} +#endif + +/* Simple table handling - private + * ------------------------------- + */ + +/* + * HText_insertBlanksInStblLines fixes up table lines when simple table + * processing is closed, by calling insert_blanks_in_line for lines + * that need fixup. Also recalculates alignment for those lines, + * does additional updating of anchor positions, and makes sure the + * display of the lines on screen will be updated after partial display + * upon return to mainloop. - kw + */ +static int HText_insertBlanksInStblLines(HText *me, int ncols) +{ + HTLine *line; + HTLine *mod_line, *first_line = NULL; + int *oldpos; + int *newpos; + int ninserts, lineno; + int last_lineno, first_lineno_pass2; + +#ifdef EXP_NESTED_TABLES + int last_nonempty = -1; +#endif + int lines_changed = 0; + int max_width = 0, indent, spare, table_offset; + HTStyle *style; + short alignment; + int i = 0; + + lineno = Stbl_getStartLine(me->stbl); + if (lineno < 0 || lineno > me->Lines) + return -1; + /* + * oldpos, newpos: allocate space for two int arrays. + */ + oldpos = typecallocn(int, 2 * (size_t)ncols); + if (!oldpos) + return -1; + else + newpos = oldpos + ncols; + for (line = FirstHTLine(me); i < lineno; line = line->next, i++) { + if (!line) { + free(oldpos); + return -1; + } + } + first_lineno_pass2 = last_lineno = me->Lines; + for (; line && lineno <= last_lineno; line = line->next, lineno++) { + ninserts = Stbl_getFixupPositions(me->stbl, lineno, oldpos, newpos); + if (ninserts < 0) + continue; + if (!first_line) { + first_line = line; + first_lineno_pass2 = lineno; + if (TRACE) { + int ip; + + CTRACE((tfp, "line %d first to adjust -- newpos:", lineno)); + for (ip = 0; ip < ncols; ip++) + CTRACE((tfp, " %d", newpos[ip])); + CTRACE((tfp, "\n")); + } + } + if (line == me->last_line) { + if (line->size == 0 || HText_TrueEmptyLine(line, me, FALSE)) + continue; + /* + * Last ditch effort to end the table with a line break, + * if HTML_end_element didn't do it. - kw + */ + if (first_line == line) /* obscure: all table on last line... */ + first_line = NULL; + new_line(me); + line = me->last_line->prev; + if (first_line == NULL) + first_line = line; + } + if (ninserts == 0) { + /* Do it also for no positions (but not error) */ + int width = HText_TrueLineSize(line, me, FALSE); + + if (width > max_width) + max_width = width; +#ifdef EXP_NESTED_TABLES + if (nested_tables) { + if (width && last_nonempty < lineno) + last_nonempty = lineno; + } +#endif + CTRACE((tfp, "line %d true/max width:%d/%d oldpos: NONE\n", + lineno, width, max_width)); + continue; + } + mod_line = insert_blanks_in_line(line, lineno, me, + &me->last_anchor_before_stbl /*updates++ */ , + ninserts, oldpos, newpos); + if (mod_line) { + if (line == me->last_line) { + me->last_line = mod_line; + } + line->prev->next = mod_line; + line->next->prev = mod_line; + lines_changed++; + if (line == first_line) + first_line = mod_line; + freeHTLine(me, line); + line = mod_line; +#ifdef DISP_PARTIAL + /* + * Make sure modified lines get fully re-displayed after + * loading with partial display is done. + */ + if (me->first_lineno_last_disp_partial >= 0) { + if (me->first_lineno_last_disp_partial >= lineno) { + ResetPartialLinenos(me); + } else if (me->last_lineno_last_disp_partial >= lineno) { + me->last_lineno_last_disp_partial = lineno - 1; + } + } +#endif + } { + int width = HText_TrueLineSize(line, me, FALSE); + + if (width > max_width) + max_width = width; +#ifdef EXP_NESTED_TABLES + if (nested_tables) { + if (width && last_nonempty < lineno) + last_nonempty = lineno; + } +#endif + if (TRACE) { + int ip; + + CTRACE((tfp, "line %d true/max width:%d/%d oldpos:", + lineno, width, max_width)); + for (ip = 0; ip < ninserts; ip++) + CTRACE((tfp, " %d", oldpos[ip])); + CTRACE((tfp, "\n")); + } + } + } + /* + * Line offsets have been set based on the paragraph style, and + * have already been updated for centering or right-alignment + * for each line in split_line. Here we want to undo all that, and + * align the table as a whole (i.e. all lines for which + * Stbl_getFixupPositions returned >= 0). All those lines have to + * get the same offset, for the simple table formatting mechanism + * to make sense, and that may not actually be the case at this point. + * + * What indentation and alignment do we want for the table as + * a whole? Let's take most style properties from me->style. + * With some luck, it is the appropriate style for the element + * enclosing the TABLE. But let's take alignment from the attribute + * of the TABLE itself instead, if it was specified. + * + * Note that this logic assumes that all lines have been finished + * by split_line. The order of calls made by HTML_end_element for + * HTML_TABLE should take care of this. + */ + style = me->style; + alignment = Stbl_getAlignment(me->stbl); + if (alignment == HT_ALIGN_NONE) + alignment = style->alignment; + indent = style->leftIndent; + /* Calculate spare character positions */ + spare = WRAP_COLS(me) - + (int) style->rightIndent - indent - max_width; + if (spare < 0 && (int) style->rightIndent + spare >= 0) { + /* + * Not enough room! But we can fit if we ignore right indentation, + * so let's do that. + */ + spare = 0; + } else if (spare < 0) { + spare += style->rightIndent; /* ignore right indent, but need more */ + } + if (spare < 0 && indent + spare >= 0) { + /* + * Still not enough room. But we can move to the left. + */ + indent += spare; + spare = 0; + } else if (spare < 0) { + /* + * Still not enough. Something went wrong. Try the best we + * can do. + */ + CTRACE((tfp, + "BUG: insertBlanks: resulting table too wide by %d positions!\n", + -spare)); + indent = spare = 0; + } + /* + * Align left, right or center. + */ + switch (alignment) { + case HT_CENTER: + table_offset = indent + spare / 2; + break; + case HT_RIGHT: + table_offset = indent + spare; + break; + case HT_LEFT: + case HT_JUSTIFY: + default: + table_offset = indent; + break; + } /* switch */ + + CTRACE((tfp, "changing offsets")); + for (line = first_line, lineno = first_lineno_pass2; + line && lineno <= last_lineno && line != me->last_line; + line = line->next, lineno++) { + ninserts = Stbl_getFixupPositions(me->stbl, lineno, oldpos, newpos); + if (ninserts >= 0 && (int) line->offset != table_offset) { +#ifdef DISP_PARTIAL + /* As above make sure modified lines get fully re-displayed */ + if (me->first_lineno_last_disp_partial >= 0) { + if (me->first_lineno_last_disp_partial >= lineno) { + ResetPartialLinenos(me); + } else if (me->last_lineno_last_disp_partial >= lineno) { + me->last_lineno_last_disp_partial = lineno - 1; + } + } +#endif + CTRACE((tfp, " %d:%d", lineno, table_offset - line->offset)); + line->offset = (unsigned short) (table_offset > 0 + ? table_offset + : 0); + } + } +#ifdef EXP_NESTED_TABLES + if (nested_tables) { + if (max_width) + Stbl_update_enclosing(me->stbl, max_width, last_nonempty); + } +#endif + CTRACE((tfp, " %d:done\n", lineno)); + free(oldpos); + return lines_changed; +} + +/* Simple table handling - public functions + * ---------------------------------------- + */ + +/* Cancel simple table handling +*/ +void HText_cancelStbl(HText *me) +{ + if (!me || !me->stbl) { + CTRACE((tfp, "cancelStbl: ignored.\n")); + return; + } + CTRACE((tfp, "cancelStbl: ok, will do.\n")); +#ifdef EXP_NESTED_TABLES + if (nested_tables) { + STable_info *stbl = me->stbl; + + while (stbl) { + STable_info *enclosing = Stbl_get_enclosing(stbl); + + Stbl_free(stbl); + stbl = enclosing; + } + } else +#endif + Stbl_free(me->stbl); + me->stbl = NULL; +} + +/* Start simple table handling +*/ +void HText_startStblTABLE(HText *me, int alignment) +{ + if (me) { +#ifdef EXP_NESTED_TABLES + STable_info *current = me->stbl; +#endif + +#ifdef EXP_NESTED_TABLES + if (nested_tables) { + if (current) + new_line(me); + } else +#endif + { + if (me->stbl) { + HText_cancelStbl(me); /* auto cancel previously open table */ + } + } + + me->stbl = Stbl_startTABLE(alignment); + if (me->stbl) { + CTRACE((tfp, "startStblTABLE: started.\n")); +#ifdef EXP_NESTED_TABLES + if (nested_tables) { + Stbl_set_enclosing(me->stbl, current, me->last_anchor_before_stbl); + } +#endif + me->last_anchor_before_stbl = me->last_anchor; + } else { + CTRACE((tfp, "startStblTABLE: failed.\n")); + } + } +} + +#ifdef EXP_NESTED_TABLES +static void free_enclosed_stbl(HText *me) +{ + if (me != NULL && me->enclosed_stbl != NULL) { + HTList *list = me->enclosed_stbl; + STable_info *stbl; + + while (NULL != (stbl = (STable_info *) HTList_nextObject(list))) { + CTRACE((tfp, "endStblTABLE: finally free %p\n", (void *) me->stbl)); + Stbl_free(stbl); + } + HTList_delete(me->enclosed_stbl); + me->enclosed_stbl = NULL; + } +} + +#else +#define free_enclosed_stbl(me) /* nothing */ +#endif + +/* Finish simple table handling + * Return TRUE if the table is nested inside another table. + */ +BOOLEAN HText_endStblTABLE(HText *me) +{ + int ncols, lines_changed = 0; + STable_info *enclosing = NULL; + + if (!me || !me->stbl) { + CTRACE((tfp, "endStblTABLE: ignored.\n")); + free_enclosed_stbl(me); + return FALSE; + } + CTRACE((tfp, "endStblTABLE: ok, will try.\n")); + + ncols = Stbl_finishTABLE(me->stbl); + CTRACE((tfp, "endStblTABLE: ncols = %d.\n", ncols)); + + if (ncols > 0) { + lines_changed = HText_insertBlanksInStblLines(me, ncols); + CTRACE((tfp, "endStblTABLE: changed %d lines, done.\n", lines_changed)); +#ifdef DISP_PARTIAL + /* allow HTDisplayPartial() to redisplay the changed lines. + * There is no harm if we got several stbl in the document, hope so. + */ + NumOfLines_partial -= lines_changed; /* fake */ +#endif /* DISP_PARTIAL */ + } +#ifdef EXP_NESTED_TABLES + if (nested_tables) { + enclosing = Stbl_get_enclosing(me->stbl); + me->last_anchor_before_stbl = Stbl_get_last_anchor_before(me->stbl); + if (enclosing == NULL) { + Stbl_free(me->stbl); + free_enclosed_stbl(me); + } else { + if (me->enclosed_stbl == NULL) + me->enclosed_stbl = HTList_new(); + HTList_addObject(me->enclosed_stbl, me->stbl); + CTRACE((tfp, "endStblTABLE: postpone free %p\n", (void *) me->stbl)); + } + me->stbl = enclosing; + } else { + Stbl_free(me->stbl); + me->stbl = NULL; + } +#else + Stbl_free(me->stbl); + me->stbl = NULL; +#endif + + CTRACE((tfp, "endStblTABLE: have%s enclosing table (%p)\n", + enclosing == 0 ? " NO" : "", (void *) enclosing)); + + return (BOOLEAN) (enclosing != 0); +} + +/* Start simple table row +*/ +void HText_startStblTR(HText *me, int alignment) +{ + if (!me || !me->stbl) + return; + if (Stbl_addRowToTable(me->stbl, alignment, me->Lines) < 0) { + HText_cancelStbl(me); /* give up */ + } +} + +/* Finish simple table row +*/ +void HText_endStblTR(HText *me) +{ + if (!me || !me->stbl) + return; + /* should this do something?? */ +} + +/* Start simple table cell +*/ +void HText_startStblTD(HText *me, int colspan, + int rowspan, + int alignment, + int isheader) +{ + if (!me || !me->stbl) + return; + if (colspan < 0) + colspan = 1; + if (colspan > TRST_MAXCOLSPAN) { + CTRACE((tfp, "*** COLSPAN=%d is too large, ignored!\n", colspan)); + colspan = 1; + } + if (rowspan > TRST_MAXROWSPAN) { + CTRACE((tfp, "*** ROWSPAN=%d is too large, ignored!\n", rowspan)); + rowspan = 1; + } + if (Stbl_addCellToTable(me->stbl, colspan, rowspan, alignment, isheader, + me->Lines, + HText_LastLineOffset(me), + HText_LastLineSize(me, FALSE)) < 0) { + HText_cancelStbl(me); /* give up */ + } +} + +/* Finish simple table cell +*/ +void HText_endStblTD(HText *me) +{ + if (!me || !me->stbl) + return; + if (Stbl_finishCellInTable(me->stbl, TRST_ENDCELL_ENDTD, + me->Lines, + HText_LastLineOffset(me), + HText_LastLineSize(me, FALSE)) < 0) { + HText_cancelStbl(me); /* give up */ + } +} + +/* Remember COL info / Start a COLGROUP and remember info +*/ +void HText_startStblCOL(HText *me, int span, + int alignment, + int isgroup) +{ + if (!me || !me->stbl) + return; + if (span <= 0) + span = 1; + if (span > TRST_MAXCOLSPAN) { + CTRACE((tfp, "*** SPAN=%d is too large, ignored!\n", span)); + span = 1; + } + if (Stbl_addColInfo(me->stbl, span, alignment, isgroup) < 0) { + HText_cancelStbl(me); /* give up */ + } +} + +/* Finish a COLGROUP +*/ +void HText_endStblCOLGROUP(HText *me) +{ + if (!me || !me->stbl) + return; + if (Stbl_finishColGroup(me->stbl) < 0) { + HText_cancelStbl(me); /* give up */ + } +} + +/* Start a THEAD / TFOOT / TBODY - remember its alignment info +*/ +void HText_startStblRowGroup(HText *me, int alignment) +{ + if (!me || !me->stbl) + return; + if (Stbl_addRowGroup(me->stbl, alignment) < 0) { + HText_cancelStbl(me); /* give up */ + } +} + +static void compute_show_number(TextAnchor *a) +{ + HTAnchor *cur, *tst; + TextAnchor *b; + int match; + + a->show_number = a->number; + if (unique_urls + && HTMainText != 0 + && HTMainText->first_anchor != 0 + && a->anchor != 0 + && (cur = a->anchor->dest) != 0 + && cur->parent != 0 + && cur->parent->address != 0) { + + match = 0; + for (b = HTMainText->first_anchor; b != a; b = b->next) { + if (b->anchor != 0 + && (tst = b->anchor->dest) != 0 + && tst->parent != 0 + && tst->parent->address != 0 + && !strcmp(cur->parent->address, + tst->parent->address) + && !strcmp(NonNull(a->anchor->tag), NonNull(b->anchor->tag))) { + match = b->show_number; + break; + } + } + if (match) + a->show_number = match; + else + a->show_number = HTMainText->next_number++; + } +} + +/* Anchor handling + * --------------- + */ +static void add_link_number(HText *text, TextAnchor *a, int save_position) +{ + char marker[32]; + + /* + * If we are doing link_numbering add the link number. + */ + if ((a->number > 0) +#ifdef USE_PRETTYSRC + && (text->source ? !psrcview_no_anchor_numbering : 1) +#endif + && links_are_numbered()) { + char saved_lastchar = HText_getLastChar(text); + int saved_linenum = text->Lines; + HTAnchor *link_dest; + char *link_text; + + compute_show_number(a); + + if (dump_links_inline + && (link_dest = HTAnchor_followLink(a->anchor)) != 0 + && (link_text = HTAnchor_address(link_dest)) != 0) { + HText_appendText(text, "["); + HText_appendText(text, link_text); + HText_appendText(text, "]"); + } else { + sprintf(marker, "[%d]", a->show_number); + HText_appendText(text, marker); + } + if (saved_linenum && text->Lines && saved_lastchar != ' ') + HText_setLastChar(text, ']'); /* if marker not after space caused split */ + if (save_position) { + a->line_num = text->Lines; + a->line_pos = (short) text->last_line->size; + } + } +} + +/* Start an anchor field +*/ +int HText_beginAnchor(HText *text, int underline, + HTChildAnchor *anc) +{ + TextAnchor *a; + + POOLtypecalloc(TextAnchor, a); + + if (a == NULL) + outofmem(__FILE__, "HText_beginAnchor"); + + a->inUnderline = (BOOLEAN) underline; + + a->sgml_offset = SGML_offset(); + a->line_num = text->Lines; + a->line_pos = (short) text->last_line->size; + if (text->last_anchor) { + text->last_anchor->next = a; + } else { + text->first_anchor = a; + } + a->next = 0; + a->anchor = anc; + a->extent = 0; + a->link_type = HYPERTEXT_ANCHOR; + text->last_anchor = a; + + if (track_internal_links + && HTAnchor_followTypedLink(anc, HTInternalLink)) { + a->number = ++(text->last_anchor_number); + a->link_type = INTERNAL_LINK_ANCHOR; + } else if (HTAnchor_followLink(anc)) { + a->number = ++(text->last_anchor_number); + } else { + a->number = 0; + } + a->show_number = 0; + + if (number_links_on_left) + add_link_number(text, a, TRUE); + return (a->number); +} + +/* If !really, report whether the anchor is empty. */ +static BOOL HText_endAnchor0(HText *text, int number, + int really) +{ + TextAnchor *a; + + /* + * The number argument is set to 0 in HTML.c and + * LYCharUtils.c when we want to end the anchor + * for the immediately preceding HText_beginAnchor() + * call. If it's greater than 0, we want to handle + * a particular anchor. This allows us to set links + * for positions indicated by NAME or ID attributes, + * without needing to close any anchor with an HREF + * within which that link might be embedded. -FM + */ + if (number <= 0 || number == text->last_anchor->number) { + a = text->last_anchor; + } else { + for (a = text->first_anchor; a; a = a->next) { + if (a->number == number) { + break; + } + } + if (a == NULL) { + /* + * There's no anchor with that number, + * so we'll default to the last anchor, + * and cross our fingers. -FM + */ + a = text->last_anchor; + } + } + + CTRACE((tfp, "GridText:HText_endAnchor0: number:%d link_type:%d\n", + a->number, a->link_type)); + if (a->link_type == INPUT_ANCHOR) { + /* + * Shouldn't happen, but put test here anyway to be safe. - LE + */ + + CTRACE((tfp, + "BUG: HText_endAnchor0: internal error: last anchor was input field!\n")); + return FALSE; + } + + if (a->number) { + /* + * If it goes somewhere... + */ + int i, j, k, l; + BOOL remove_numbers_on_empty = (BOOL) ((links_are_numbered() && + ((text->hiddenlinkflag != HIDDENLINKS_MERGE) + || (LYNoISMAPifUSEMAP && + !(text->node_anchor && text->node_anchor->bookmark) + && HTAnchor_isISMAPScript + (HTAnchor_followLink(a->anchor)))))); + HTLine *last = text->last_line; + HTLine *prev = text->last_line->prev; + HTLine *start = last; + int CurBlankExtent = 0; + int BlankExtent = 0; + int extent_adjust = 0; + + /* Find the length taken by the anchor */ + l = text->Lines; /* lineno of last */ + + /* the last line of an anchor may contain a trailing blank, + * which will be trimmed later. Discount it from the extent. + */ + if (l > a->line_num) { + for (i = start->size; i > 0; --i) { + if (isspace(UCH(start->data[i - 1]))) { + --extent_adjust; + } else { + break; + } + } + } + + while (l > a->line_num) { + extent_adjust += start->size; + start = start->prev; + l--; + } + /* Now start is the start line of the anchor */ + extent_adjust += start->size - a->line_pos; + start = last; /* Used later */ + + /* + * Check if the anchor content has only + * white and special characters, starting + * with the content on the last line. -FM + */ + a->extent = (short) (a->extent + extent_adjust); + if (a->extent > (int) last->size) { + /* + * The anchor extends over more than one line, + * so set up to check the entire last line. -FM + */ + i = last->size; + } else { + /* + * The anchor is restricted to the last line, + * so check from the start of the anchor. -FM + */ + i = a->extent; + } + k = j = (last->size - i); + while (j < (int) last->size) { + if (!IsSpecialAttrChar(last->data[j]) && + !isspace(UCH(last->data[j])) && + last->data[j] != HT_NON_BREAK_SPACE && + last->data[j] != HT_EN_SPACE) + break; + i--; + j++; + } + if (i == 0) { + if (a->extent > (int) last->size) { + /* + * The anchor starts on a preceding line, and + * the last line has only white and special + * characters, so declare the entire extent + * of the last line as blank. -FM + */ + CurBlankExtent = BlankExtent = last->size; + } else { + /* + * The anchor starts on the last line, and + * has only white or special characters, so + * declare the anchor's extent as blank. -FM + */ + CurBlankExtent = BlankExtent = a->extent; + } + } + /* + * While the anchor starts on a line preceding + * the one we just checked, and the one we just + * checked has only white and special characters, + * check whether the anchor's content on the + * immediately preceding line also has only + * white and special characters. -FM + */ + while (i == 0 && + (a->extent > CurBlankExtent || + (a->extent == CurBlankExtent && + k == 0 && + prev != text->last_line && + (prev->size == 0 || + prev->data[prev->size - 1] == ']')))) { + start = prev; + k = j = prev->size - a->extent + CurBlankExtent; + if (j < 0) { + /* + * The anchor starts on a preceding line, + * so check all of this line. -FM + */ + j = 0; + i = prev->size; + } else { + /* + * The anchor starts on this line. -FM + */ + i = a->extent - CurBlankExtent; + } + while (j < (int) prev->size) { + if (!IsSpecialAttrChar(prev->data[j]) && + !isspace(UCH(prev->data[j])) && + prev->data[j] != HT_NON_BREAK_SPACE && + prev->data[j] != HT_EN_SPACE) + break; + i--; + j++; + } + if (i == 0) { + if (a->extent > (CurBlankExtent + (int) prev->size) || + (a->extent == CurBlankExtent + (int) prev->size && + k == 0 && + prev->prev != text->last_line && + (prev->prev->size == 0 || + prev->prev->data[prev->prev->size - 1] == ']'))) { + /* + * This line has only white and special + * characters, so treat its entire extent + * as blank, and decrement the pointer for + * the line to be analyzed. -FM + */ + CurBlankExtent += prev->size; + BlankExtent = CurBlankExtent; + prev = prev->prev; + } else { + /* + * The anchor starts on this line, and it + * has only white or special characters, so + * declare the anchor's extent as blank. -FM + */ + BlankExtent = a->extent; + break; + } + } + } + if (!really) { /* Just report whether it is empty */ + a->extent = (short) (a->extent - extent_adjust); + return (BOOL) (i == 0); + } + if (i == 0) { + /* + * It's an invisible anchor probably from an ALT="" + * or an ignored ISMAP attribute due to a companion + * USEMAP. -FM + */ + a->show_anchor = NO; + + CTRACE((tfp, + "HText_endAnchor0: hidden (line,pos,ext,BlankExtent):(%d,%d,%d,%d)", + a->line_num, a->line_pos, a->extent, + BlankExtent)); + + /* + * If links are numbered, then try to get rid of the + * numbered bracket and adjust the anchor count. -FM + * + * Well, let's do this only if -hiddenlinks=merged is not in + * effect, or if we can be reasonably sure that + * this is the result of an intentional non-generation of + * anchor text via NO_ISMAP_IF_USEMAP. In other cases it can + * actually be a feature that numbered links alert the viewer + * to the presence of a link which is otherwise not selectable - + * possibly caused by HTML errors. - kw + */ + if (remove_numbers_on_empty) { + int NumSize = 0; + TextAnchor *anc; + + /* + * Set start->data[j] to the close-square-bracket, + * or to the beginning of the line on which the + * anchor start. -FM + */ + if (start == last) { + /* + * The anchor starts on the last line. -FM + */ + j = (last->size - a->extent - 1); + } else { + /* + * The anchor starts on a previous line. -FM + */ + prev = start->prev; + j = (start->size - a->extent + CurBlankExtent - 1); + } + if (j < 0) + j = 0; + i = j; + + /* + * If start->data[j] is a close-square-bracket, verify + * that it's the end of the numbered bracket, and if so, + * strip the numbered bracket. If start->data[j] is not + * a close-square-bracket, check whether we had a wrap + * and the close-square-bracket is at the end of the + * previous line. If so, strip the numbered bracket + * from that line. -FM + */ + if (start->data[j] == ']') { + j--; + NumSize++; + while (j >= 0 && isdigit(UCH(start->data[j]))) { + j--; + NumSize++; + } + while (j < 0) { + j++; + NumSize--; + } + if (start->data[j] == '[') { + /* + * The numbered bracket is entirely + * on this line. -FM + */ + NumSize++; + if (start == last && (int) text->permissible_split > j) { + if ((int) text->permissible_split - NumSize < j) + text->permissible_split = (unsigned) j; + else + text->permissible_split -= (unsigned) NumSize; + } + k = j + NumSize; + while (k < (int) start->size) + start->data[j++] = start->data[k++]; + for (anc = a; anc; anc = anc->next) { + if (anc->line_num == a->line_num && + anc->line_pos >= NumSize) { + anc->line_pos = (short) (anc->line_pos - NumSize); + } + } + start->size = (unsigned short) j; + start->data[j++] = '\0'; + while (j < k) + start->data[j++] = '\0'; + } else if (prev && prev->size > 1) { + k = (i + 1); + j = (prev->size - 1); + while ((j >= 0) && IsSpecialAttrChar(prev->data[j])) + j--; + i = (j + 1); + while (j >= 0 && + isdigit(UCH(prev->data[j]))) { + j--; + NumSize++; + } + while (j < 0) { + j++; + NumSize--; + } + if (prev->data[j] == '[') { + /* + * The numbered bracket started on the + * previous line, and part of it was + * wrapped to this line. -FM + */ + while (i < (int) prev->size) + prev->data[j++] = prev->data[i++]; + prev->size = (unsigned short) j; + prev->data[j] = '\0'; + while (j < i) + prev->data[j++] = '\0'; + if (start == last && text->permissible_split > 0) { + if ((int) text->permissible_split < k) + text->permissible_split = 0; + else + text->permissible_split -= (unsigned) k; + } + j = 0; + i = k; + while (k < (int) start->size) + start->data[j++] = start->data[k++]; + for (anc = a; anc; anc = anc->next) { + if (anc->line_num == a->line_num && + anc->line_pos >= i) { + anc->line_pos = (short) (anc->line_pos - i); + } + } + start->size = (unsigned short) j; + start->data[j++] = '\0'; + while (j < k) + start->data[j++] = '\0'; + } else { + /* + * Shucks! We didn't find the + * numbered bracket. -FM + */ + a->show_anchor = YES; + } + } else { + /* + * Shucks! We didn't find the + * numbered bracket. -FM + */ + a->show_anchor = YES; + } + } else if (prev && prev->size > 2) { + j = (prev->size - 1); + while ((j >= 0) && IsSpecialAttrChar(prev->data[j])) + j--; + if (j < 0) + j = 0; + if ((j >= 2) && + (prev->data[j] == ']' && + isdigit(UCH(prev->data[j - 1])))) { + j--; + NumSize++; + while (j >= 0 && + isdigit(UCH(prev->data[j]))) { + j--; + NumSize++; + } + while (j < 0) { + j++; + NumSize--; + } + if (prev->data[j] == '[') { + /* + * The numbered bracket is all on the + * previous line, and the anchor content + * was wrapped to the last line. -FM + */ + NumSize++; + k = j + NumSize; + while (k < (int) prev->size) + prev->data[j++] = prev->data[k++]; + prev->size = (unsigned short) j; + prev->data[j++] = '\0'; + while (j < k) + prev->data[j++] = '\0'; + } else { + /* + * Shucks! We didn't find the + * numbered bracket. -FM + */ + a->show_anchor = YES; + } + } else { + /* + * Shucks! We didn't find the + * numbered bracket. -FM + */ + a->show_anchor = YES; + } + } else { + /* + * Shucks! We didn't find the + * numbered bracket. -FM + */ + a->show_anchor = YES; + } + } + } else { + if (!number_links_on_left) + add_link_number(text, a, FALSE); + /* + * The anchor's content is not restricted to only + * white and special characters, so we'll show it + * as a link. -FM + */ + a->show_anchor = YES; + if (BlankExtent) { + CTRACE((tfp, + "HText_endAnchor0: blanks (line,pos,ext,BlankExtent):(%d,%d,%d,%d)", + a->line_num, a->line_pos, a->extent, + BlankExtent)); + } + } + if (a->show_anchor == NO) { + /* + * The anchor's content is restricted to white + * and special characters, so set its number + * and extent to zero, decrement the visible + * anchor number counter, and add this anchor + * to the hidden links list. -FM + */ + a->extent = 0; + if (text->hiddenlinkflag != HIDDENLINKS_MERGE) { + a->number = 0; + text->last_anchor_number--; + HText_AddHiddenLink(text, a); + } + } else { + /* + * The anchor's content is not restricted to white + * and special characters, so we'll display the + * content, but shorten its extent by any trailing + * blank lines we've detected. -FM + */ + a->extent = (short) (a->extent - ((BlankExtent < a->extent) + ? BlankExtent + : 0)); + } + if (BlankExtent || a->extent <= 0 || a->number <= 0) { + CTRACE((tfp, + "->[%d](%d,%d,%d,%d)\n", + a->number, + a->line_num, a->line_pos, a->extent, + BlankExtent)); + } + } else { + if (!really) /* Just report whether it is empty */ + return FALSE; + /* + * It's a named anchor without an HREF, so it + * should be registered but not shown as a + * link. -FM + */ + a->show_anchor = NO; + a->extent = 0; + } + return FALSE; +} + +void HText_endAnchor(HText *text, int number) +{ + HText_endAnchor0(text, number, 1); +} + +/* + This returns whether the given anchor has blank content. Shamelessly copied + from HText_endAnchor. The values returned are meaningful only for "normal" + links - like ones produced by foo, no inputs, etc. - VH +*/ +#ifdef MARK_HIDDEN_LINKS +BOOL HText_isAnchorBlank(HText *text, int number) +{ + return HText_endAnchor0(text, number, 0); +} +#endif /* MARK_HIDDEN_LINKS */ + +void HText_appendText(HText *text, const char *str) +{ + const char *p; + + if (str != NULL && + text != NULL && + text->halted != 3) { + for (p = str; *p; p++) { + HText_appendCharacter(text, *p); + } + } +} + +static int remove_special_attr_chars(char *buf) +{ + register char *cp; + register int soft_newline_count = 0; + + for (cp = buf; *cp != '\0'; cp++) { + /* + * Don't print underline chars. + */ + soft_newline_count += (*cp == LY_SOFT_NEWLINE); + if (!IsSpecialAttrChar(*cp)) { + *buf++ = *cp; + } + } + *buf = '\0'; + return soft_newline_count; +} + +/* + * This function trims blank lines from the end of the document, and + * then gets the hightext from the text by finding the char position, + * and brings the anchors in line with the text by adding the text + * offset to each of the anchors. + */ +void HText_endAppend(HText *text) +{ + HTLine *line_ptr; + + if (!text) + return; + + CTRACE((tfp, "GridText: Entering HText_endAppend\n")); + + /* + * Create a blank line at the bottom. + */ + new_line(text); + + if (text->halted) { + if (text->stbl) { + HText_cancelStbl(text); + } + /* + * If output was stopped because memory was low, and we made + * it to the end of the document, reset those flags and hope + * things are better now. - kw + */ + LYFakeZap(NO); + text->halted = 0; + } else if (text->stbl) { + /* + * Could happen if TABLE end tag was missing. + * Alternatively we could cancel in this case. - kw + */ + HText_endStblTABLE(text); + } + + /* + * Get the first line. + */ + if (LYtrimBlankLines && (line_ptr = FirstHTLine(text)) != 0) { + /* + * Remove blank lines at the end of the document. + */ + while (text->last_line->data[0] == '\0' && text->Lines > 0) { + HTLine *next_to_the_last_line = text->last_line->prev; + + CTRACE((tfp, "GridText: Removing bottom blank line: `%s'\n", + text->last_line->data)); + /* + * line_ptr points to the first line. + */ + next_to_the_last_line->next = line_ptr; + line_ptr->prev = next_to_the_last_line; + freeHTLine(text, text->last_line); + text->last_line = next_to_the_last_line; + text->Lines--; + CTRACE((tfp, "GridText: New bottom line: `%s'\n", + text->last_line->data)); + } + } + + /* + * Fix up the anchor structure values and + * create the hightext strings. -FM + */ + HText_trimHightext(text, TRUE, -1); +} + +/* + * This function gets the hightext from the text by finding the char + * position, and brings the anchors in line with the text by adding the text + * offset to each of the anchors. + * + * `Forms input' fields cannot be displayed properly without this function + * to be invoked (detected in display_partial mode). + * + * If final is set, this is the final fixup; if not set, we don't have + * to do everything because there should be another call later. + * + * BEFORE this function has treated a TextAnchor, its line_pos and + * extent fields are counting bytes in the HTLine data, including + * invisible special attribute chars and counting UTF-8 multibyte + * characters as multiple bytes. + * + * AFTER the adjustment, the anchor line_pos (and hightext offset if + * applicable) fields indicate x positions in terms of displayed character + * cells, and the extent field apparently is unimportant; the anchor text has + * been copied to the hightext fields (which should have been NULL up to that + * point), with special attribute chars removed. + * + * This needs to be done so that display_page finds the anchors in the + * form it expects when it sets the links[] elements. + */ +static void HText_trimHightext(HText *text, + int final, + int stop_before) +{ + int cur_line; + TextAnchor *anchor_ptr; + TextAnchor *prev_a = NULL; + HTLine *line_ptr; + HTLine *line_ptr2; + unsigned char ch; + char *hilite_str; + int hilite_len; + int actual_len; + int count_line; + + if (!text) + return; + + if (final) { + CTRACE((tfp, "GridText: Entering HText_trimHightext (final)\n")); + } else { + if (stop_before < 0 || stop_before > text->Lines) + stop_before = text->Lines; + CTRACE((tfp, + "GridText: Entering HText_trimHightext (partial: 0..%d/%d)\n", + stop_before, text->Lines)); + } + + /* + * Get the first line. + */ + line_ptr = FirstHTLine(text); + cur_line = 0; + + /* + * Fix up the anchor structure values and + * create the hightext strings. -FM + */ + for (anchor_ptr = text->first_anchor; + anchor_ptr != NULL; + prev_a = anchor_ptr, anchor_ptr = anchor_ptr->next) { + int anchor_col; + + re_parse: + /* + * Find the right line. + */ + for (; line_ptr != NULL && anchor_ptr->line_num > cur_line; + line_ptr = line_ptr->next, cur_line++) { + ; /* null body */ + } + if (line_ptr == NULL) + continue; + + if (!final) { + /* + * If this is not the final call, stop when we have reached + * the last line, or the very end of preceding line. + * The last line is probably still not finished. - kw + */ + if (cur_line >= stop_before) + break; + if (anchor_ptr->line_num >= text->Lines - 1 + && anchor_ptr->line_pos >= (int) text->last_line->prev->size) + break; + /* + * Also skip this anchor if it looks like HText_endAnchor + * is not yet done with it. - kw + */ + if (!anchor_ptr->extent && anchor_ptr->number && + (anchor_ptr->link_type & HYPERTEXT_ANCHOR) && + !anchor_ptr->show_anchor && + anchor_ptr->number == text->last_anchor_number) + continue; + } + + /* + * If hightext has already been set, then we must have already + * done the trimming & adjusting for this anchor, so avoid + * doing it a second time. - kw + */ + if (LYGetHiTextStr(anchor_ptr, 0) != NULL) + continue; + + if (anchor_ptr->line_pos > (int) line_ptr->size) { + anchor_ptr->line_pos = (short) line_ptr->size; + } + if (anchor_ptr->line_pos < 0) { + anchor_ptr->line_pos = 0; + anchor_ptr->line_num = cur_line; + } + CTRACE((tfp, + "GridText: Anchor found on line:%d col:%d [%05d:%d] ext:%d\n", + cur_line, + anchor_ptr->line_pos, + anchor_ptr->sgml_offset, + anchor_ptr->number, + anchor_ptr->extent)); + + /* + * Strip off any spaces or SpecialAttrChars at the beginning, + * if they exist, but only on HYPERTEXT_ANCHORS. + */ + if (anchor_ptr->link_type & HYPERTEXT_ANCHOR) { + ch = UCH(line_ptr->data[anchor_ptr->line_pos]); + while (isspace(ch) || + IsSpecialAttrChar(ch)) { + anchor_ptr->line_pos++; + anchor_ptr->extent--; + ch = UCH(line_ptr->data[anchor_ptr->line_pos]); + } + } + if (anchor_ptr->extent < 0) { + anchor_ptr->extent = 0; + } + + CTRACE((tfp, "anchor text: '%s'\n", line_ptr->data)); + /* + * If the link begins with an end of line and we have more lines, then + * start the highlighting on the next line. -FM. + * + * But if an empty anchor is at the end of line and empty, keep it + * where it is, unless the previous anchor in the list (if any) already + * starts later. - kw + */ + if ((unsigned) anchor_ptr->line_pos >= strlen(line_ptr->data)) { + if (cur_line < text->Lines && + (anchor_ptr->extent || + anchor_ptr->line_pos != (int) line_ptr->size || + (prev_a && /* How could this happen? */ + (prev_a->line_num > anchor_ptr->line_num)))) { + anchor_ptr->line_num++; + anchor_ptr->line_pos = 0; + CTRACE((tfp, "found anchor at end of line\n")); + goto re_parse; + } else { + CTRACE((tfp, "found anchor at end of line, leaving it there\n")); + } + } + + /* + * Copy the link name into the data structure. + */ + if (anchor_ptr->extent > 0 + && anchor_ptr->line_pos >= 0) { + int size = (int) line_ptr->size - anchor_ptr->line_pos; + + if (size > anchor_ptr->extent) + size = anchor_ptr->extent; + LYClearHiText(anchor_ptr); + LYSetHiText(anchor_ptr, + &line_ptr->data[anchor_ptr->line_pos], + (unsigned) size); + } else { + LYClearHiText(anchor_ptr); + LYSetHiText(anchor_ptr, "", 0); + } + + /* + * If the anchor extends over more than one line, copy that into the + * data structure. + */ + hilite_str = LYGetHiTextStr(anchor_ptr, 0); + hilite_len = (int) strlen(hilite_str); + actual_len = anchor_ptr->extent; + + line_ptr2 = line_ptr; + assert(line_ptr2 != 0); + + count_line = cur_line; + while (actual_len > hilite_len) { + HTLine *old_line_ptr2 = line_ptr2; + + count_line++; + if ((line_ptr2 = line_ptr2->next) == NULL) + break; + + if (!final + && count_line >= stop_before) { + LYClearHiText(anchor_ptr); + break; + } else if (old_line_ptr2 == text->last_line) { + break; + } + + /* + * Double check that we have a line pointer, and if so, copy into + * highlight text. + */ + if (line_ptr2) { + char *hi_string = NULL; + int hi_offset = line_ptr2->offset; + + StrnAllocCopy(hi_string, + line_ptr2->data, + (size_t) (actual_len - hilite_len)); + actual_len -= (int) strlen(hi_string); + /*handle LY_SOFT_NEWLINEs -VH */ + hi_offset += remove_special_attr_chars(hi_string); + + if (anchor_ptr->link_type & HYPERTEXT_ANCHOR) { + LYTrimTrailing(hi_string); + } + if (non_empty(hi_string)) { + LYAddHiText(anchor_ptr, hi_string, hi_offset); + } else if (actual_len > hilite_len) { + LYAddHiText(anchor_ptr, "", hi_offset); + } + FREE(hi_string); + } + } + + if (!final + && count_line >= stop_before) { + break; + } + + hilite_str = LYGetHiTextStr(anchor_ptr, 0); + remove_special_attr_chars(hilite_str); + if (anchor_ptr->link_type & HYPERTEXT_ANCHOR) { + LYTrimTrailing(hilite_str); + } + + /* + * Save the offset (bytes) of the anchor in the line's data. + */ + anchor_col = anchor_ptr->line_pos; + + /* + * Subtract any formatting characters from the x position of the link. + */ +#ifdef WIDEC_CURSES + if (anchor_ptr->line_pos > 0) { + /* + * LYstrExtent filters out the formatting characters, so we do not + * have to count them here, except for soft newlines. + */ + anchor_ptr->line_pos = (short) LYstrExtent2(line_ptr->data, anchor_col); + if (line_ptr->data[0] == LY_SOFT_NEWLINE) + anchor_ptr->line_pos = (short) (anchor_ptr->line_pos + 1); + } +#else /* 8-bit curses, etc. */ + if (anchor_ptr->line_pos > 0) { + register int offset = 0, i = 0; + int have_soft_newline_in_1st_line = 0; + + for (; i < anchor_col; i++) { + if (IS_UTF_EXTRA(line_ptr->data[i]) || + IsSpecialAttrChar(line_ptr->data[i])) { + offset++; + have_soft_newline_in_1st_line += (line_ptr->data[i] == LY_SOFT_NEWLINE); + } + } + anchor_ptr->line_pos = (short) (anchor_ptr->line_pos - offset); + /*handle LY_SOFT_NEWLINEs -VH */ + anchor_ptr->line_pos = (short) (anchor_ptr->line_pos + have_soft_newline_in_1st_line); + } +#endif /* WIDEC_CURSES */ + + /* + * Set the line number. + */ + anchor_ptr->line_pos = (short) (anchor_ptr->line_pos + line_ptr->offset); + anchor_ptr->line_num = cur_line; + + CTRACE((tfp, "GridText: add link on line %d col %d [%d] %s\n", + cur_line, anchor_ptr->line_pos, + anchor_ptr->number, "in HText_trimHightext")); + } +} + +/* Return the anchor associated with this node +*/ +HTParentAnchor *HText_nodeAnchor(HText *text) +{ + return text->node_anchor; +} + +/* GridText specials + * ================= + */ + +/* + * HText_childNextNumber() returns the anchor with index [number], + * using a pointer from the previous number (=optimization) or NULL. + */ +HTChildAnchor *HText_childNextNumber(int number, void **prev) +{ + /* Sorry, TextAnchor is not declared outside this file, use a cast. */ + TextAnchor *a = (TextAnchor *) *prev; + + if (!HTMainText || number <= 0) + return (HTChildAnchor *) 0; /* Fail */ + if (number == 1 || !a) + a = HTMainText->first_anchor; + + /* a strange thing: positive a->number's are sorted, + * and between them several a->number's may be 0 -- skip them + */ + for (; a && a->number != number; a = a->next) ; + + if (!a) + return (HTChildAnchor *) 0; /* Fail */ + *prev = (void *) a; + return a->anchor; +} + +/* + * For the -unique-urls option, find the anchor-number of the first occurrence + * of a given address. + */ +int HText_findAnchorNumber(void *avoid) +{ + TextAnchor *a = (TextAnchor *) avoid; + + if (a->number > 0 && a->show_number == 0) + compute_show_number(a); + + return a->show_number; +} + +static const char *inputFieldDesc(FormInfo * input) +{ + const char *result = 0; + + switch (input->type) { + case F_TEXT_TYPE: + result = gettext("text entry field"); + break; + case F_PASSWORD_TYPE: + result = gettext("password entry field"); + break; + case F_CHECKBOX_TYPE: + result = gettext("checkbox"); + break; + case F_RADIO_TYPE: + result = gettext("radio button"); + break; + case F_SUBMIT_TYPE: + result = gettext("submit button"); + break; + case F_RESET_TYPE: + result = gettext("reset button"); + break; + case F_BUTTON_TYPE: + result = gettext("script button"); + break; + case F_OPTION_LIST_TYPE: + result = gettext("popup menu"); + break; + case F_HIDDEN_TYPE: + result = gettext("hidden form field"); + break; + case F_TEXTAREA_TYPE: + result = gettext("text entry area"); + break; + case F_RANGE_TYPE: + result = gettext("range entry field"); + break; + case F_FILE_TYPE: + result = gettext("file entry field"); + break; + case F_TEXT_SUBMIT_TYPE: + result = gettext("text-submit field"); + break; + case F_IMAGE_SUBMIT_TYPE: + result = gettext("image-submit button"); + break; + case F_KEYGEN_TYPE: + result = gettext("keygen field"); + break; + default: + result = gettext("unknown form field"); + break; + } + return result; +} + +/* + * HText_FormDescNumber() returns a description of the form field + * with index N. The index corresponds to the [number] we print + * for the field. -FM & LE + */ +void HText_FormDescNumber(int number, + const char **desc) +{ + TextAnchor *a; + + if (!desc) + return; + + if (!(HTMainText && HTMainText->first_anchor) || number <= 0) { + *desc = gettext("unknown field or link"); + return; + } + + for (a = HTMainText->first_anchor; a; a = a->next) { + if (a->number == number) { + if (!(a->input_field && a->input_field->type)) { + *desc = gettext("unknown field or link"); + return; + } + break; + } + } + + if (a != NULL) + *desc = inputFieldDesc(a->input_field); +} + +/* HTGetRelLinkNum returns the anchor number to which follow_link_number() + * is to jump (input was 123+ or 123- or 123+g or 123-g or 123 or 123g) + * num is the number specified + * rel is 0 or '+' or '-' + * cur is the current link + */ +int HTGetRelLinkNum(int num, + int rel, + int cur) +{ + TextAnchor *a, *l = 0; + int scrtop = HText_getTopOfScreen(); /*XXX +1? */ + int curline = links[cur].anchor_line_num; + int curpos = links[cur].lx; + int on_screen = (curline >= scrtop && curline < (scrtop + display_lines)); + + /* curanchor may or may not be the "current link", depending whether it's + * on the current screen + */ + int curanchor = links[cur].anchor_number; + + CTRACE((tfp, "HTGetRelLinkNum(%d,%d,%d) -- HTMainText=%p\n", + num, rel, cur, (void *) HTMainText)); + CTRACE((tfp, + " scrtop=%d, curline=%d, curanchor=%d, display_lines=%d, %s\n", + scrtop, curline, curanchor, display_lines, + on_screen ? "on_screen" : "0")); + if (!HTMainText) + return 0; + if (rel == 0) + return num; + + /* if cur numbered link is on current page, use it */ + if (on_screen && curanchor) { + CTRACE((tfp, "curanchor=%d at line %d on screen\n", curanchor, curline)); + if (rel == '+') + return curanchor + num; + else if (rel == '-') + return curanchor - num; + else + return num; /* shouldn't happen */ + } + + /* no current link on screen, or current link is not numbered + * -- find previous closest numbered link + */ + for (a = HTMainText->first_anchor; a; a = a->next) { + CTRACE((tfp, " a->line_num=%d, a->number=%d\n", a->line_num, a->number)); + if (a->line_num >= scrtop) + break; + if (a->number == 0) + continue; + l = a; + curanchor = l->number; + } + CTRACE((tfp, " a=%p, l=%p, curanchor=%d\n", (void *) a, (void *) l, curanchor)); + if (on_screen) { /* on screen but not a numbered link */ + for (; a; a = a->next) { + if (a->number) { + l = a; + curanchor = l->number; + } + if (curline == a->line_num && curpos == a->line_pos) + break; + } + } + if (rel == '+') { + return curanchor + num; + } else if (rel == '-') { + if (l) + return curanchor + 1 - num; + else { + for (; a && a->number == 0; a = a->next) ; + return a ? a->number - num : 0; + } + } else + return num; /* shouldn't happen */ +} + +/* + * HTGetLinkInfo returns some link info based on the number. + * + * If want_go is not 0, caller requests to know a line number for + * the link indicated by number. It will be returned in *go_line, and + * *linknum will be set to an index into the links[] array, to use after + * the line in *go_line has been made the new top screen line. + * *hightext and *lname are unchanged. - KW + * + * If want_go is 0 and the number doesn't represent an input field, info + * on the link indicated by number is deposited in *hightext and *lname. + */ +int HTGetLinkInfo(int number, + int want_go, + int *go_line, + int *linknum, + char **hightext, + char **lname) +{ + TextAnchor *a; + HTAnchor *link_dest; + + HTAnchor *link_dest_intl = NULL; + int anchors_this_line = 0, anchors_this_screen = 0; + int prev_anchor_line = -1, prev_prev_anchor_line = -1; + + if (!HTMainText) + return (NO); + + for (a = HTMainText->first_anchor; a; a = a->next) { + /* + * Count anchors, first on current line if there is more + * than one. We have to count all links, including form + * field anchors and others with a->number == 0, because + * they are or will be included in the links[] array. + * The exceptions are hidden form fields and anchors with + * show_anchor not set, because they won't appear in links[] + * and don't count towards nlinks. - KW + */ + if ((a->show_anchor) && + !(a->link_type == INPUT_ANCHOR + && a->input_field->type == F_HIDDEN_TYPE)) { + if (a->line_num == prev_anchor_line) { + anchors_this_line++; + } else { + /* + * This anchor is on a different line than the previous one. + * Remember which was the line number of the previous anchor, + * for use in screen positioning later. - KW + */ + anchors_this_line = 1; + prev_prev_anchor_line = prev_anchor_line; + prev_anchor_line = a->line_num; + } + if (a->line_num >= HTMainText->top_of_screen) { + /* + * Count all anchors starting with the top line of the + * currently displayed screen. Just keep on counting + * beyond this screen's bottom line - we'll know whether + * a found anchor is below the current screen by a check + * against nlinks later. - KW + */ + anchors_this_screen++; + } + } + + if (a->number == number) { + /* + * We found it. Now process it, depending + * on what kind of info is requested. - KW + */ + if (want_go || a->link_type == INPUT_ANCHOR) { + if (a->show_anchor == NO) { + /* + * The number requested has been assigned to an anchor + * without any selectable text, so we cannot position + * on it. The code for suppressing such anchors in + * HText_endAnchor() may not have applied, or it may + * have failed. Return a failure indication so that + * the user will notice that something is wrong, + * instead of positioning on some other anchor which + * might result in inadvertent activation. - KW + */ + return (NO); + } + if (anchors_this_screen > 0 && + anchors_this_screen <= nlinks && + a->line_num >= HTMainText->top_of_screen && + a->line_num < HTMainText->top_of_screen + (display_lines)) { + /* + * If the requested anchor is within the current screen, + * just set *go_line so that the screen window won't move + * (keep it as it is), and set *linknum to the index of + * this link in the current links[] array. - KW + */ + *go_line = HTMainText->top_of_screen; + if (linknum) + *linknum = anchors_this_screen - 1; + } else { + /* + * if the requested anchor is not within the currently + * displayed screen, set *go_line such that the top line + * will be either + * (1) the line immediately below the previous + * anchor, or + * (2) about one third of a screenful above the line + * with the target, or + * (3) the first line of the document - + * whichever comes last. In all cases the line with our + * target will end up being the first line with any links + * on the new screen, so that we can use the + * anchors_this_line counter to point to the anchor in + * the new links[] array. - kw + */ + int max_offset = SEARCH_GOAL_LINE - 1; + + if (max_offset < 0) + max_offset = 0; + else if (max_offset >= display_lines) + max_offset = display_lines - 1; + *go_line = prev_anchor_line - max_offset; + if (*go_line <= prev_prev_anchor_line) + *go_line = prev_prev_anchor_line + 1; + if (*go_line < 0) + *go_line = 0; + if (linknum) + *linknum = anchors_this_line - 1; + } + return (LINK_LINE_FOUND); + } else { + *hightext = LYGetHiTextStr(a, 0); + link_dest = HTAnchor_followLink(a->anchor); + { + char *cp_freeme = NULL; + + if (traversal) { + cp_freeme = stub_HTAnchor_address(link_dest); + } else if (track_internal_links) { + if (a->link_type == INTERNAL_LINK_ANCHOR) { + link_dest_intl = + HTAnchor_followTypedLink(a->anchor, HTInternalLink); + if (link_dest_intl && link_dest_intl != link_dest) { + + CTRACE((tfp, + "HTGetLinkInfo: unexpected typed link to %s!\n", + link_dest_intl->parent->address)); + link_dest_intl = NULL; + } + } + if (link_dest_intl) { + char *cp2 = HTAnchor_address(link_dest_intl); + + FREE(*lname); + *lname = cp2; + return (WWW_INTERN_LINK_TYPE); + } else { + cp_freeme = HTAnchor_address(link_dest); + } + } else { + cp_freeme = HTAnchor_address(link_dest); + } + StrAllocCopy(*lname, cp_freeme); + FREE(cp_freeme); + } + return (WWW_LINK_TYPE); + } + } + } + return (NO); +} + +static BOOLEAN same_anchor_or_field(int numberA, + FormInfo * formA, + int numberB, + FormInfo * formB, + int ta_same) +{ + if (numberA > 0 || numberB > 0) { + if (numberA == numberB) + return (YES); + else if (!ta_same) + return (NO); + } + if (formA || formB) { + if (formA == formB) { + return (YES); + } else if (!ta_same) { + return (NO); + } else if (!(formA && formB)) { + return (NO); + } + } else { + return (NO); + } + if (formA->type != formB->type || + formA->type != F_TEXTAREA_TYPE || + formB->type != F_TEXTAREA_TYPE) { + return (NO); + } + if (formA->number != formB->number) + return (NO); + if (!formA->name || !formB->name) + return (YES); + return (BOOL) (strcmp(formA->name, formB->name) == 0); +} + +#define same_anchor_as_link(i,a,ta_same) (BOOL) (i >= 0 && a && \ + same_anchor_or_field(links[i].anchor_number,\ + (links[i].type == WWW_FORM_LINK_TYPE) ? links[i].l_form : NULL,\ + a->number,\ + (a->link_type == INPUT_ANCHOR) ? a->input_field : NULL,\ + ta_same)) +#define same_anchors(a1,a2,ta_same) (BOOL) (a1 && a2 && \ + same_anchor_or_field(a1->number,\ + (a1->link_type == INPUT_ANCHOR) ? a1->input_field : NULL,\ + a2->number,\ + (a2->link_type == INPUT_ANCHOR) ? a2->input_field : NULL,\ + ta_same)) + +/* + * Are there more textarea lines belonging to the same textarea before + * (direction < 0) or after (direction > 0) the current one? + * On entry, curlink must be the index in links[] of a textarea field. - kw + */ +BOOL HText_TAHasMoreLines(int curlink, + int direction) +{ + TextAnchor *a; + TextAnchor *prev_a = NULL; + + if (!HTMainText) + return (NO); + if (direction < 0) { + for (a = HTMainText->first_anchor; a; prev_a = a, a = a->next) { + if (a->link_type == INPUT_ANCHOR && + links[curlink].l_form == a->input_field) { + return same_anchors(a, prev_a, TRUE); + } + if (links[curlink].anchor_number && + a->number >= links[curlink].anchor_number) + break; + } + return NO; + } else { + for (a = HTMainText->first_anchor; a; a = a->next) { + if (a->link_type == INPUT_ANCHOR && + links[curlink].l_form == a->input_field) { + return same_anchors(a, a->next, TRUE); + } + if (links[curlink].anchor_number && + a->number >= links[curlink].anchor_number) + break; + } + return NO; + } +} + +/* + * HTGetLinkOrFieldStart - moving to previous or next link or form field. + * + * On input, + * curlink: current link, as index in links[] array (-1 if none) + * direction: whether to move up or down (or stay where we are) + * ta_skip: if FALSE, input fields belonging to the same textarea are + * are treated as different fields, as usual; + * if TRUE, fields of the same textarea are treated as a + * group for skipping. + * The caller wants information for positioning on the new link to be + * deposited in *go_line and (if linknum is not NULL) *linknum. + * + * On failure (no more links in the requested direction) returns NO + * and doesn't change *go_line or *linknum. Otherwise, LINK_DO_ARROWUP + * may be returned, and *go_line and *linknum not changed, to indicate that + * the caller should use a normal PREV_LINK or PREV_PAGE mechanism. + * Otherwise: + * The number (0-based counting) for the new top screen line will be returned + * in *go_line, and *linknum will be set to an index into the links[] array, + * to use after the line in *go_line has been made the new top screen + * line. - kw + */ +int HTGetLinkOrFieldStart(int curlink, + int *go_line, + int *linknum, + int direction, + int ta_skip) +{ + TextAnchor *a; + int anchors_this_line = 0; + int prev_anchor_line = -1, prev_prev_anchor_line = -1; + + struct agroup { + TextAnchor *anc; + int prev_anchor_line; + int anchors_this_line; + int anchors_this_group; + } previous, current; + struct agroup *group_to_go = NULL; + + if (!HTMainText) + return (NO); + + previous.anc = current.anc = NULL; + previous.prev_anchor_line = current.prev_anchor_line = -1; + previous.anchors_this_line = current.anchors_this_line = 0; + previous.anchors_this_group = current.anchors_this_group = 0; + + for (a = HTMainText->first_anchor; a; a = a->next) { + /* + * Count anchors, first on current line if there is more + * than one. We have to count all links, including form + * field anchors and others with a->number == 0, because + * they are or will be included in the links[] array. + * The exceptions are hidden form fields and anchors with + * show_anchor not set, because they won't appear in links[] + * and don't count towards nlinks. - KW + */ + if ((a->show_anchor) && + !(a->link_type == INPUT_ANCHOR + && a->input_field->type == F_HIDDEN_TYPE)) { + if (a->line_num == prev_anchor_line) { + anchors_this_line++; + } else { + /* + * This anchor is on a different line than the previous one. + * Remember which was the line number of the previous anchor, + * for use in screen positioning later. - KW + */ + anchors_this_line = 1; + prev_prev_anchor_line = prev_anchor_line; + prev_anchor_line = a->line_num; + } + + if (!same_anchors(current.anc, a, ta_skip)) { + previous.anc = current.anc; + previous.prev_anchor_line = current.prev_anchor_line; + previous.anchors_this_line = current.anchors_this_line; + previous.anchors_this_group = current.anchors_this_group; + current.anc = a; + current.prev_anchor_line = prev_prev_anchor_line; + current.anchors_this_line = anchors_this_line; + current.anchors_this_group = 1; + } else { + current.anchors_this_group++; + } + if (curlink >= 0) { + if (same_anchor_as_link(curlink, a, ta_skip)) { + if (direction == -1) { + group_to_go = &previous; + break; + } else if (direction == 0) { + group_to_go = ¤t; + break; + } + } else if (direction > 0 && + same_anchor_as_link(curlink, previous.anc, ta_skip)) { + group_to_go = ¤t; + break; + } + } else { + if (a->line_num >= HTMainText->top_of_screen) { + if (direction < 0) { + group_to_go = &previous; + break; + } else if (direction == 0) { + if (previous.anc) { + group_to_go = &previous; + break; + } else { + group_to_go = ¤t; + break; + } + } else { + group_to_go = ¤t; + break; + } + } + } + } + } + if (!group_to_go && curlink < 0 && direction <= 0) { + group_to_go = ¤t; + } + if (group_to_go) { + a = group_to_go->anc; + if (a) { + int max_offset; + + /* + * We know where to go; most of the stuff below is just + * tweaks to try to position the new screen in a specific + * way. + * + * In some cases going to a previous link can be done + * via the normal LYK_PREV_LINK action, which may give + * better positioning of the new screen. - kw + */ + if (a->line_num < HTMainText->top_of_screen && + a->line_num >= HTMainText->top_of_screen - (display_lines)) { + if ((curlink < 0 && + group_to_go->anchors_this_group == 1) || + (direction < 0 && + group_to_go != ¤t && + current.anc && + current.anc->line_num >= HTMainText->top_of_screen && + group_to_go->anchors_this_group == 1) || + (a->next && + a->next->line_num >= HTMainText->top_of_screen)) { + return (LINK_DO_ARROWUP); + } + } + /* + * The fundamental limitation of the current anchors_this_line + * counter method is that we only can set *linknum to the right + * index into the future links[] array if the line with our link + * ends up being the first line with any links (that count) on + * the new screen. Subject to that restriction we still have + * some vertical liberty (sometimes), and try to make the best + * of it. It may be a question of taste though. - kw + */ + if (a->line_num <= (display_lines)) { + max_offset = 0; + } else if (a->line_num < HTMainText->top_of_screen) { + int screensback = + (HTMainText->top_of_screen - a->line_num + (display_lines) - 1) + / (display_lines); + + max_offset = a->line_num - (HTMainText->top_of_screen - + screensback * (display_lines)); + } else if (HTMainText->Lines - a->line_num <= (display_lines)) { + max_offset = a->line_num - (HTMainText->Lines + 1 + - (display_lines)); + } else if (a->line_num >= + HTMainText->top_of_screen + (display_lines)) { + int screensahead = + (a->line_num - HTMainText->top_of_screen) / (display_lines); + + max_offset = a->line_num - HTMainText->top_of_screen - + screensahead * (display_lines); + } else { + max_offset = SEARCH_GOAL_LINE - 1; + } + + /* Stuff below should remain unchanged if line positioning + is tweaked. - kw */ + if (max_offset < 0) + max_offset = 0; + else if (max_offset >= display_lines) + max_offset = display_lines - 1; + *go_line = a->line_num - max_offset; + if (*go_line <= group_to_go->prev_anchor_line) + *go_line = group_to_go->prev_anchor_line + 1; + + if (*go_line < 0) + *go_line = 0; + if (linknum) + *linknum = group_to_go->anchors_this_line - 1; + return (LINK_LINE_FOUND); + } + } + return (NO); +} + +/* + * This function finds the line indicated by line_num in the + * HText structure indicated by text, and searches that line + * for the first hit with the string indicated by target. If + * there is no hit, FALSE is returned. If there is a hit, then + * a copy of the line starting at that first hit is loaded into + * *data with all IsSpecial characters stripped, its offset and + * the printable target length (without IsSpecial, or extra CJK + * or utf8 characters) are loaded into *offset and *tLen, and + * TRUE is returned. -FM + */ +BOOL HText_getFirstTargetInLine(HText *text, int line_num, + int utf_flag, + int *offset, + int *tLen, + char **data, + const char *target) +{ + HTLine *line; + char *LineData; + int LineOffset, HitOffset, LenNeeded, i; + const char *cp; + + /* + * Make sure we have an HText structure, that line_num is + * in its range, and that we have a target string. -FM + */ + if (!(text && + line_num >= 0 && + line_num <= text->Lines && + non_empty(target))) { + return (FALSE); + } + + /* + * Find the line and set up its data and offset -FM + */ + for (i = 0, line = FirstHTLine(text); + i < line_num && (line != text->last_line); + i++, line = line->next) { + if (line->next == NULL) { + return (FALSE); + } + } + if (!(line && line->data[0])) + return (FALSE); + LineData = (char *) line->data; + LineOffset = (int) line->offset; + + /* + * If the target is on the line, load the offset of + * its first character and the subsequent line data, + * strip any special characters from the loaded line + * data, and return TRUE. -FM + */ + if (((cp = LYno_attr_mb_strstr(LineData, + target, + utf_flag, YES, + &HitOffset, + &LenNeeded)) != NULL) && + (LineOffset + LenNeeded) <= DISPLAY_COLS) { + /* + * We had a hit so load the results, + * remove IsSpecial characters from + * the allocated data string, and + * return TRUE. -FM + */ + *offset = (LineOffset + HitOffset); + *tLen = (LenNeeded - HitOffset); + StrAllocCopy(*data, cp); + remove_special_attr_chars(*data); + return (TRUE); + } + + /* + * The line does not contain the target. -FM + */ + return (FALSE); +} + +/* + * HText_getNumOfLines returns the number of lines in the + * current document. + */ +int HText_getNumOfLines(void) +{ + return (HTMainText ? HTMainText->Lines : 0); +} + +/* + * HText_getNumOfBytes returns the size of the document, as rendered. This + * may be different from the original filesize. + */ +int HText_getNumOfBytes(void) +{ + int result = -1; + HTLine *line = NULL; + + if (HTMainText != 0) { + for (line = FirstHTLine(HTMainText); + line != HTMainText->last_line; + line = line->next) { + result += 1 + (int) strlen(line->data); + } + } + return result; +} + +/* + * HText_getTitle returns the title of the + * current document. + */ +const char *HText_getTitle(void) +{ + return (HTMainText ? + HTAnchor_title(HTMainText->node_anchor) : 0); +} + +#ifdef USE_COLOR_STYLE +const char *HText_getStyle(void) +{ + return (HTMainText ? + HTAnchor_style(HTMainText->node_anchor) : 0); +} +#endif + +/* + * HText_getSugFname returns the suggested filename of the current + * document (normally derived from a Content-Disposition header with + * attachment; filename=name.suffix). -FM + */ +const char *HText_getSugFname(void) +{ + return (HTMainText ? + HTAnchor_SugFname(HTMainText->node_anchor) : 0); +} + +/* + * HTCheckFnameForCompression receives the address of an allocated + * string containing a filename, and an anchor pointer, and expands + * or truncates the string's suffix if appropriate, based on whether + * the anchor indicates that the file is compressed. We assume + * that the file was not uncompressed (as when downloading), and + * believe the headers about whether it's compressed or not. -FM + * + * Added third arg - if strip_ok is FALSE, we don't trust the anchor + * info enough to remove a compression suffix if the anchor object + * does not indicate compression. - kw + */ +void HTCheckFnameForCompression(char **fname, + HTParentAnchor *anchor, + int strip_ok) +{ + char *fn = *fname; + char *dot = NULL; + char *cp = NULL; + const char *suffix; + CompressFileType method; + CompressFileType second; + + /* + * Make sure we have a string and anchor. -FM + */ + if (!(fn && anchor)) + return; + + /* + * Make sure we have a file, not directory, name. -FM + */ + if (*(fn = LYPathLeaf(fn)) == '\0') + return; + + method = HTContentToCompressType(anchor); + + /* + * If no Content-Encoding has been detected via the anchor + * pointer, but strip_ok is not set, there is nothing left + * to do. - kw + */ + if ((method == cftNone) && !strip_ok) + return; + + /* + * Treat .tgz specially + */ + if ((dot = strrchr(fn, '.')) != NULL + && !strcasecomp(dot, ".tgz")) { + if (method == cftNone) { + strcpy(dot, ".tar"); + } + return; + } + + /* + * Seek the last dot, and check whether + * we have a gzip or compress suffix. -FM + */ + if ((dot = strrchr(fn, '.')) != NULL) { + int rootlen = 0; + + if (HTCompressFileType(fn, ".", &rootlen) != cftNone) { + if (method == cftNone) { + /* + * It has a suffix which signifies a gzipped + * or compressed file for us, but the anchor + * claims otherwise, so tweak the suffix. -FM + */ + *dot = '\0'; + } + return; + } + if ((second = HTCompressFileType(fn, "-_", &rootlen)) != cftNone) { + cp = fn + rootlen; + if (method == cftNone) { + /* + * It has a tail which signifies a gzipped + * file for us, but the anchor claims otherwise, + * so tweak the suffix. -FM + */ + if (cp == dot + 1) + cp--; + *cp = '\0'; + } else { + /* + * The anchor claims it's gzipped, and we + * believe it, so force this tail to the + * conventional suffix. -FM + */ +#ifdef VMS + *cp = '-'; +#else + *cp = '.'; +#endif /* VMS */ + if (second == cftCompress) + LYUpperCase(cp); + else + LYLowerCase(cp); + } + return; + } + } + + suffix = HTCompressTypeToSuffix(method); + + /* + * Add the appropriate suffix. -FM + */ + if (*suffix) { + if (!dot) { + StrAllocCat(*fname, suffix); + } else if (*++dot == '\0') { + StrAllocCat(*fname, suffix + 1); + } else { + StrAllocCat(*fname, suffix); +#ifdef VMS + (*fname)[strlen(*fname) - strlen(suffix)] = '-'; +#endif /* !VMS */ + } + } +} + +/* + * HText_getLastModified returns the Last-Modified header + * if available, for the current document. -FM + */ +const char *HText_getLastModified(void) +{ + return (HTMainText ? + HTAnchor_last_modified(HTMainText->node_anchor) : 0); +} + +/* + * HText_getDate returns the Date header + * if available, for the current document. -FM + */ +const char *HText_getDate(void) +{ + return (HTMainText ? + HTAnchor_date(HTMainText->node_anchor) : 0); +} + +/* + * HText_getServer returns the Server header + * if available, for the current document. -FM + */ +const char *HText_getServer(void) +{ + return (HTMainText ? + HTAnchor_server(HTMainText->node_anchor) : 0); +} + +/* + * Returns the full text of HTTP headers, if available, for the current + * document. + */ +const char *HText_getHttpHeaders(void) +{ + return (HTMainText ? + HTAnchor_http_headers(HTMainText->node_anchor) : 0); +} + +/* + * HText_pageDisplay displays a screen of text + * starting from the line 'line_num'-1. + * This is the primary call for lynx. + */ +void HText_pageDisplay(int line_num, + char *target) +{ +#ifdef DISP_PARTIAL + if (debug_display_partial || (LYTraceLogFP != NULL)) { + CTRACE((tfp, "GridText: HText_pageDisplay at line %d started\n", line_num)); + } + + if (display_partial) { + int stop_before = -1; + + /* + * Garbage is reported from forms input fields in incremental mode. + * So we start HText_trimHightext() to forget this side effect. + * This function was split-out from HText_endAppend(). + * It may not be the best solution but it works. - LP + * + * (FALSE = indicate that we are in partial mode) + * Multiple calls of HText_trimHightext works without problem now. + */ + if (HTMainText && HTMainText->stbl) + stop_before = Stbl_getStartLineDeep(HTMainText->stbl); + HText_trimHightext(HTMainText, FALSE, stop_before); + } +#endif + display_page(HTMainText, line_num - 1, target); + +#ifdef DISP_PARTIAL + if (display_partial && debug_display_partial) + LYSleepMsg(); +#endif + + is_www_index = HTAnchor_isIndex(HTMainAnchor); + +#ifdef DISP_PARTIAL + if (debug_display_partial || (LYTraceLogFP != NULL)) { + CTRACE((tfp, "GridText: HText_pageDisplay finished\n")); + } +#endif +} + +/* + * Return YES if we have a whereis search target on the displayed + * page. - kw + */ +BOOL HText_pageHasPrevTarget(void) +{ + if (!HTMainText) + return NO; + else + return HTMainText->page_has_target; +} + +/* + * Find the number of the closest anchor to the given document offset. Used + * in reparsing, this will usually find an exact match, as a link shifts around + * on the display. It will not find a match when (for example) the source view + * shows images that are not links in the html. + */ +int HText_closestAnchor(HText *text, int offset) +{ + int result = -1; + int absdiff = 0; + int newdiff; + TextAnchor *Anchor_ptr = NULL; + TextAnchor *closest = NULL; + + for (Anchor_ptr = text->first_anchor; + Anchor_ptr != NULL; + Anchor_ptr = Anchor_ptr->next) { + if (Anchor_ptr->sgml_offset == offset) { + result = Anchor_ptr->number; + break; + } else { + newdiff = abs(Anchor_ptr->sgml_offset - offset); + if (absdiff == 0 || absdiff > newdiff) { + absdiff = newdiff; + closest = Anchor_ptr; + } + } + } + if (result < 0 && closest != 0) { + result = closest->number; + } + + return result; +} + +/* + * Find the offset for the given anchor, e.g., the inverse of + * HText_closestAnchor(). + */ +int HText_locateAnchor(HText *text, int anchor_number) +{ + int result = -1; + TextAnchor *Anchor_ptr = NULL; + + for (Anchor_ptr = text->first_anchor; + Anchor_ptr != NULL; + Anchor_ptr = Anchor_ptr->next) { + if (Anchor_ptr->number == anchor_number) { + result = Anchor_ptr->sgml_offset; + break; + } + } + + return result; +} + +/* + * This is supposed to give the same result as the inline checks in + * display_page(), so we can decide which anchors will be visible. + */ +static BOOL anchor_is_numbered(TextAnchor *Anchor_ptr) +{ + BOOL result = FALSE; + + if (Anchor_ptr->show_anchor + && (Anchor_ptr->link_type & HYPERTEXT_ANCHOR)) { + result = TRUE; + } else if (Anchor_ptr->link_type == INPUT_ANCHOR + && Anchor_ptr->input_field->type != F_HIDDEN_TYPE) { + result = TRUE; + } + return result; +} + +/* + * Return the absolute line number (counting from the beginning of the + * document) for the given absolute anchor number. Normally line numbers are + * computed within the screen, and for that we use the links[] array. A few + * uses require the absolute anchor number. For example, reparsing a document, + * e.g., switching between normal and source views will alter the line numbers + * of each link, and may require adjusting the top line number used for the + * display, before we recompute the links[] array. + */ +int HText_getAbsLineNumber(HText *text, + int anchor_number) +{ + int result = -1; + + if (anchor_number >= 0 && text != 0) { + TextAnchor *Anchor_ptr = NULL; + + for (Anchor_ptr = text->first_anchor; + Anchor_ptr != NULL; + Anchor_ptr = Anchor_ptr->next) { + if (anchor_is_numbered(Anchor_ptr) + && Anchor_ptr->number == anchor_number) { + result = Anchor_ptr->line_num; + break; + } + } + } + return result; +} + +/* + * Compute the link-number in a page, given the top line number of the page and + * the absolute anchor number. + */ +int HText_anchorRelativeTo(HText *text, int top_lineno, int anchor_number) +{ + int result = 0; + int from_top = 0; + TextAnchor *Anchor_ptr = NULL; + + for (Anchor_ptr = text->first_anchor; + Anchor_ptr != NULL; + Anchor_ptr = Anchor_ptr->next) { + if (Anchor_ptr->number == anchor_number) { + result = from_top; + break; + } + if (!anchor_is_numbered(Anchor_ptr)) + continue; + if (Anchor_ptr->line_num >= top_lineno) { + ++from_top; + } + } + return result; +} + +/* + * HText_LinksInLines returns the number of links in the + * 'Lines' number of lines beginning with 'line_num'-1. -FM + */ +int HText_LinksInLines(HText *text, + int line_num, + int Lines) +{ + int total = 0; + int start = (line_num - 1); + int end = (start + Lines); + TextAnchor *Anchor_ptr = NULL; + + if (!text) + return total; + + for (Anchor_ptr = text->first_anchor; + Anchor_ptr != NULL && Anchor_ptr->line_num <= end; + Anchor_ptr = Anchor_ptr->next) { + if (Anchor_ptr->line_num >= start && + Anchor_ptr->line_num < end && + Anchor_ptr->show_anchor && + !(Anchor_ptr->link_type == INPUT_ANCHOR + && Anchor_ptr->input_field->type == F_HIDDEN_TYPE)) + ++total; + } + + return total; +} + +void HText_setStale(HText *text) +{ + text->stale = YES; +} + +void HText_refresh(HText *text) +{ + if (text->stale) + display_page(text, text->top_of_screen, ""); +} + +int HText_sourceAnchors(HText *text) +{ + return (text ? text->last_anchor_number : -1); +} + +BOOL HText_canScrollUp(HText *text) +{ + return (BOOL) (text->top_of_screen != 0); +} + +/* + * Check if there is more info below this page. + */ +BOOL HText_canScrollDown(void) +{ + HText *text = HTMainText; + + return (BOOL) ((text != 0) + && ((text->top_of_screen + display_lines) <= text->Lines)); +} + +/* Scroll actions +*/ +void HText_scrollTop(HText *text) +{ + display_page(text, 0, ""); +} + +void HText_scrollDown(HText *text) +{ + display_page(text, text->top_of_screen + display_lines, ""); +} + +void HText_scrollUp(HText *text) +{ + display_page(text, text->top_of_screen - display_lines, ""); +} + +void HText_scrollBottom(HText *text) +{ + display_page(text, text->Lines - display_lines, ""); +} + +/* Browsing functions + * ================== + */ + +/* Bring to front and highlight it +*/ +BOOL HText_select(HText *text) +{ + if (text != HTMainText) { + /* + * Reset flag for whereis search string - cannot be true here + * since text is not our HTMainText. - kw + */ + if (text) + text->page_has_target = NO; + +#ifdef DISP_PARTIAL + /* Reset these for the previous and current text. - kw */ + ResetPartialLinenos(text); + ResetPartialLinenos(HTMainText); +#endif /* DISP_PARTIAL */ + +#ifdef CAN_SWITCH_DISPLAY_CHARSET + /* text->UCLYhndl is not reset by META, so use a more circumvent way */ + if (text->node_anchor->UCStages->s[UCT_STAGE_HTEXT].LYhndl + != current_char_set) + Switch_Display_Charset(text->node_anchor->UCStages->s[UCT_STAGE_HTEXT].LYhndl, SWITCH_DISPLAY_CHARSET_MAYBE); +#endif + assert(text != NULL); + if (HTMainText) { + if (HText_hasUTF8OutputSet(HTMainText) && + HTLoadedDocumentEightbit() && + IS_UTF8_TTY) { + text->had_utf8 = HTMainText->has_utf8; + } else { + text->had_utf8 = NO; + } + HTMainText->has_utf8 = NO; + text->has_utf8 = NO; + } + + HTMainText = text; + HTMainAnchor = text->node_anchor; + + /* + * Make this text the most current in the loaded texts list. -FM + */ + if (loaded_texts && HTList_removeObject(loaded_texts, text)) + HTList_addObject(loaded_texts, text); + } + return YES; +} + +/* + * This function returns TRUE if doc's post_data, address + * and isHEAD elements are identical to those of a loaded + * (memory cached) text. -FM + */ +BOOL HText_POSTReplyLoaded(DocInfo *doc) +{ + HText *text = NULL; + HTList *cur = loaded_texts; + bstring *post_data; + char *address; + BOOL is_head; + + /* + * Make sure we have the structures. -FM + */ + if (!cur || !doc) + return (FALSE); + + /* + * Make sure doc is for a POST reply. -FM + */ + if ((post_data = doc->post_data) == NULL || + (address = doc->address) == NULL) + return (FALSE); + is_head = doc->isHEAD; + + /* + * Loop through the loaded texts looking for a + * POST reply match. -FM + */ + while (NULL != (text = (HText *) HTList_nextObject(cur))) { + if (text->node_anchor && + text->node_anchor->post_data && + BINEQ(post_data, text->node_anchor->post_data) && + text->node_anchor->address && + !strcmp(address, text->node_anchor->address) && + is_head == text->node_anchor->isHEAD) { + return (TRUE); + } + } + + return (FALSE); +} + +BOOL HTFindPoundSelector(const char *selector) +{ + TextAnchor *a; + + CTRACE((tfp, "FindPound: searching for \"%s\"\n", selector)); + for (a = HTMainText->first_anchor; a != 0; a = a->next) { + + if (a->anchor && a->anchor->tag) { + if (!strcmp(a->anchor->tag, selector)) { + + www_search_result = a->line_num + 1; + + CTRACE((tfp, "FindPound: Selecting anchor [%d] at line %d\n", + a->number, www_search_result)); + if (!strcmp(selector, LYToolbarName)) { + --www_search_result; + } + return (YES); + } + } + } + return (NO); +} + +BOOL HText_selectAnchor(HText *text, HTChildAnchor *anchor) +{ + TextAnchor *a; + int l; + + for (a = text->first_anchor; a; a = a->next) { + if (a->anchor == anchor) + break; + } + if (!a) { + CTRACE((tfp, "HText: No such anchor in this text!\n")); + return NO; + } + + if (text != HTMainText) { /* Comment out by ??? */ + HTMainText = text; /* Put back in by tbl 921208 */ + HTMainAnchor = text->node_anchor; + } + l = a->line_num; + + CTRACE((tfp, "HText: Selecting anchor [%d] at line %d\n", + a->number, l)); + + if (!text->stale && + (l >= text->top_of_screen) && + (l < text->top_of_screen + display_lines + 1)) + return YES; + + www_search_result = l - (display_lines / 3); /* put in global variable */ + + return YES; +} + +/* Editing functions - NOT IMPLEMENTED + * ================= + * + * These are called from the application. There are many more functions + * not included here from the original text object. + */ + +/* Style handling: +*/ +/* Apply this style to the selection +*/ +void HText_applyStyle(HText *me GCC_UNUSED, HTStyle *style GCC_UNUSED) +{ + +} + +/* Update all text with changed style. +*/ +void HText_updateStyle(HText *me GCC_UNUSED, HTStyle *style GCC_UNUSED) +{ + +} + +/* Return style of selection +*/ +HTStyle *HText_selectionStyle(HText *me GCC_UNUSED, HTStyleSheet *sheet GCC_UNUSED) +{ + return 0; +} + +/* Paste in styled text +*/ +void HText_replaceSel(HText *me GCC_UNUSED, const char *aString GCC_UNUSED, + HTStyle *aStyle GCC_UNUSED) +{ +} + +/* Apply this style to the selection and all similarly formatted text + * (style recovery only) + */ +void HTextApplyToSimilar(HText *me GCC_UNUSED, HTStyle *style GCC_UNUSED) +{ + +} + +/* Select the first unstyled run. + * (style recovery only) + */ +void HTextSelectUnstyled(HText *me GCC_UNUSED, HTStyleSheet *sheet GCC_UNUSED) +{ + +} + +/* Anchor handling: +*/ +void HText_unlinkSelection(HText *me GCC_UNUSED) +{ + +} + +HTAnchor *HText_referenceSelected(HText *me GCC_UNUSED) +{ + return 0; +} + +int HText_getTopOfScreen(void) +{ + HText *text = HTMainText; + + return text != 0 ? text->top_of_screen : 0; +} + +int HText_getLines(HText *text) +{ + return text->Lines; +} + +/* + * Constrain the line number to be within the document. The line number is + * zero-based. + */ +int HText_getPreferredTopLine(HText *text, int line_number) +{ + int last_screen = text->Lines - (display_lines - 2); + + if (text->Lines < display_lines) { + line_number = 0; + } else if (line_number > text->Lines) { + line_number = last_screen; + } else if (line_number < 0) { + line_number = 0; + } + return line_number; +} + +HTAnchor *HText_linkSelTo(HText *me GCC_UNUSED, + HTAnchor * anchor GCC_UNUSED) +{ + return 0; +} + +/* + * Utility for freeing the list of previous isindex and whereis queries. -FM + */ +void HTSearchQueries_free(void) +{ + LYFreeStringList(search_queries); + search_queries = NULL; +} + +/* + * Utility for listing isindex and whereis queries, making + * any repeated queries the most current in the list. -FM + */ +void HTAddSearchQuery(char *query) +{ + char *new_query = NULL; + char *old; + HTList *cur; + + if (!non_empty(query)) + return; + + StrAllocCopy(new_query, query); + + if (!search_queries) { + search_queries = HTList_new(); +#ifdef LY_FIND_LEAKS + atexit(HTSearchQueries_free); +#endif + HTList_addObject(search_queries, new_query); + return; + } + + cur = search_queries; + while (NULL != (old = (char *) HTList_nextObject(cur))) { + if (!strcmp(old, new_query)) { + HTList_removeObject(search_queries, old); + FREE(old); + break; + } + } + HTList_addObject(search_queries, new_query); + + return; +} + +int do_www_search(DocInfo *doc) +{ + bstring *searchstring = NULL; + bstring *temp = NULL; + char *cp; + char *tmpaddress = NULL; + int ch; + RecallType recall; + int QueryTotal; + int QueryNum; + BOOLEAN PreviousSearch = FALSE; + int code; + + /* + * Load the default query buffer + */ + if ((cp = StrChr(doc->address, '?')) != NULL) { + /* + * This is an index from a previous search. + * Use its query as the default. + */ + PreviousSearch = TRUE; + BStrCopy0(searchstring, ++cp); + for (cp = searchstring->str; *cp; cp++) + if (*cp == '+') + *cp = ' '; + HTUnEscape(searchstring->str); + BStrCopy(temp, searchstring); + /* + * Make sure it's treated as the most recent query. -FM + */ + HTAddSearchQuery(searchstring->str); + } else { + /* + * New search; no default. + */ + BStrCopy0(searchstring, ""); + BStrCopy0(temp, ""); + } + + /* + * Prompt for a query string. + */ + if (isBEmpty(searchstring)) { + if (HTMainAnchor->isIndexPrompt) + _statusline(HTMainAnchor->isIndexPrompt); + else + _statusline(ENTER_DATABASE_QUERY); + } else + _statusline(EDIT_CURRENT_QUERY); + QueryTotal = (search_queries ? HTList_count(search_queries) : 0); + recall = (((PreviousSearch && QueryTotal >= 2) || + (!PreviousSearch && QueryTotal >= 1)) ? RECALL_URL : NORECALL); + QueryNum = QueryTotal; + + get_query: + if ((ch = LYgetBString(&searchstring, FALSE, 0, recall)) < 0 || + isBEmpty(searchstring) || + ch == UPARROW_KEY || + ch == DNARROW_KEY) { + + if (recall && ch == UPARROW_KEY) { + if (PreviousSearch) { + /* + * Use the second to last query in the list. -FM + */ + QueryNum = 1; + PreviousSearch = FALSE; + } else { + /* + * Go back to the previous query in the list. -FM + */ + QueryNum++; + } + if (QueryNum >= QueryTotal) + /* + * Roll around to the last query in the list. -FM + */ + QueryNum = 0; + if ((cp = (char *) HTList_objectAt(search_queries, + QueryNum)) != NULL) { + BStrCopy0(searchstring, cp); + if (!isBEmpty(temp) && + !strcmp(temp->str, searchstring->str)) { + _statusline(EDIT_CURRENT_QUERY); + } else if ((!isBEmpty(temp) && QueryTotal == 2) || + (isBEmpty(temp) && QueryTotal == 1)) { + _statusline(EDIT_THE_PREV_QUERY); + } else { + _statusline(EDIT_A_PREV_QUERY); + } + goto get_query; + } + } else if (recall && ch == DNARROW_KEY) { + if (PreviousSearch) { + /* + * Use the first query in the list. -FM + */ + QueryNum = QueryTotal - 1; + PreviousSearch = FALSE; + } else { + /* + * Advance to the next query in the list. -FM + */ + QueryNum--; + } + if (QueryNum < 0) + /* + * Roll around to the first query in the list. -FM + */ + QueryNum = QueryTotal - 1; + if ((cp = (char *) HTList_objectAt(search_queries, + QueryNum)) != NULL) { + BStrCopy0(searchstring, cp); + if (!isBEmpty(temp) && + !strcmp(temp->str, searchstring->str)) { + _statusline(EDIT_CURRENT_QUERY); + } else if ((!isBEmpty(temp) && QueryTotal == 2) || + (isBEmpty(temp) && QueryTotal == 1)) { + _statusline(EDIT_THE_PREV_QUERY); + } else { + _statusline(EDIT_A_PREV_QUERY); + } + goto get_query; + } + } + + /* + * Search cancelled. + */ + HTInfoMsg(CANCELLED); + code = NULLFILE; + } else { + + LYTrimLeading(searchstring->str); + LYTrimTrailing(searchstring->str); + if (isBEmpty(searchstring)) { + HTInfoMsg(CANCELLED); + code = NULLFILE; + } else if (!LYforce_no_cache && + !isBEmpty(temp) && + !strcmp(temp->str, searchstring->str)) { + /* + * Don't resubmit the same query unintentionally. + */ + HTUserMsg(USE_C_R_TO_RESUB_CUR_QUERY); + code = NULLFILE; + } else { + + /* + * Add searchstring to the query list, + * or make it the most current. -FM + */ + HTAddSearchQuery(searchstring->str); + + /* + * Show the URL with the new query. + */ + if ((cp = StrChr(doc->address, '?')) != NULL) + *cp = '\0'; + StrAllocCopy(tmpaddress, doc->address); + StrAllocCat(tmpaddress, "?"); + StrAllocCat(tmpaddress, searchstring->str); + user_message(WWW_WAIT_MESSAGE, tmpaddress); +#ifdef SYSLOG_REQUESTED_URLS + LYSyslog(tmpaddress); +#endif + FREE(tmpaddress); + if (cp) + *cp = '?'; + + /* + * OK, now we do the search. + */ + if (HTSearch(searchstring->str, HTMainAnchor)) { + auto char *cp_freeme = NULL; + + if (traversal) + cp_freeme = stub_HTAnchor_address((HTAnchor *) HTMainAnchor); + else + cp_freeme = HTAnchor_address((HTAnchor *) HTMainAnchor); + StrAllocCopy(doc->address, cp_freeme); + FREE(cp_freeme); + + CTRACE((tfp, "\ndo_www_search: newfile: %s\n", doc->address)); + + /* + * Yah, the search succeeded. + */ + code = NORMAL; + } else { + + /* + * Either the search failed (Yuk), or we got redirection. + * If it's redirection, use_this_url_instead is set, and + * mainloop() will deal with it such that security features + * and restrictions are checked before acting on the URL, or + * rejecting it. -FM + */ + code = NOT_FOUND; + } + } + } + BStrFree(searchstring); + BStrFree(temp); + return code; +} + +static void write_offset(FILE *fp, HTLine *line) +{ + int i; + + if (line->data[0]) { + for (i = 0; i < (int) line->offset; i++) { + fputc(' ', fp); + } + } +} + +static void write_hyphen(FILE *fp) +{ + if (dump_output_immediately && + LYRawMode && + LYlowest_eightbit[current_char_set] <= 173 && + (LYCharSet_UC[current_char_set].enc == UCT_ENC_8859 || + (LYCharSet_UC[current_char_set].like8859 & UCT_R_8859SPECL)) != 0) { + fputc(0xad, fp); /* the iso8859 byte for SHY */ + } else { + fputc('-', fp); + } +} + +/* + * Returns the length after trimming trailing blanks. Modify the string as + * needed so that any special character which follows a trailing blank is moved + * before the (trimmed) blank, so the result which will be dumped has no + * trailing blanks. + */ +static int TrimmedLength(char *string) +{ + int result = (int) strlen(string); + + if (!HTisDocumentSource()) { + int adjust = result; + int ch; + + while (adjust > 0) { + ch = UCH(string[adjust - 1]); + if (isspace(ch) || IsSpecialAttrChar(ch)) { + --adjust; + } else { + break; + } + } + if (result != adjust) { + char *dst = string + adjust; + char *src = dst; + + for (;;) { + src = LYSkipBlanks(src); + if ((*dst++ = *src++) == '\0') + break; + } + result = (int) (dst - string - 1); + } + } + return result; +} + +typedef struct _AnchorIndex { + struct _AnchorIndex *next; + int type; /* field type */ + int size; /* character-width of field */ + int length; /* byte-count for field's data */ + int offset; /* byte-offset in line's data */ + char filler; /* character to use for filler */ + const char *value; /* field's value */ +} AnchorIndex; + +static unsigned countHTLines(void) +{ + unsigned result = 0; + HTLine *line = FirstHTLine(HTMainText); + + while (line != 0) { + ++result; + if (line == HTMainText->last_line) + break; + line = line->next; + } + CTRACE((tfp, "countHTLines %u\n", result)); + return result; +} + +/* + * The TextAnchor list is not organized to allow efficient dumping of a page. + * Make an array with one item per line of the page, and store (by byte-offset) + * pointers to the TextAnchor's we want to use. + */ +static AnchorIndex **allocAnchorIndex(unsigned *size) +{ + AnchorIndex **result = NULL; + AnchorIndex *p, *q; + TextAnchor *anchor = NULL; + FormInfo *input = NULL; + + *size = countHTLines(); + if (*size != 0) { + result = typecallocn(AnchorIndex *, *size + 1); + if (result == NULL) + outofmem(__FILE__, "allocAnchorIndex"); + + for (anchor = HTMainText->first_anchor; + anchor != NULL; + anchor = anchor->next) { + + if (anchor->link_type == INPUT_ANCHOR + && anchor->show_anchor + && anchor->line_num < (int) *size + && (input = anchor->input_field) != NULL) { + CTRACE2(TRACE_GRIDTEXT, + (tfp, "line %d.%d %d %s->%s(%s)\n", + anchor->line_num, + anchor->line_pos, + input->size, + inputFieldDesc(input), + input->value, + input->orig_value)); + switch (input->type) { + case F_SUBMIT_TYPE: + case F_RESET_TYPE: + case F_TEXT_SUBMIT_TYPE: + case F_IMAGE_SUBMIT_TYPE: + CTRACE2(TRACE_GRIDTEXT, (tfp, "skipping\n")); + continue; + case F_TEXT_TYPE: + case F_PASSWORD_TYPE: + case F_CHECKBOX_TYPE: + case F_RADIO_TYPE: + case F_OPTION_LIST_TYPE: + case F_TEXTAREA_TYPE: + case F_RANGE_TYPE: + case F_FILE_TYPE: + p = typecalloc(AnchorIndex); + if (p == NULL) + outofmem(__FILE__, "allocAnchorIndex"); + + p->type = input->type; + p->size = input->size; + p->offset = anchor->line_pos; + p->value = input->value; + + switch (input->type) { + case F_TEXTAREA_TYPE: + case F_TEXT_TYPE: + case F_PASSWORD_TYPE: + p->filler = '_'; + break; + case F_OPTION_LIST_TYPE: + p->filler = '_'; + break; + case F_CHECKBOX_TYPE: + p->value = (input->num_value + ? checked_box + : unchecked_box); + break; + case F_RADIO_TYPE: + p->value = (input->num_value + ? checked_radio + : unchecked_radio); + break; + default: + p->filler = ' '; + break; + } + p->length = p->value ? (int) strlen(p->value) : 0; + + if ((q = result[anchor->line_num]) != NULL) { + /* insert, ordering by offset */ + if (q->offset < p->offset) { + while (q->next != NULL + && q->next->offset < p->offset) { + q = q->next; + } + p->next = q->next; + q->next = p; + } else { + p->next = q; + result[anchor->line_num] = p; + } + } else { + result[anchor->line_num] = p; + } + break; + } + } + } + } + return result; +} + +/* + * Free the data allocated in allocAnchorIndex(). + */ +static void freeAnchorIndex(AnchorIndex ** inx, unsigned inx_size) +{ + AnchorIndex *cur; + unsigned num; + + if (inx != 0) { + if (inx_size != 0) { + for (num = 0; num < inx_size; ++num) { + while ((cur = inx[num]) != NULL) { + inx[num] = cur->next; + free(cur); + } + } + } + free(inx); + } +} + +/* + * Return the column (counting from zero) at which a field should be overlaid + * on the form. + */ +static int FieldFirst(AnchorIndex * p, int wrap) +{ + return (wrap ? 0 : (p)->offset); +} + +/* + * Return the column (counting from zero) just past the field in a form. + */ +static int FieldLast(AnchorIndex * p, int wrap) +{ + return ((p)->size - wrap) + FieldFirst(p, wrap); +} + +/* + * Print the contents of the file in HTMainText to + * the file descriptor fp. + * If is_email is TRUE add ">" before each "From " line. + * If is_reply is TRUE add ">" to the beginning of each + * line to specify the file is a reply to message. + */ +void print_wwwfile_to_fd(FILE *fp, + int is_email, + int is_reply) +{ + int line_num, byte_num, byte_count, byte_offset; + int first = TRUE; + HTLine *line; + AnchorIndex **inx; /* sorted index of input-fields */ + AnchorIndex *cur = 0; /* current input-field */ + unsigned inx_size; /* number of entries in inx[] */ + int in_field = -1; /* if positive, is index in cur->value[] */ + int this_wrap = 0; /* current wrapping point of cur->value[] */ + int next_wrap = 0; /* next wrapping point of cur->value[] */ + +#ifndef NO_DUMP_WITH_BACKSPACES + HText *text = HTMainText; + BOOL in_b = FALSE; + BOOL in_u = FALSE; + BOOL bs = (BOOL) (!is_email && !is_reply + && text != 0 + && with_backspaces + && !IS_CJK_TTY + && !text->T.output_utf8); +#endif + + if (!HTMainText) + return; + + /* + * Build an index of anchors for each line, so we can override the + * static text which is stored in the list of HTLine's. + */ + inx = allocAnchorIndex(&inx_size); + + line = FirstHTLine(HTMainText); + for (line_num = 0; line != NULL; ++line_num, line = line->next) { + if (in_field >= 0) { + this_wrap = next_wrap; + next_wrap = 0; /* FIXME - allow for multiple continuations */ + CTRACE2(TRACE_GRIDTEXT, + (tfp, "wrap %d:%d, offset %d\n", + in_field, cur ? cur->length : -1, this_wrap)); + } else { + cur = inx[line_num]; + } + + CTRACE2(TRACE_GRIDTEXT, (tfp, "dump %d:%s\n", line_num, line->data)); + + if (first) { + first = FALSE; + if (is_reply) { + fputc('>', fp); + } else if (is_email && !StrNCmp(line->data, "From ", 5)) { + fputc('>', fp); + } + } else if (line->data[0] != LY_SOFT_NEWLINE) { + fputc('\n', fp); + /* + * Add news-style quotation if requested. -FM + */ + if (is_reply) { + fputc('>', fp); + } else if (is_email && !StrNCmp(line->data, "From ", 5)) { + fputc('>', fp); + } + } + + write_offset(fp, line); + + /* + * Add data. + */ + byte_offset = line->offset; + byte_count = TrimmedLength(line->data); + for (byte_num = 0; byte_num < byte_count; byte_num += 1) { + int cell_chr, temp_chr; + size_t cell_len, temp_len; + const char *cell_ptr, *temp_ptr, *try_utf8; + + cell_ptr = &line->data[byte_num]; + cell_len = 1; + cell_chr = UCH(*cell_ptr); + + while (cur != 0 && FieldLast(cur, this_wrap) < byte_offset) { + CTRACE2(TRACE_GRIDTEXT, + (tfp, "skip field since last %d < %d\n", + FieldLast(cur, this_wrap), byte_offset)); + cur = cur->next; + in_field = -1; + } + if (cur != 0 && in_field >= 0) { + CTRACE2(TRACE_GRIDTEXT, + (tfp, "compare %d to [%d..%d]\n", + byte_offset, + FieldFirst(cur, this_wrap), + FieldLast(cur, this_wrap) - 1)); + } + if (cur != 0 + && FieldFirst(cur, this_wrap) <= byte_offset + && FieldLast(cur, this_wrap) > byte_offset) { + int off2 = ((in_field > 0) + ? in_field + : (byte_offset - FieldFirst(cur, this_wrap))); + + /* + * On the first time (for each line that the field appears on), + * check if this field wraps. If it does, save the offset into + * the field which will be used to adjust the beginning of the + * continuation line. + */ + if (byte_offset == FieldFirst(cur, this_wrap)) { + next_wrap = 0; + if (cur->size - this_wrap + byte_num > byte_count) { + CTRACE((tfp, "size %d, offset %d, length %d\n", + cur->size, + cur->offset, + cur->length)); + CTRACE((tfp, "byte_count %d, byte_num %d\n", + byte_count, byte_num)); + next_wrap = byte_count - byte_num; + CTRACE2(TRACE_GRIDTEXT, + (tfp, "field will wrap: %d\n", next_wrap)); + } + } + + if (off2 >= 0 && off2 < cur->length) { + temp_ptr = &(cur->value[off2]); + try_utf8 = temp_ptr; + temp_chr = (int) UCGetUniFromUtf8String(&try_utf8); + if (temp_chr > 127) { + temp_len = (size_t) (try_utf8 - temp_ptr) + 1; + } else { + temp_chr = UCH(*temp_ptr); + temp_len = 1; + } + } else { + temp_ptr = &(cur->filler); + temp_len = 1; + temp_chr = UCH(*temp_ptr); + } + + if (cell_chr != temp_chr) { + CTRACE2(TRACE_GRIDTEXT, + (tfp, "line %d %d/%d [%d..%d] map %d %04X->%04X\n", + line_num, + off2, cur->length, + FieldFirst(cur, this_wrap), + FieldLast(cur, this_wrap) - 1, + byte_offset, + (unsigned) cell_chr, + (unsigned) temp_chr)); + cell_chr = temp_chr; + cell_ptr = temp_ptr; + cell_len = temp_len; + } + off2 += (int) temp_len; + byte_offset += (int) temp_len; + if ((off2 >= cur->size) && + (off2 >= cur->length || F_TEXTLIKE(cur->type))) { + in_field = -1; + this_wrap = 0; + next_wrap = 0; + } else { + in_field = off2; + } + } else { + byte_offset++; + } + + if (!IsSpecialAttrChar(cell_chr)) { +#ifndef NO_DUMP_WITH_BACKSPACES + size_t n; + + if (in_b) { + IGNORE_RC(fwrite(cell_ptr, sizeof(char), cell_len, fp)); + + for (n = 0; n < cell_len; ++n) { + fputc('\b', fp); + } + IGNORE_RC(fwrite(cell_ptr, sizeof(char), cell_len, fp)); + } else if (in_u) { + for (n = 0; n < cell_len; ++n) { + fputc('_', fp); + } + for (n = 0; n < cell_len; ++n) { + fputc('\b', fp); + } + IGNORE_RC(fwrite(cell_ptr, sizeof(char), cell_len, fp)); + } else +#endif + IGNORE_RC(fwrite(cell_ptr, sizeof(char), cell_len, fp)); + } else if (cell_chr == LY_SOFT_HYPHEN && + (byte_num + 1) >= byte_count) { + write_hyphen(fp); + } else if (dump_output_immediately && use_underscore) { + switch (cell_chr) { + case LY_UNDERLINE_START_CHAR: + case LY_UNDERLINE_END_CHAR: + fputc('_', fp); + break; + case LY_BOLD_START_CHAR: + case LY_BOLD_END_CHAR: + break; + } + } +#ifndef NO_DUMP_WITH_BACKSPACES + else if (bs) { + switch (cell_chr) { + case LY_UNDERLINE_START_CHAR: + if (!in_b) + in_u = TRUE; /*favor bold over underline */ + break; + case LY_UNDERLINE_END_CHAR: + in_u = FALSE; + break; + case LY_BOLD_START_CHAR: + if (in_u) + in_u = FALSE; /* turn it off */ + in_b = TRUE; + break; + case LY_BOLD_END_CHAR: + in_b = FALSE; + break; + } + } +#endif + } + + if (line == HTMainText->last_line) + break; + +#ifdef VMS + if (HadVMSInterrupt) + break; +#endif /* VMS */ + } + fputc('\n', fp); + + freeAnchorIndex(inx, inx_size); +} + +/* + * Print the contents of the file in HTMainText to + * the file descriptor fp. + * First output line is "thelink", ie, the URL for this file. + */ +void print_crawl_to_fd(FILE *fp, char *thelink, + char *thetitle) +{ + register int i; + int first = TRUE; + int limit; + HTLine *line; + + if (!HTMainText) + return; + + line = FirstHTLine(HTMainText); + fprintf(fp, "THE_URL:%s\n", thelink); + if (thetitle != NULL) { + fprintf(fp, "THE_TITLE:%s\n", thetitle); + } + + for (;; line = line->next) { + if (!first && line->data[0] != LY_SOFT_NEWLINE) + fputc('\n', fp); + first = FALSE; + write_offset(fp, line); + + /* + * Add data. + */ + limit = TrimmedLength(line->data); + for (i = 0; i < limit; i++) { + int ch = UCH(line->data[i]); + + if (!IsSpecialAttrChar(ch)) { + fputc(ch, fp); + } else if (ch == LY_SOFT_HYPHEN && + (i + 1) >= limit) { /* last char on line */ + write_hyphen(fp); + } + } + + if (!HTMainText || (line == HTMainText->last_line)) { + break; + } + } + fputc('\n', fp); + + /* + * Add the References list if appropriate + */ + if ((no_list == FALSE) && + (dump_links_inline == FALSE) && + links_are_numbered()) { + printlist(fp, FALSE); + } +#ifdef VMS + HadVMSInterrupt = FALSE; +#endif /* VMS */ +} + +static void adjust_search_result(DocInfo *doc, int tentative_result, + int start_line) +{ + if (tentative_result > 0) { + int anch_line = -1; + TextAnchor *a; + int nl_closest = -1; + int goal = SEARCH_GOAL_LINE; + int max_offset; + BOOL on_screen = (BOOL) (tentative_result > HTMainText->top_of_screen && + tentative_result <= HTMainText->top_of_screen + + display_lines); + + if (goal < 1) + goal = 1; + else if (goal > display_lines) + goal = display_lines; + max_offset = goal - 1; + + if (on_screen && nlinks > 0) { + int i; + + for (i = 0; i < nlinks; i++) { + if (doc->line + links[i].ly - 1 <= tentative_result) + nl_closest = i; + if (doc->line + links[i].ly - 1 >= tentative_result) + break; + } + if (nl_closest >= 0 && + doc->line + links[nl_closest].ly - 1 == tentative_result) { + www_search_result = doc->line; + doc->link = nl_closest; + return; + } + } + + /* find last anchor before or on target line */ + for (a = HTMainText->first_anchor; + a && a->line_num <= tentative_result - 1; a = a->next) { + anch_line = a->line_num + 1; + } + /* position such that the anchor found is on first screen line, + if it is not too far above the target line; but also try to + make sure we move forward. */ + if (anch_line >= 0 && + anch_line >= tentative_result - max_offset && + (anch_line > start_line || + tentative_result <= HTMainText->top_of_screen)) { + www_search_result = anch_line; + } else if (tentative_result - start_line > 0 && + tentative_result - (start_line + 1) <= max_offset) { + www_search_result = start_line + 1; + } else if (tentative_result > HTMainText->top_of_screen && + tentative_result <= start_line && /* have wrapped */ + tentative_result <= HTMainText->top_of_screen + goal) { + www_search_result = HTMainText->top_of_screen + 1; + } else if (tentative_result <= goal) + www_search_result = 1; + else + www_search_result = tentative_result - max_offset; + if (www_search_result == doc->line) { + if (nl_closest >= 0) { + doc->link = nl_closest; + return; + } + } + } +} + +/* + * see also link_has_target + */ +static BOOL anchor_has_target(TextAnchor *a, char *target) +{ + char *text = NULL; + const char *last = "?"; + int count; + + /* + * Combine the parts of the link's text using the highlighting information, + * and compare the target against that. + */ + for (count = 0; count < 10; ++count) { + const char *part = LYGetHiTextStr(a, count); + + if (part == NULL || part == last) { + if (text != NULL && LYno_attr_strstr(text, target)) { + return TRUE; + } + break; + } + StrAllocCat(text, part); + last = part; + } + + return field_has_target(a->input_field, target); +} + +static TextAnchor *line_num_to_anchor(int line_num) +{ + TextAnchor *a; + + if (HTMainText != 0) { + a = HTMainText->first_anchor; + while (a != 0 && a->line_num < line_num) { + a = a->next; + } + } else { + a = 0; + } + return a; +} + +static int line_num_in_text(HText *text, HTLine *line) +{ + int result = 1; + HTLine *temp = FirstHTLine(text); + + while (temp != line) { + temp = temp->next; + ++result; + } + return result; +} + +/* Computes the 'prev' pointers on demand, and returns the one for the given + * anchor. + */ +static TextAnchor *get_prev_anchor(TextAnchor *a) +{ + TextAnchor *p, *q; + + if (a->prev == 0) { + if ((p = HTMainText->first_anchor) != 0) { + while ((q = p->next) != 0) { + q->prev = p; + p = q; + } + } + } + return a->prev; +} + +static int www_search_forward(int start_line, + DocInfo *doc, + char *target, + HTLine *line, + int count) +{ + int wrapped = 0; + TextAnchor *a = line_num_to_anchor(count - 1); + int tentative_result = -1; + + for (;;) { + while ((a != NULL) && a->line_num == (count - 1)) { + if (a->show_anchor && + !(a->link_type == INPUT_ANCHOR + && a->input_field->type == F_HIDDEN_TYPE)) { + if (anchor_has_target(a, target)) { + adjust_search_result(doc, count, start_line); + return 1; + } + } + a = a->next; + } + + if (LYno_attr_strstr(line->data, target)) { + tentative_result = count; + break; + } else if ((count == start_line && wrapped) || wrapped > 1) { + HTUserMsg2(STRING_NOT_FOUND, target); + return -1; + } else if (line == HTMainText->last_line) { + count = 0; + wrapped++; + a = HTMainText->first_anchor; + } + line = line->next; + count++; + } + if (tentative_result > 0) { + adjust_search_result(doc, tentative_result, start_line); + } + return 0; +} + +static int www_search_backward(int start_line, + DocInfo *doc, + char *target, + HTLine *line, + int count) +{ + int wrapped = 0; + TextAnchor *a = line_num_to_anchor(count - 1); + int tentative_result = -1; + + for (;;) { + while ((a != NULL) && a->line_num == (count - 1)) { + if (a->show_anchor && + !(a->link_type == INPUT_ANCHOR + && a->input_field->type == F_HIDDEN_TYPE)) { + if (anchor_has_target(a, target)) { + adjust_search_result(doc, count, start_line); + return 1; + } + } + a = get_prev_anchor(a); + } + + if (LYno_attr_strstr(line->data, target)) { + tentative_result = count; + break; + } else if ((count == start_line && wrapped) || wrapped > 1) { + HTUserMsg2(STRING_NOT_FOUND, target); + return -1; + } else if (line == FirstHTLine(HTMainText)) { + count = line_num_in_text(HTMainText, LastHTLine(HTMainText)) + 1; + wrapped++; + a = HTMainText->last_anchor; + } + line = line->prev; + count--; + } + if (tentative_result > 0) { + adjust_search_result(doc, tentative_result, start_line); + } + return 0; +} + +void www_user_search(int start_line, + DocInfo *doc, + char *target, + int direction) +{ + HTLine *line; + int count; + + if (!HTMainText) { + return; + } + + /* + * Advance to the start line. + */ + line = FirstHTLine(HTMainText); + if (start_line + direction > 0) { + for (count = 1; + count < start_line + direction; + line = line->next, count++) { + if (line == HTMainText->last_line) { + line = FirstHTLine(HTMainText); + count = 1; + break; + } + } + } else { + line = HTMainText->last_line; + count = line_num_in_text(HTMainText, line); + } + + if (direction >= 0) + www_search_forward(start_line, doc, target, line, count); + else + www_search_backward(start_line, doc, target, line, count); +} + +void user_message(const char *message, + const char *argument) +{ + if (message == NULL) { + mustshow = FALSE; + } else { + char *temp = NULL; + + HTSprintf0(&temp, message, NonNull(argument)); + statusline(temp); + FREE(temp); + } +} + +/* + * HText_getOwner returns the owner of the + * current document. + */ +const char *HText_getOwner(void) +{ + return (HTMainText ? + HTAnchor_owner(HTMainText->node_anchor) : 0); +} + +/* + * HText_setMainTextOwner sets the owner for the + * current document. + */ +void HText_setMainTextOwner(const char *owner) +{ + if (!HTMainText) + return; + + HTAnchor_setOwner(HTMainText->node_anchor, owner); +} + +/* + * HText_getRevTitle returns the RevTitle element of the + * current document, used as the subject for mailto comments + * to the owner. + */ +const char *HText_getRevTitle(void) +{ + return (HTMainText ? + HTAnchor_RevTitle(HTMainText->node_anchor) : 0); +} + +/* + * HText_getContentBase returns the Content-Base header + * of the current document. + */ +const char *HText_getContentBase(void) +{ + return (HTMainText ? + HTAnchor_content_base(HTMainText->node_anchor) : 0); +} + +/* + * HText_getContentLocation returns the Content-Location header + * of the current document. + */ +const char *HText_getContentLocation(void) +{ + return (HTMainText ? + HTAnchor_content_location(HTMainText->node_anchor) : 0); +} + +/* + * HText_getMessageID returns the Message-ID of the + * current document. + */ +const char *HText_getMessageID(void) +{ + return (HTMainText ? + HTAnchor_messageID(HTMainText->node_anchor) : NULL); +} + +void HTuncache_current_document(void) +{ + /* + * Should remove current document from memory. + */ + if (HTMainText) { + HTParentAnchor *htmain_anchor = HTMainText->node_anchor; + + if (htmain_anchor) { + if (!(HTOutputFormat && HTOutputFormat == WWW_SOURCE)) { + FREE(htmain_anchor->UCStages); + } + } + CTRACE((tfp, "\nHTuncache.. freeing document for '%s'%s\n", + ((htmain_anchor && + htmain_anchor->address) ? + htmain_anchor->address : "unknown anchor"), + ((htmain_anchor && + htmain_anchor->post_data) + ? " with POST data" + : ""))); + HTList_removeObject(loaded_texts, HTMainText); + HText_free(HTMainText); + HTMainText = NULL; + } else { + CTRACE((tfp, "HTuncache.. HTMainText already is NULL!\n")); + } +} + +/* + * This magic FREE(anchor->UCStages) call + * stolen from HTuncache_current_document() above. + */ +static void magicUncache(void) +{ + if (!(HTOutputFormat && HTOutputFormat == WWW_SOURCE)) { + FREE(HTMainAnchor->UCStages); + } + /* avoid null-reference later */ + if (!HTOutputFormat) + HTOutputFormat = WWW_SOURCE; +} + +#ifdef USE_SOURCE_CACHE + +/* dummy - kw */ +static HTProtocol scm = +{ + "source-cache-mem", 0, 0 +}; + +static BOOLEAN useSourceCache(void) +{ + BOOLEAN result = FALSE; + + if (LYCacheSource == SOURCE_CACHE_FILE) { + result = (BOOLEAN) (HTMainAnchor->source_cache_file != 0); + CTRACE((tfp, "SourceCache: file-cache%s found\n", + result ? "" : " not")); + } + return result; +} + +static BOOLEAN useMemoryCache(void) +{ + BOOLEAN result = FALSE; + + if (LYCacheSource == SOURCE_CACHE_MEMORY) { + result = (BOOLEAN) (HTMainAnchor->source_cache_chunk != 0); + CTRACE((tfp, "SourceCache: memory-cache%s found\n", + result ? "" : " not")); + } + return result; +} + +BOOLEAN HTreparse_document(void) +{ + BOOLEAN ok = FALSE; + + if (!HTMainAnchor || LYCacheSource == SOURCE_CACHE_NONE) { + CTRACE((tfp, "HTreparse_document returns FALSE\n")); + } else if (useSourceCache()) { + FILE *fp; + HTFormat format; + int ret; + + CTRACE((tfp, "SourceCache: Reparsing file %s\n", + HTMainAnchor->source_cache_file)); + + magicUncache(); + + /* + * This is more or less copied out of HTLoadFile(), except we don't + * get a content encoding. This may be overkill. -dsb + */ + if (HTMainAnchor->content_type) { + format = HTAtom_for(HTMainAnchor->content_type); + } else { + format = HTFileFormat(HTMainAnchor->source_cache_file, NULL, NULL); + format = HTCharsetFormat(format, HTMainAnchor, + UCLYhndl_for_unspec); + /* not UCLYhndl_HTFile_for_unspec - we are talking about remote + * documents... + */ + } + CTRACE((tfp, " Content type is \"%s\"\n", format->name)); + + fp = fopen(HTMainAnchor->source_cache_file, "r"); + if (!fp) { + CTRACE((tfp, " Cannot read file %s\n", HTMainAnchor->source_cache_file)); + (void) LYRemoveTemp(HTMainAnchor->source_cache_file); + FREE(HTMainAnchor->source_cache_file); + } else { + + if (HText_HaveUserChangedForms(HTMainText)) { + /* + * Issue a warning. Will not restore changed forms, currently. + */ + HTAlert(RELOADING_FORM); + } + /* Set HTMainAnchor->protocol or HTMainAnchor->physical to convince + * the SourceCacheWriter to not regenerate the cache file (which + * would be an unnecessary "loop"). - kw + */ + HTAnchor_setProtocol(HTMainAnchor, &HTFile); + ret = HTParseFile(format, HTOutputFormat, HTMainAnchor, fp, NULL); + LYCloseInput(fp); + if (ret == HT_PARTIAL_CONTENT) { + HTInfoMsg(gettext("Loading incomplete.")); + CTRACE((tfp, + "SourceCache: `%s' has been accessed, partial content.\n", + HTLoadedDocumentURL())); + } + ok = (BOOL) (ret == HT_LOADED || ret == HT_PARTIAL_CONTENT); + + CTRACE((tfp, "Reparse file %s\n", (ok ? "succeeded" : "failed"))); + } + } else if (useMemoryCache()) { + HTFormat format = WWW_HTML; + int ret; + + CTRACE((tfp, "SourceCache: Reparsing from memory chunk %p\n", + (void *) HTMainAnchor->source_cache_chunk)); + + magicUncache(); + + if (HTMainAnchor->content_type) { + format = HTAtom_for(HTMainAnchor->content_type); + } else { + /* + * This is only done to make things aligned with SOURCE_CACHE_NONE + * and SOURCE_CACHE_FILE when switching to source mode since the + * original document's charset will be LYPushAssumed() and then + * LYPopAssumed(). See LYK_SOURCE in mainloop if you change + * something here. No user-visible benefits, seems just '=' Info + * Page will show source's effective charset as "(assumed)". + */ + format = HTCharsetFormat(format, HTMainAnchor, + UCLYhndl_for_unspec); + } + /* not UCLYhndl_HTFile_for_unspec - we are talking about remote documents... */ + + if (HText_HaveUserChangedForms(HTMainText)) { + /* + * Issue a warning. Will not restore changed forms, currently. + */ + HTAlert(RELOADING_FORM); + } + /* Set HTMainAnchor->protocol or HTMainAnchor->physical to convince + * the SourceCacheWriter to not regenerate the cache chunk (which + * would be an unnecessary "loop"). - kw + */ + HTAnchor_setProtocol(HTMainAnchor, &scm); /* cheating - + anything != &HTTP or &HTTPS would do - kw */ + ret = HTParseMem(format, HTOutputFormat, HTMainAnchor, + HTMainAnchor->source_cache_chunk, NULL); + ok = (BOOL) (ret == HT_LOADED); + + CTRACE((tfp, "Reparse memory %s\n", (ok ? "succeeded" : "failed"))); + } + + return ok; +} + +BOOLEAN HTcan_reparse_document(void) +{ + BOOLEAN result = FALSE; + + if (!HTMainAnchor || LYCacheSource == SOURCE_CACHE_NONE) { + result = FALSE; + } else if (useSourceCache()) { + result = LYCanReadFile(HTMainAnchor->source_cache_file); + } else if (useMemoryCache()) { + result = TRUE; + } + + CTRACE((tfp, "HTcan_reparse_document -> %d\n", result)); + return result; +} + +static void trace_setting_change(const char *name, + int prev_setting, + int new_setting) +{ + if (prev_setting != new_setting) + CTRACE((tfp, + "HTdocument_settings_changed: %s setting has changed (was %d, now %d)\n", + name, prev_setting, new_setting)); +} + +BOOLEAN HTdocument_settings_changed(void) +{ + /* + * Annoying Hack(TM): If we don't have a source cache, we can't + * reparse anyway, so pretend the settings haven't changed. + */ + if (!HTMainText || !HTcan_reparse_document()) + return FALSE; + + if (TRACE) { + /* + * If we're tracing, note everying that has changed. + */ + trace_setting_change("CLICKABLE_IMAGES", + HTMainText->clickable_images, clickable_images); + trace_setting_change("PSEUDO_INLINE_ALTS", + HTMainText->pseudo_inline_alts, + pseudo_inline_alts); + trace_setting_change("VERBOSE_IMG", + HTMainText->verbose_img, + verbose_img); + trace_setting_change("RAW_MODE", HTMainText->raw_mode, + LYUseDefaultRawMode); + trace_setting_change("HISTORICAL_COMMENTS", + HTMainText->historical_comments, + historical_comments); + trace_setting_change("MINIMAL_COMMENTS", + HTMainText->minimal_comments, minimal_comments); + trace_setting_change("SOFT_DQUOTES", + HTMainText->soft_dquotes, soft_dquotes); + trace_setting_change("OLD_DTD", HTMainText->old_dtd, Old_DTD); + trace_setting_change("KEYPAD_MODE", + HTMainText->keypad_mode, keypad_mode); + if (HTMainText->disp_lines != LYlines || HTMainText->disp_cols != DISPLAY_COLS) + CTRACE((tfp, + "HTdocument_settings_changed: Screen size has changed (was %dx%d, now %dx%d)\n", + HTMainText->disp_cols, + HTMainText->disp_lines, + DISPLAY_COLS, + LYlines)); + } + + return (BOOLEAN) (HTMainText->clickable_images != clickable_images || + HTMainText->pseudo_inline_alts != pseudo_inline_alts || + HTMainText->verbose_img != verbose_img || + HTMainText->raw_mode != LYUseDefaultRawMode || + HTMainText->historical_comments != historical_comments || + (HTMainText->minimal_comments != minimal_comments && + !historical_comments) || + HTMainText->soft_dquotes != soft_dquotes || + HTMainText->old_dtd != Old_DTD || + HTMainText->keypad_mode != keypad_mode || + HTMainText->disp_cols != DISPLAY_COLS || + HTMainText->disp_lines != LYlines); +} +#endif + +int HTisDocumentSource(void) +{ + return (HTMainText != 0) ? HTMainText->source : FALSE; +} + +const char *HTLoadedDocumentURL(void) +{ + if (!HTMainText) + return (""); + + if (HTMainText->node_anchor && HTMainText->node_anchor->address) + return (HTMainText->node_anchor->address); + else + return (""); +} + +bstring *HTLoadedDocumentPost_data(void) +{ + if (HTMainText + && HTMainText->node_anchor + && HTMainText->node_anchor->post_data) + return (HTMainText->node_anchor->post_data); + else + return (0); +} + +const char *HTLoadedDocumentTitle(void) +{ + if (!HTMainText) + return (""); + + if (HTMainText->node_anchor && HTMainText->node_anchor->title) + return (HTMainText->node_anchor->title); + else + return (""); +} + +BOOLEAN HTLoadedDocumentIsHEAD(void) +{ + if (!HTMainText) + return (FALSE); + + if (HTMainText->node_anchor && HTMainText->node_anchor->isHEAD) + return (HTMainText->node_anchor->isHEAD); + else + return (FALSE); +} + +BOOLEAN HTLoadedDocumentIsSafe(void) +{ + if (!HTMainText) + return (FALSE); + + if (HTMainText->node_anchor && HTMainText->node_anchor->safe) + return (HTMainText->node_anchor->safe); + else + return (FALSE); +} + +const char *HTLoadedDocumentCharset(void) +{ + const char *result = NULL; + + if (HTMainText && + HTMainText->node_anchor) { + result = HTMainText->node_anchor->charset; + } + + return result; +} + +BOOL HTLoadedDocumentEightbit(void) +{ + if (!HTMainText) + return (NO); + else + return (HTMainText->have_8bit_chars); +} + +void HText_setNodeAnchorBookmark(const char *bookmark) +{ + if (!HTMainText) + return; + + if (HTMainText->node_anchor) + HTAnchor_setBookmark(HTMainText->node_anchor, bookmark); +} + +const char *HTLoadedDocumentBookmark(void) +{ + if (!HTMainText) + return (NULL); + + if (HTMainText->node_anchor && HTMainText->node_anchor->bookmark) + return (HTMainText->node_anchor->bookmark); + else + return (NULL); +} + +int HText_LastLineSize(HText *text, int IgnoreSpaces) +{ + if (!text || !text->last_line || !text->last_line->size) + return 0; + return HText_TrueLineSize(text->last_line, text, IgnoreSpaces); +} + +BOOL HText_LastLineEmpty(HText *text, int IgnoreSpaces) +{ + if (!text || !text->last_line || !text->last_line->size) + return TRUE; + return HText_TrueEmptyLine(text->last_line, text, IgnoreSpaces); +} + +int HText_LastLineOffset(HText *text) +{ + if (!text || !text->last_line) + return 0; + return text->last_line->offset; +} + +int HText_PreviousLineSize(HText *text, int IgnoreSpaces) +{ + HTLine *line; + + if (!text || !text->last_line) + return 0; + if (!(line = text->last_line->prev)) + return 0; + return HText_TrueLineSize(line, text, IgnoreSpaces); +} + +BOOL HText_PreviousLineEmpty(HText *text, int IgnoreSpaces) +{ + HTLine *line; + + if (!text || !text->last_line) + return TRUE; + if (!(line = text->last_line->prev)) + return TRUE; + return HText_TrueEmptyLine(line, text, IgnoreSpaces); +} + +/* + * Compute the "true" line size. + */ +static int HText_TrueLineSize(HTLine *line, HText *text, int IgnoreSpaces) +{ + size_t i; + int true_size = 0; + + if (!(line && line->size)) + return 0; + + if (IgnoreSpaces) { + for (i = 0; i < line->size; i++) { + if (!IsSpecialAttrChar(UCH(line->data[i])) && + IS_UTF8_EXTRA(line->data[i]) && + !isspace(UCH(line->data[i])) && + UCH(line->data[i]) != HT_NON_BREAK_SPACE && + UCH(line->data[i]) != HT_EN_SPACE) { + true_size++; +#ifdef EXP_WCWIDTH_SUPPORT + if (text && text->T.output_utf8 && + IS_UTF_FIRST(line->data[i])) { + true_size += utfextracells(&(line->data[i])); + } +#endif + } + } + } else { + for (i = 0; i < line->size; i++) { + if (!IsSpecialAttrChar(line->data[i]) && + IS_UTF8_EXTRA(line->data[i])) { + true_size++; +#ifdef EXP_WCWIDTH_SUPPORT + if (text && text->T.output_utf8 && + IS_UTF_FIRST(line->data[i])) { + true_size += utfextracells(&(line->data[i])); + } +#endif + } + } + } + return true_size; +} + +/* + * Tell if the line is really empty. This is invoked much more often than + * HText_TrueLineSize(), and most lines are not empty. So it is faster to + * do this check than to check if the line size happens to be zero. + */ +static BOOL HText_TrueEmptyLine(HTLine *line, HText *text, int IgnoreSpaces) +{ + size_t i; + + if (!(line && line->size)) + return TRUE; + + if (IgnoreSpaces) { + for (i = 0; i < line->size; i++) { + if (!IsSpecialAttrChar(UCH(line->data[i])) && + IS_UTF8_EXTRA(line->data[i]) && + !isspace(UCH(line->data[i])) && + UCH(line->data[i]) != HT_NON_BREAK_SPACE && + UCH(line->data[i]) != HT_EN_SPACE) { + return FALSE; + } + } + } else { + for (i = 0; i < line->size; i++) { + if (!IsSpecialAttrChar(line->data[i]) && + IS_UTF8_EXTRA(line->data[i])) { + return FALSE; + } + } + } + return TRUE; +} + +void HText_NegateLineOne(HText *text) +{ + if (text) { + text->in_line_1 = NO; + } + return; +} + +BOOL HText_inLineOne(HText *text) +{ + if (text) { + return text->in_line_1; + } + return YES; +} + +/* + * This function is for removing the first of two + * successive blank lines. It should be called after + * checking the situation with HText_LastLineSize() + * and HText_PreviousLineSize(). Any characters in + * the removed line (i.e., control characters, or it + * wouldn't have tested blank) should have been + * reiterated by split_line() in the retained blank + * line. -FM + */ +void HText_RemovePreviousLine(HText *text) +{ + HTLine *line, *previous; + + if (!(text && text->Lines > 1)) + return; + + line = text->last_line->prev; + previous = line->prev; + previous->next = text->last_line; + text->last_line->prev = previous; + text->Lines--; + freeHTLine(text, line); +} + +/* + * NOTE: This function presently is correct only if the + * alignment is HT_LEFT. The offset is still zero, + * because that's not determined for HT_CENTER or + * HT_RIGHT until subsequent characters are received + * and split_line() is called. -FM + */ +int HText_getCurrentColumn(HText *text) +{ + int column = 0; + BOOL IgnoreSpaces = FALSE; + + if (text) { + column = ((text->in_line_1 + ? (int) text->style->indent1st + : (int) text->style->leftIndent) + + (int) text->last_line->offset + + HText_LastLineSize(text, IgnoreSpaces)); + } + return column; +} + +int HText_getMaximumColumn(HText *text) +{ + int column = DISPLAY_COLS; + + if (text) { + column -= (int) text->style->rightIndent; + } + return column; +} + +/* + * NOTE: This function uses HText_getCurrentColumn() which + * presently is correct only if the alignment is + * HT_LEFT. -FM + */ +void HText_setTabID(HText *text, const char *name) +{ + HTTabID *Tab = NULL; + HTList *cur = text->tabs; + HTList *last = NULL; + + if (!text || isEmpty(name)) + return; + + if (!cur) { + cur = text->tabs = HTList_new(); + } else { + while (NULL != (Tab = (HTTabID *) HTList_nextObject(cur))) { + if (Tab->name && !strcmp(Tab->name, name)) + return; /* Already set. Keep the first value. */ + last = cur; + } + if (last) + cur = last; + } + if (!Tab) { /* New name. Create a new node */ + Tab = typecalloc(HTTabID); + if (Tab == NULL) + outofmem(__FILE__, "HText_setTabID"); + HTList_addObject(cur, Tab); + StrAllocCopy(Tab->name, name); + } + + Tab->column = HText_getCurrentColumn(text); + return; +} + +int HText_getTabIDColumn(HText *text, const char *name) +{ + int column = 0; + HTTabID *Tab; + HTList *cur = text->tabs; + + if (text && non_empty(name) && cur) { + while (NULL != (Tab = (HTTabID *) HTList_nextObject(cur))) { + if (Tab->name && !strcmp(Tab->name, name)) + break; + } + if (Tab) + column = Tab->column; + } + return column; +} + +/* + * This function is for saving the address of a link + * which had an attribute in the markup that resolved + * to a URL (i.e., not just a NAME or ID attribute), + * but was found in HText_endAnchor() to have no visible + * content for use as a link name. It loads the address + * into text->hidden_links, whose count can be determined + * via HText_HiddenLinks(), below. The addresses can be + * retrieved via HText_HiddenLinkAt(), below, based on + * count. -FM + */ +static void HText_AddHiddenLink(HText *text, TextAnchor *textanchor) +{ + HTAnchor *dest; + + /* + * Make sure we have an HText structure and anchor. -FM + */ + if (!(text && textanchor && textanchor->anchor)) + return; + + /* + * Create the hidden links list + * if it hasn't been already. -FM + */ + if (text->hidden_links == NULL) + text->hidden_links = HTList_new(); + + /* + * Store the address, in reverse list order + * so that first in will be first out on + * retrievals. -FM + */ + if ((dest = HTAnchor_followLink(textanchor->anchor)) && + (text->hiddenlinkflag != HIDDENLINKS_IGNORE || + HTList_isEmpty(text->hidden_links))) { + char *value = HTAnchor_address(dest); + BOOL ignore = FALSE; + + if (unique_urls) { + int cnt; + char *check; + + for (cnt = 0;; ++cnt) { + + check = (char *) HTList_objectAt(text->hidden_links, cnt); + if (check == 0) + break; + if (!strcmp(check, value)) { + ignore = TRUE; + break; + } + } + } + if (ignore) { + FREE(value); + } else { + HTList_appendObject(text->hidden_links, value); + } + } + + return; +} + +/* + * This function returns the number of addresses + * that are loaded in text->hidden_links. -FM + */ +int HText_HiddenLinkCount(HText *text) +{ + int count = 0; + + if (text && text->hidden_links) + count = HTList_count((HTList *) text->hidden_links); + + return (count); +} + +/* + * This function returns the address, corresponding to + * a hidden link, at the position (zero-based) in the + * text->hidden_links list of the number argument. -FM + */ +const char *HText_HiddenLinkAt(HText *text, int number) +{ + char *href = NULL; + + if (text && text->hidden_links && number >= 0) + href = (char *) HTList_objectAt((HTList *) text->hidden_links, number); + + return (href); +} + +/* + * Form methods + * These routines are used to build forms consisting + * of input fields + */ +static BOOLEAN HTFormDisabled = FALSE; +static PerFormInfo *HTCurrentForm; + +static BOOLEAN addFormAction(FormInfo * f) +{ + BOOLEAN result = FALSE; + + if (HTCurrentForm != NULL) { + result = TRUE; + f->submit_action = NULL; + StrAllocCopy(f->submit_action, HTCurrentForm->data.submit_action); + if (HTCurrentForm->data.submit_enctype != NULL) + StrAllocCopy(f->submit_enctype, HTCurrentForm->data.submit_enctype); + if (HTCurrentForm->data.submit_title != NULL) + StrAllocCopy(f->submit_title, HTCurrentForm->data.submit_title); + f->submit_method = HTCurrentForm->data.submit_method; + } + return result; +} + +void HText_beginForm(char *action, + char *method, + char *enctype, + char *title, + const char *accept_cs) +{ + PerFormInfo *newform; + int HTFormMethod = URL_GET_METHOD; + char *HTFormAction = NULL; + char *HTFormEnctype = NULL; + char *HTFormTitle = NULL; + char *HTFormAcceptCharset = NULL; + + HTFormNumber++; + + HTFormFields = 0; + HTFormDisabled = FALSE; + + /* + * Check the ACTION. -FM + */ + if (action != NULL) { + if (isMAILTO_URL(action)) { + HTFormMethod = URL_MAIL_METHOD; + } + StrAllocCopy(HTFormAction, action); + } else + StrAllocCopy(HTFormAction, HTLoadedDocumentURL()); + + /* + * Check the METHOD. -FM + */ + if (method != NULL && HTFormMethod != URL_MAIL_METHOD) + if (!strcasecomp(method, "post") || !strcasecomp(method, "pget")) + HTFormMethod = URL_POST_METHOD; + + /* + * Check the ENCTYPE. -FM + */ + if (non_empty(enctype)) { + StrAllocCopy(HTFormEnctype, enctype); + if (HTFormMethod != URL_MAIL_METHOD && + !strncasecomp(enctype, "multipart/form-data", 19)) + HTFormMethod = URL_POST_METHOD; + } else { + FREE(HTFormEnctype); + } + + /* + * Check the TITLE. -FM + */ + if (non_empty(title)) + StrAllocCopy(HTFormTitle, title); + else + FREE(HTFormTitle); + + /* + * Check for an ACCEPT_CHARSET. If present, store it and + * convert to lowercase and collapse spaces. - kw + */ + if (accept_cs != NULL) { + StrAllocCopy(HTFormAcceptCharset, accept_cs); + LYRemoveBlanks(HTFormAcceptCharset); + LYLowerCase(HTFormAcceptCharset); + } + + /* + * Create a new "PerFormInfo" structure to hold info on the current form. + * This will be appended to the forms list kept by the HText object if and + * when we reach a HText_endForm. + */ + newform = typecalloc(PerFormInfo); + if (newform == NULL) + outofmem(__FILE__, "HText_beginForm"); + + PerFormInfo_free(HTCurrentForm); /* shouldn't happen here - kw */ + HTCurrentForm = newform; + + newform->number = HTFormNumber; + newform->data.submit_action = HTFormAction; + newform->data.submit_enctype = HTFormEnctype; + newform->data.submit_method = HTFormMethod; + newform->data.submit_title = HTFormTitle; + newform->accept_cs = HTFormAcceptCharset; + + CTRACE((tfp, "BeginForm: action:%s Method:%d%s%s%s%s%s%s\n", + HTFormAction, HTFormMethod, + (HTFormTitle ? " Title:" : ""), + NonNull(HTFormTitle), + (HTFormEnctype ? " Enctype:" : ""), + NonNull(HTFormEnctype), + (HTFormAcceptCharset ? " Accept-charset:" : ""), + NonNull(HTFormAcceptCharset))); +} + +void HText_endForm(HText *text) +{ + if (text != NULL) { + if (HTFormFields == 1 && text->first_anchor) { + /* + * Support submission of a single text input field in + * the form via instead of a submit button. -FM + */ + TextAnchor *a; + + /* + * Go through list of anchors and get our input field. -FM + */ + for (a = text->first_anchor; a != NULL; a = a->next) { + if (a->link_type == INPUT_ANCHOR && + a->input_field->number == HTFormNumber && + a->input_field->type != F_TEXTAREA_TYPE && + F_TEXTLIKE(a->input_field->type)) { + /* + * Got it. Make it submitting. -FM + */ + if (addFormAction(a->input_field)) { + a->input_field->type = F_TEXT_SUBMIT_TYPE; + if (HTFormDisabled) + a->input_field->disabled = TRUE; + } + break; + } + } + } + + /* + * Append info on the current form to the HText object's list of forms. + * HText_beginInput call will have set some of the data in the + * PerFormInfo structure (if there were any form fields at all). + */ + if (HTCurrentForm) { + if (HTFormDisabled) + HTCurrentForm->disabled = TRUE; + if (!text->forms) + text->forms = HTList_new(); + HTList_appendObject(text->forms, HTCurrentForm); + HTCurrentForm = NULL; + } else { + CTRACE((tfp, "endForm: HTCurrentForm is missing!\n")); + } + } else { + CTRACE((tfp, "endForm: HText is missing!\n")); + } + + FREE(HTCurSelectGroup); + FREE(HTCurSelectGroupSize); + FREE(HTCurSelectedOptionValue); + HTFormFields = 0; + HTFormDisabled = FALSE; +} + +void HText_beginSelect(char *name, + int name_cs, + int multiple, + char *size) +{ + /* + * Save the group name. + */ + StrAllocCopy(HTCurSelectGroup, name); + HTCurSelectGroupCharset = name_cs; + + /* + * If multiple then all options are actually checkboxes. + */ + if (multiple) + HTCurSelectGroupType = F_CHECKBOX_TYPE; + /* + * If not multiple then all options are radio buttons. + */ + else + HTCurSelectGroupType = F_RADIO_TYPE; + + /* + * Length of an option list. + */ + StrAllocCopy(HTCurSelectGroupSize, size); + + CTRACE((tfp, "HText_beginSelect: name=%s type=%d size=%s\n", + ((HTCurSelectGroup == NULL) ? + "" : HTCurSelectGroup), + HTCurSelectGroupType, + ((HTCurSelectGroupSize == NULL) ? + "" : HTCurSelectGroupSize))); + CTRACE((tfp, "HText_beginSelect: name_cs=%d \"%s\"\n", + HTCurSelectGroupCharset, + (HTCurSelectGroupCharset >= 0 ? + LYCharSet_UC[HTCurSelectGroupCharset].MIMEname : ""))); +} + +/* + * This function returns the number of the option whose + * value currently is being accumulated for a select + * block. - LE && FM + */ +int HText_getOptionNum(HText *text) +{ + TextAnchor *a; + OptionType *op; + int n = 1; /* start count at 1 */ + + if (!(text && text->last_anchor)) + return (0); + + a = text->last_anchor; + if (!(a->link_type == INPUT_ANCHOR && a->input_field && + a->input_field->type == F_OPTION_LIST_TYPE)) + return (0); + + for (op = a->input_field->select_list; op; op = op->next) + n++; + CTRACE((tfp, "HText_getOptionNum: Got number '%d'.\n", n)); + return (n); +} + +/* + * This function checks for a numbered option pattern + * as the prefix for an option value. If present, and + * we are in the correct keypad mode, it returns a + * pointer to the actual value, following that prefix. + * Otherwise, it returns the original pointer. + */ +static char *HText_skipOptionNumPrefix(char *opname) +{ + /* + * Check if we are in the correct keypad mode. + */ + if (fields_are_numbered()) { + /* + * Skip the option number embedded in the option name so the + * extra chars won't mess up cgi scripts processing the value. + * The format is (nnn)__ where nnn is a number and there is a + * minimum of 5 chars (no underscores if (nnn) exceeds 5 chars). + * See HTML.c. If the chars don't exactly match this format, + * just use all of opname. - LE + */ + char *cp = opname; + + if ((non_empty(cp) && *cp++ == '(') && + *cp && isdigit(UCH(*cp++))) { + while (*cp && isdigit(UCH(*cp))) + ++cp; + if (*cp && *cp++ == ')') { + int i = (int) (cp - opname); + + while (i < 5) { + if (*cp != '_') + break; + i++; + cp++; + } + if (i < 5) { + cp = opname; + } + } else { + cp = opname; + } + } else { + cp = opname; + } + return (cp); + } + + return (opname); +} + +/* + * We couldn't set the value field for the previous option tag so we have to do + * it now. Assume that the last anchor was the previous options' tag. + */ +char *HText_setLastOptionValue(HText *text, char *value, + char *submit_value, + int order, + int checked, + int val_cs, + int submit_val_cs) +{ + char *cp, *cp1; + char *ret_Value = NULL; + unsigned char *tmp = NULL; + int number = 0, i, j; + + if (!(value + && text + && text->last_anchor + && text->last_anchor->input_field + && text->last_anchor->link_type == INPUT_ANCHOR)) { + CTRACE((tfp, "HText_setLastOptionValue: invalid call! value:%s!\n", + (value ? value : ""))); + return NULL; + } + + CTRACE((tfp, + "Entering HText_setLastOptionValue: value:\"%s\", checked:%s\n", + value, (checked ? "on" : "off"))); + + /* + * Strip end spaces, newline is also whitespace. + */ + if (*value) { + cp = &value[strlen(value) - 1]; + while ((cp >= value) && (isspace(UCH(*cp)) || + IsSpecialAttrChar(UCH(*cp)))) + cp--; + *(cp + 1) = '\0'; + } + + /* + * Find first non space + */ + cp = value; + while (isspace(UCH(*cp)) || + IsSpecialAttrChar(UCH(*cp))) + cp++; + if (HTCurSelectGroupType == F_RADIO_TYPE && + LYSelectPopups && + fields_are_numbered()) { + /* + * Collapse any space between the popup option + * prefix and actual value. -FM + */ + if ((cp1 = HText_skipOptionNumPrefix(cp)) > cp) { + i = 0, j = (int) (cp1 - cp); + while (isspace(UCH(cp1[i])) || + IsSpecialAttrChar(UCH(cp1[i]))) { + i++; + } + if (i > 0) { + while (cp1[i] != '\0') + cp[j++] = cp1[i++]; + cp[j] = '\0'; + } + } + } + + if (HTCurSelectGroupType == F_CHECKBOX_TYPE) { + StrAllocCopy(text->last_anchor->input_field->value, cp); + text->last_anchor->input_field->value_cs = val_cs; + /* + * Put the text on the screen as well. + */ + HText_appendText(text, cp); + + } else if (LYSelectPopups == FALSE) { + StrAllocCopy(text->last_anchor->input_field->value, + (submit_value ? submit_value : cp)); + text->last_anchor->input_field->value_cs = (submit_value ? + submit_val_cs : val_cs); + /* + * Put the text on the screen as well. + */ + HText_appendText(text, cp); + + } else { + /* + * Create a linked list of option values. + */ + OptionType *op_ptr = text->last_anchor->input_field->select_list; + OptionType *new_ptr = NULL; + BOOLEAN first_option = FALSE; + + /* + * Deal with newlines or tabs. + */ + LYReduceBlanks(value); + + if (!op_ptr) { + /* + * No option items yet. + */ + if (text->last_anchor->input_field->type != F_OPTION_LIST_TYPE) { + CTRACE((tfp, + "HText_setLastOptionValue: last input_field not F_OPTION_LIST_TYPE (%d)\n", + F_OPTION_LIST_TYPE)); + CTRACE((tfp, " but %d, ignoring!\n", + text->last_anchor->input_field->type)); + return NULL; + } + + new_ptr = typecalloc(OptionType); + if (new_ptr == NULL) + outofmem(__FILE__, "HText_setLastOptionValue"); + + text->last_anchor->input_field->select_list = new_ptr; + first_option = TRUE; + } else { + while (op_ptr->next) { + number++; + op_ptr = op_ptr->next; + } + number++; /* add one more */ + + op_ptr->next = new_ptr = typecalloc(OptionType); + if (new_ptr == NULL) + outofmem(__FILE__, "HText_setLastOptionValue"); + } + + new_ptr->name = NULL; + new_ptr->cp_submit_value = NULL; + new_ptr->next = NULL; + /* + * Find first non-space again, convert_to_spaces above may have + * changed the string. - kw + */ + cp = value; + while (isspace(UCH(*cp)) || + IsSpecialAttrChar(UCH(*cp))) + cp++; + for (i = 0, j = 0; cp[i]; i++) { + if (cp[i] == HT_NON_BREAK_SPACE || + cp[i] == HT_EN_SPACE) { + cp[j++] = ' '; + } else if (cp[i] != LY_SOFT_HYPHEN && + !IsSpecialAttrChar(UCH(cp[i]))) { + cp[j++] = cp[i]; + } + } + cp[j] = '\0'; + if (IS_CJK_TTY) { + if ((tmp = typecallocn(unsigned char, strlen(cp) * 2 + 1)) != 0) { + if (kanji_code == EUC) { + TO_EUC((unsigned char *) cp, tmp); + val_cs = current_char_set; + } else if (kanji_code == SJIS) { + TO_SJIS((unsigned char *) cp, tmp); + val_cs = current_char_set; + } else { + for (i = 0, j = 0; cp[i]; i++) { + if (cp[i] != CH_ESC) { /* S/390 -- gil -- 1604 */ + tmp[j++] = UCH(cp[i]); + } + } + } + StrAllocCopy(new_ptr->name, (const char *) tmp); + FREE(tmp); + } else { + outofmem(__FILE__, "HText_setLastOptionValue"); + } + } else { + StrAllocCopy(new_ptr->name, cp); + } + StrAllocCopy(new_ptr->cp_submit_value, + (submit_value ? submit_value : + HText_skipOptionNumPrefix(new_ptr->name))); + new_ptr->value_cs = (submit_value ? submit_val_cs : val_cs); + + if (first_option) { + FormInfo *last_input = text->last_anchor->input_field; + + StrAllocCopy(HTCurSelectedOptionValue, new_ptr->name); + last_input->num_value = 0; + /* + * If this is the first option in a popup select list, + * HText_beginInput may have allocated the value and + * cp_submit_value fields, so free them now to avoid + * a memory leak. - kw + */ + FREE(last_input->value); + FREE(last_input->cp_submit_value); + + last_input->value = last_input->select_list->name; + last_input->orig_value = last_input->select_list->name; + last_input->cp_submit_value = last_input->select_list->cp_submit_value; + last_input->orig_submit_value = last_input->select_list->cp_submit_value; + last_input->value_cs = new_ptr->value_cs; + } else { + int newlen = (int) strlen(new_ptr->name); + int curlen = (int) (HTCurSelectedOptionValue + ? strlen(HTCurSelectedOptionValue) + : 0); + + /* + * Make the selected Option Value as long as + * the longest option. + */ + if (newlen > curlen) + StrAllocCat(HTCurSelectedOptionValue, + UNDERSCORES(newlen - curlen)); + } + + if (checked) { + int curlen = (int) strlen(new_ptr->name); + int newlen = (HTCurSelectedOptionValue + ? (int) strlen(HTCurSelectedOptionValue) + : 0); + FormInfo *last_input = text->last_anchor->input_field; + + /* + * Set the default option as this one. + */ + last_input->num_value = number; + last_input->value = new_ptr->name; + last_input->orig_value = new_ptr->name; + last_input->cp_submit_value = new_ptr->cp_submit_value; + last_input->orig_submit_value = new_ptr->cp_submit_value; + last_input->value_cs = new_ptr->value_cs; + StrAllocCopy(HTCurSelectedOptionValue, new_ptr->name); + if (newlen > curlen) + StrAllocCat(HTCurSelectedOptionValue, + UNDERSCORES(newlen - curlen)); + } + + /* + * Return the selected Option value to be sent to the screen. + */ + if (order == LAST_ORDER) { + /* + * Change the value. + */ + if (HTCurSelectedOptionValue == 0) + StrAllocCopy(HTCurSelectedOptionValue, ""); + text->last_anchor->input_field->size = + (int) strlen(HTCurSelectedOptionValue); + ret_Value = HTCurSelectedOptionValue; + } + } + + if (TRACE) { + CTRACE((tfp, "HText_setLastOptionValue:%s value=\"%s\"\n", + (order == LAST_ORDER) ? " LAST_ORDER" : "", + value)); + CTRACE((tfp, " val_cs=%d \"%s\"", + val_cs, + (val_cs >= 0 ? + LYCharSet_UC[val_cs].MIMEname : ""))); + if (submit_value) { + CTRACE((tfp, " (submit_val_cs %d \"%s\") submit_value%s=\"%s\"\n", + submit_val_cs, + (submit_val_cs >= 0 ? + LYCharSet_UC[submit_val_cs].MIMEname : ""), + (HTCurSelectGroupType == F_CHECKBOX_TYPE) ? + "(ignored)" : "", + submit_value)); + } else { + CTRACE((tfp, "\n")); + } + } + return (ret_Value); +} + +/* + * Assign a form input anchor. + * Returns the number of characters to leave + * blank so that the input field can fit. + */ +int HText_beginInput(HText *text, + int underline, + InputFieldData * I) +{ + TextAnchor *a; + FormInfo *f; + const char *cp_option = NULL; + char *IValue = NULL; + unsigned char *tmp = NULL; + int i, j; + int adjust_marker = 0; + int MaximumSize; + char marker[16]; + + CTRACE((tfp, "GridText: Entering HText_beginInput type=%s\n", NonNull(I->type))); + + POOLtypecalloc(TextAnchor, a); + + POOLtypecalloc(FormInfo, f); + if (a == NULL || f == NULL) + outofmem(__FILE__, "HText_beginInput"); + + a->sgml_offset = SGML_offset(); + a->inUnderline = (BOOLEAN) underline; + a->line_num = text->Lines; + a->line_pos = (short) text->last_line->size; + + /* + * If this is a radio button, or an OPTION we're converting + * to a radio button, and it's the first with this name, make + * sure it's checked by default. Otherwise, if it's checked, + * uncheck the default or any preceding radio button with this + * name that was checked. -FM + */ + if (I->type != NULL && !strcmp(I->type, "OPTION") && + HTCurSelectGroupType == F_RADIO_TYPE && LYSelectPopups == FALSE) { + I->type = "RADIO"; + I->name = HTCurSelectGroup; + I->name_cs = HTCurSelectGroupCharset; + } + if (I->name && I->type && !strcasecomp(I->type, "radio")) { + if (!text->last_anchor) { + I->checked = TRUE; + } else { + TextAnchor *b; + int i2 = 0; + + for (b = text->first_anchor; b != NULL; b = b->next) { + if (b->link_type == INPUT_ANCHOR && + b->input_field->type == F_RADIO_TYPE && + b->input_field->number == HTFormNumber) { + if (!strcmp(b->input_field->name, I->name)) { + if (I->checked && b->input_field->num_value) { + b->input_field->num_value = 0; + StrAllocCopy(b->input_field->orig_value, "0"); + break; + } + i2++; + } + } + } + if (i2 == 0) + I->checked = TRUE; + } + } + + a->next = 0; + a->anchor = NULL; + a->link_type = INPUT_ANCHOR; + a->show_anchor = YES; + + LYClearHiText(a); + a->extent = 2; + + a->input_field = f; + + f->select_list = 0; + f->number = HTFormNumber; + f->disabled = HTFormDisabled || I->disabled; + f->readonly = I->readonly; + f->no_cache = NO; + + HTFormFields++; + + /* + * Set up VALUE. + */ + if (I->value) { + StrAllocCopy(IValue, I->value); + } + if (IValue && + IS_CJK_TTY && + ((I->type == NULL) || strcasecomp(I->type, "hidden"))) { + if ((tmp = typecallocn(unsigned char, strlen(IValue) * 2 + 1)) != 0) { + if (kanji_code == EUC) { + TO_EUC((unsigned char *) IValue, tmp); + I->value_cs = current_char_set; + } else if (kanji_code == SJIS) { + TO_SJIS((unsigned char *) IValue, tmp); + I->value_cs = current_char_set; + } else { + for (i = 0, j = 0; IValue[i]; i++) { + if (IValue[i] != CH_ESC) { /* S/390 -- gil -- 1621 */ + tmp[j++] = UCH(IValue[i]); + } + } + } + StrAllocCopy(IValue, (const char *) tmp); + FREE(tmp); + } + } + + /* + * Special case of OPTION. + * Is handled above if radio type and LYSelectPopups is FALSE. + */ + /* set the values and let the parsing below do the work */ + if (I->type != NULL && !strcmp(I->type, "OPTION")) { + cp_option = I->type; + if (HTCurSelectGroupType == F_RADIO_TYPE) + I->type = "OPTION_LIST"; + else + I->type = "CHECKBOX"; + I->name = HTCurSelectGroup; + I->name_cs = HTCurSelectGroupCharset; + + /* + * The option's size parameter actually gives the length and not + * the width of the list. Perform the conversion here + * and get rid of the allocated HTCurSelect.... + * 0 is ok as it means any length (arbitrary decision). + */ + if (HTCurSelectGroupSize != NULL) { + f->size_l = atoi(HTCurSelectGroupSize); + FREE(HTCurSelectGroupSize); + } + } + + /* + * Set SIZE. + */ + if (I->size != 0) { + f->size = I->size; + /* + * Leave at zero for option lists. + */ + if (f->size == 0 && cp_option == NULL) { + f->size = 20; /* default */ + } + } else { + f->size = 20; /* default */ + } + + /* + * Set MAXLENGTH. + */ + if (I->maxlength != NULL) { + f->maxlength = (unsigned) atoi(I->maxlength); + } else { + f->maxlength = 0; /* 0 means infinite */ + } + + /* + * Set CHECKED + * (num_value is only relevant to check and radio types). + */ + if (I->checked == TRUE) + f->num_value = 1; + else + f->num_value = 0; + + /* + * Set TYPE. + */ + if (I->type != NULL) { + if (!strcasecomp(I->type, "password")) { + f->type = F_PASSWORD_TYPE; + } else if (!strcasecomp(I->type, "checkbox")) { + f->type = F_CHECKBOX_TYPE; + } else if (!strcasecomp(I->type, "radio")) { + f->type = F_RADIO_TYPE; + } else if (!strcasecomp(I->type, "submit")) { + f->type = F_SUBMIT_TYPE; + } else if (!strcasecomp(I->type, "image")) { + f->type = F_IMAGE_SUBMIT_TYPE; + } else if (!strcasecomp(I->type, "reset")) { + f->type = F_RESET_TYPE; + } else if (!strcasecomp(I->type, "OPTION_LIST")) { + f->type = F_OPTION_LIST_TYPE; + } else if (!strcasecomp(I->type, "hidden")) { + f->type = F_HIDDEN_TYPE; + HTFormFields--; + f->size = 0; + } else if (!strcasecomp(I->type, "textarea")) { + f->type = F_TEXTAREA_TYPE; + } else if (!strcasecomp(I->type, "range")) { + f->type = F_RANGE_TYPE; + } else if (!strcasecomp(I->type, "file")) { + f->type = F_FILE_TYPE; + CTRACE((tfp, "ok, got a file uploader\n")); + } else if (!strcasecomp(I->type, "keygen")) { + f->type = F_KEYGEN_TYPE; + } else if (!strcasecomp(I->type, "button")) { + f->type = F_BUTTON_TYPE; + } else { + /* + * Note that TYPE="scribble" defaults to TYPE="text". -FM + */ + f->type = F_TEXT_TYPE; /* default */ + } + } else { + f->type = F_TEXT_TYPE; + } + + /* + * Set NAME. + */ + if (I->name != NULL) { + StrAllocCopy(f->name, I->name); + f->name_cs = I->name_cs; + } else { + if (f->type == F_RESET_TYPE || + f->type == F_SUBMIT_TYPE || + f->type == F_IMAGE_SUBMIT_TYPE) { + /* + * Set name to empty string. + */ + StrAllocCopy(f->name, ""); + } else { + /* + * Error! NAME must be present. + */ + CTRACE((tfp, + "GridText: No name present in input field; not displaying\n")); + FREE(IValue); + return (0); + } + } + + /* + * Add this anchor to the anchor list + */ + if (text->last_anchor) { + text->last_anchor->next = a; + } else { + text->first_anchor = a; + } + + /* + * Set VALUE, if it exists. Otherwise, if it's not + * an option list make it a zero-length string. -FM + */ + if (IValue != NULL) { + /* + * OPTION VALUE is not actually the value to be seen but is to + * be sent.... + */ + if (f->type == F_OPTION_LIST_TYPE || + f->type == F_CHECKBOX_TYPE) { + /* + * Fill both with the value. The f->value may be + * overwritten in HText_setLastOptionValue.... + */ + StrAllocCopy(f->value, IValue); + StrAllocCopy(f->cp_submit_value, IValue); + } else { + StrAllocCopy(f->value, IValue); + } + f->value_cs = I->value_cs; + } else if (f->type != F_OPTION_LIST_TYPE) { + StrAllocCopy(f->value, ""); + /* + * May be an empty INPUT field. The text entered will then + * probably be in the current display character set. - kw + */ + f->value_cs = current_char_set; + } + + /* + * Run checks and fill in necessary values. + */ + if (f->type == F_RESET_TYPE) { + if (non_empty(f->value)) { + f->size = (int) strlen(f->value); + } else { + StrAllocCopy(f->value, "Reset"); + f->size = 5; + } + } else if (f->type == F_BUTTON_TYPE) { + if (non_empty(f->value)) { + f->size = (int) strlen(f->value); + } else { + StrAllocCopy(f->value, "BUTTON"); + f->size = 5; + } + } else if (f->type == F_IMAGE_SUBMIT_TYPE || + f->type == F_SUBMIT_TYPE) { + if (non_empty(f->value)) { + f->size = (int) strlen(f->value); + } else if (f->type == F_IMAGE_SUBMIT_TYPE) { + StrAllocCopy(f->value, "[IMAGE]-Submit"); + f->size = 14; + } else { + StrAllocCopy(f->value, "Submit"); + f->size = 6; + } + addFormAction(f); + } else if (f->type == F_RADIO_TYPE || f->type == F_CHECKBOX_TYPE) { + f->size = 3; + if (IValue == NULL) + StrAllocCopy(f->value, (f->type == F_CHECKBOX_TYPE ? "on" : "")); + + } + FREE(IValue); + + /* + * Set original values. + */ + if (f->type == F_RADIO_TYPE || f->type == F_CHECKBOX_TYPE) { + if (f->num_value) + StrAllocCopy(f->orig_value, "1"); + else + StrAllocCopy(f->orig_value, "0"); + } else if (f->type == F_OPTION_LIST_TYPE) { + f->orig_value = NULL; + } else { + StrAllocCopy(f->orig_value, f->value); + } + + /* + * Store accept-charset if present, converting to lowercase + * and collapsing spaces. - kw + */ + if (I->accept_cs) { + StrAllocCopy(f->accept_cs, I->accept_cs); + LYRemoveBlanks(f->accept_cs); + LYLowerCase(f->accept_cs); + } + + /* + * Add numbers to form fields if needed. - LE & FM + */ + switch (f->type) { + /* + * Do not supply number for hidden fields, nor + * for types that are not yet implemented. + */ + case F_HIDDEN_TYPE: +#ifndef USE_FILE_UPLOAD + case F_FILE_TYPE: +#endif + case F_RANGE_TYPE: + case F_KEYGEN_TYPE: + case F_BUTTON_TYPE: + a->number = 0; + break; + + default: + if (fields_are_numbered()) + a->number = ++(text->last_anchor_number); + else + a->number = 0; + break; + } + if (fields_are_numbered() && (a->number > 0)) { + if (HTMainText != 0) { + HText_findAnchorNumber(a); + } else { + a->show_number = a->number; + } + sprintf(marker, "[%d]", a->show_number); + adjust_marker = (int) strlen(marker); + if (number_fields_on_left) { + BOOL had_bracket = (BOOL) (f->type == F_OPTION_LIST_TYPE); + + HText_appendText(text, had_bracket ? (marker + 1) : marker); + if (had_bracket) + HText_appendCharacter(text, '['); + } + a->line_num = text->Lines; + a->line_pos = (short) text->last_line->size; + } else { + *marker = '\0'; + } + + /* + * Restrict SIZE to maximum allowable size. + */ + MaximumSize = WRAP_COLS(text) + 1 - adjust_marker; + switch (f->type) { + + case F_SUBMIT_TYPE: + case F_IMAGE_SUBMIT_TYPE: + case F_RESET_TYPE: + case F_TEXT_TYPE: + case F_TEXTAREA_TYPE: + /* + * For submit and reset buttons, and for text entry + * fields and areas, we limit the size element to that + * of one line for the current style because that's + * the most we could highlight on overwrites, and/or + * handle in the line editor. The actual values for + * text entry lines can be long, and will be scrolled + * horizontally within the editing window. -FM + */ + MaximumSize -= (1 + + (int) text->style->leftIndent + + (int) text->style->rightIndent); + + /* If we are numbering form links, place is taken by [nn] */ + if (fields_are_numbered()) { + if (!number_fields_on_left + && f->type == F_TEXT_TYPE + && MaximumSize > a->line_pos + 10) + MaximumSize -= a->line_pos; + else + MaximumSize -= (int) strlen(marker); + } + + /* + * Save value for submit/reset buttons so they + * will be visible when printing the page. - LE + */ + if (f->type == F_SUBMIT_TYPE) + FREE(I->value); + I->value = f->value; + break; + + default: + /* + * For all other fields we limit the size element to + * 10 less than the screen width, because either they + * are types with small placeholders, and/or are a + * type which is handled via a popup window. -FM + */ + MaximumSize -= 10; + break; + } + + if (MaximumSize < 1) + MaximumSize = 1; + + if (f->size > MaximumSize) + f->size = MaximumSize; + + /* + * Add this anchor to the anchor list + */ + text->last_anchor = a; + + if (HTCurrentForm) { /* should always apply! - kw */ + if (!HTCurrentForm->first_field) { + HTCurrentForm->first_field = f; + } + HTCurrentForm->last_field = f; + HTCurrentForm->nfields++; /* will count hidden fields - kw */ + /* + * Set the no_cache flag if the METHOD is POST. -FM + */ + if (HTCurrentForm->data.submit_method == URL_POST_METHOD) + f->no_cache = TRUE; + /* + * Propagate form field's accept-charset attribute to enclosing + * form if the form itself didn't have an accept-charset - kw + */ + if (f->accept_cs && !HTCurrentForm->accept_cs) { + StrAllocCopy(HTCurrentForm->accept_cs, f->accept_cs); + } + if (!text->forms) { + text->forms = HTList_new(); + } + } else { + CTRACE((tfp, "beginInput: HTCurrentForm is missing!\n")); + } + + CTRACE((tfp, "Input link: name=%s\nvalue=%s\nsize=%d\n", + f->name, + NonNull(f->value), + f->size)); + CTRACE((tfp, "Input link: name_cs=%d \"%s\" (from %d \"%s\")\n", + f->name_cs, + (f->name_cs >= 0 ? + LYCharSet_UC[f->name_cs].MIMEname : ""), + I->name_cs, + (I->name_cs >= 0 ? + LYCharSet_UC[I->name_cs].MIMEname : ""))); + CTRACE((tfp, " value_cs=%d \"%s\" (from %d \"%s\")\n", + f->value_cs, + (f->value_cs >= 0 ? + LYCharSet_UC[f->value_cs].MIMEname : ""), + I->value_cs, + (I->value_cs >= 0 ? + LYCharSet_UC[I->value_cs].MIMEname : ""))); + + /* + * Return the SIZE of the input field. + */ + if (I->size && f->size > adjust_marker) { + f->size -= adjust_marker; + } + return (f->size); +} + +/* + * If we're numbering fields on the right, do it. Note that some fields may + * be too long for the line - we'll lose the marker in that case rather than + * truncate the field. + */ +void HText_endInput(HText *text) +{ + if (fields_are_numbered() + && !number_fields_on_left + && text != NULL + && text->last_anchor != NULL + && text->last_anchor->number > 0) { + char marker[20]; + + sprintf(marker, "[%d]", text->last_anchor->show_number); + HText_appendText(text, marker); + } +} + +/* + * Get a translation (properly: transcoding) quality, factoring in + * our ability to translate (an UCTQ_t) and a possible q parameter + * on the given charset string, for cs_from -> givenmime. + * The parsed input string will be mutilated on exit(!). + * Note that results are not normalised to 1.0, but results from + * different calls of this function can be compared. - kw + * + * Obsolete, it was planned to use here a quality parameter UCTQ_t, + * which is boolean now. + */ +static double get_trans_q(int cs_from, + char *givenmime) +{ + double df = 1.0; + BOOL tq; + char *p; + + if (!givenmime || !(*givenmime)) + return 0.0; + if ((p = StrChr(givenmime, ';')) != NULL) { + *p++ = '\0'; + } + if (!strcmp(givenmime, "*")) + tq = UCCanTranslateFromTo(cs_from, + UCGetLYhndl_byMIME("utf-8")); + else + tq = UCCanTranslateFromTo(cs_from, + UCGetLYhndl_byMIME(givenmime)); + if (!tq) + return 0.0; + if (non_empty(p)) { + char *pair, *field = p, *pval, *ptok; + + /* Get all the parameters to the Charset */ + while ((pair = HTNextTok(&field, ";", "\"", NULL)) != NULL) { + if ((ptok = HTNextTok(&pair, "= ", NULL, NULL)) != NULL && + (pval = HTNextField(&pair)) != NULL) { + if (0 == strcasecomp(ptok, "q")) { + df = strtod(pval, NULL); + break; + } + } + } + return (df * tq); + } else { + return tq; + } +} + +/* + * Find the best charset for submission, if we have an ACCEPT_CHARSET + * list. It factors in how well we can translate (just as guess, and + * not a very good one..) and possible ";q=" factors. Yes this is + * more general than it needs to be here. + * + * Input is cs_in and acceptstring. + * + * Will return charset handle as int. + * best_csname will point to a newly allocated MIME string for the + * charset corresponding to the return value if return value >= 0. + * - kw + */ +static int find_best_target_cs(char **best_csname, + int cs_from, + const char *acceptstring) +{ + char *paccept = NULL; + double bestq = -1.0; + char *bestmime = NULL; + char *field, *nextfield; + + StrAllocCopy(paccept, acceptstring); + nextfield = paccept; + while ((field = HTNextTok(&nextfield, ",", "\"", NULL)) != NULL) { + double q; + + if (*field != '\0') { + /* Get the Charset */ + q = get_trans_q(cs_from, field); + if (q > bestq) { + bestq = q; + bestmime = field; + } + } + } + if (bestmime) { + if (!strcmp(bestmime, "*")) /* non-standard for HTML attribute.. */ + StrAllocCopy(*best_csname, "utf-8"); + else + StrAllocCopy(*best_csname, bestmime); + FREE(paccept); + if (bestq > 0) + return (UCGetLYhndl_byMIME(*best_csname)); + else + return (-1); + } + FREE(paccept); + return (-1); +} + +#ifdef USE_FILE_UPLOAD +static void load_a_file(const char *val_used, + bstring **result) +{ + FILE *fd; + size_t bytes; + char bfr[BUFSIZ + 1]; + + CTRACE((tfp, "Ok, about to convert \"%s\" to mime/thingy\n", val_used)); + + if (*val_used) { /* ignore empty form field */ + if ((fd = fopen(val_used, BIN_R)) == 0) { + HTAlert(gettext("Can't open file for uploading")); + } else { + while ((bytes = fread(bfr, sizeof(char), sizeof(bfr) - 1, fd)) != 0) { + HTSABCat(result, bfr, (int) bytes); + } + LYCloseInput(fd); + } + } +} + +static const char *guess_content_type(const char *filename) +{ + HTAtom *encoding; + const char *desc; + HTFormat format = HTFileFormat(filename, &encoding, &desc); + + return (format != 0 && non_empty(format->name)) + ? format->name + : STR_PLAINTEXT; +} +#endif /* USE_FILE_UPLOAD */ + +static void cannot_transcode(BOOL *had_warning, + const char *target_csname) +{ + if (*had_warning == NO) { + *had_warning = YES; + _user_message(CANNOT_TRANSCODE_FORM, + target_csname ? target_csname : "UNKNOWN"); + LYSleepAlert(); + } +} + +#define SPECIAL_8BIT 1 +#define SPECIAL_FORM 2 + +static unsigned check_form_specialchars(const char *value) +{ + unsigned result = 0; + const char *p; + + for (p = value; + non_empty(p) && (result != (SPECIAL_8BIT | SPECIAL_FORM)); + p++) { + if ((*p == HT_NON_BREAK_SPACE) || + (*p == HT_EN_SPACE) || + (*p == LY_SOFT_HYPHEN)) { + result |= SPECIAL_FORM; + } else if ((*p & 0x80) != 0) { + result |= SPECIAL_8BIT; + } + } + return result; +} + +/* + * Scan the given data, adding characters to the MIME-boundary to keep it from + * matching any part of the data. + */ +static void UpdateBoundary(char **Boundary, + bstring *data) +{ + size_t j; + size_t have = strlen(*Boundary); + size_t last = (size_t) BStrLen(data); + char *text = BStrData(data); + char *want = *Boundary; + + for (j = 0; (long) j <= (long) (last - have); ++j) { + if (want[0] == text[j] + && !memcmp(want, text + j, have)) { + char temp[2]; + + temp[0] = (char) (isdigit(UCH(text[have + j])) ? 'a' : '0'); + temp[1] = '\0'; + StrAllocCat(want, temp); + ++have; + } + } + *Boundary = want; +} + +/* + * Convert a string to base64 + */ +static char *convert_to_base64(const char *src, + size_t len) +{ +#define B64_LINE 76 + + static const char basis_64[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + char *dest; + size_t rlen; /* length of result string */ + unsigned char c1, c2, c3; + const char *eol; + char *r; + const char *str; + size_t eollen; + int chunk; + + str = src; + eol = "\n"; + eollen = 1; + + /* calculate the length of the result */ + rlen = (len + 2) / 3 * 4; /* encoded bytes */ + if (rlen) { + /* add space for EOL */ + rlen += ((rlen - 1) / B64_LINE + 1) * eollen; + } + + /* allocate a result buffer */ + if ((dest = (char *) malloc(rlen + 1)) == NULL) { + outofmem(__FILE__, "convert_to_base64"); + } + r = dest; + + /* encode */ + for (chunk = 0; len > 0; len -= 3, chunk++) { + if (chunk == (B64_LINE / 4)) { + const char *c = eol; + const char *e = eol + eollen; + + while (c < e) + *r++ = *c++; + chunk = 0; + } + c1 = UCH(*str++); + c2 = UCH(*str++); + *r++ = basis_64[c1 >> 2]; + *r++ = basis_64[((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4)]; + if (len > 2) { + c3 = UCH(*str++); + *r++ = basis_64[((c2 & 0xF) << 2) | ((c3 & 0xC0) >> 6)]; + *r++ = basis_64[c3 & 0x3F]; + } else if (len == 2) { + *r++ = basis_64[(c2 & 0xF) << 2]; + *r++ = '='; + } else { /* len == 1 */ + *r++ = '='; + *r++ = '='; + } + } + if (rlen) { + /* append eol to the result string */ + const char *c = eol; + const char *e = eol + eollen; + + while (c < e) + *r++ = *c++; + } + *r = '\0'; + + return dest; +} + +typedef enum { + NO_QUOTE /* no quoting needed */ + ,QUOTE_MULTI /* multipart */ + ,QUOTE_BASE64 /* encode as base64 */ + ,QUOTE_SPECIAL /* escape special characters only */ +} QuoteData; + +typedef struct { + int type; /* the type of this field */ + BOOL first; /* true if this begins a submission part */ + char *name; /* the name of this field */ + char *value; /* the nominal value of this field */ + bstring *data; /* its data, which is usually the same as the value */ + QuoteData quote; /* how to quote/translate the data */ +} PostData; + +static char *escape_or_quote_name(const char *name, + QuoteData quoting, + const char *MultipartContentType) +{ + char *escaped1 = NULL; + + switch (quoting) { + case NO_QUOTE: + StrAllocCopy(escaped1, name); + break; + case QUOTE_MULTI: + case QUOTE_BASE64: + StrAllocCopy(escaped1, "Content-Disposition: form-data"); + HTSprintf(&escaped1, "; name=\"%s\"", name); + if (MultipartContentType) + HTSprintf(&escaped1, MultipartContentType, STR_PLAINTEXT); + if (quoting == QUOTE_BASE64) + StrAllocCat(escaped1, "\r\nContent-Transfer-Encoding: base64"); + StrAllocCat(escaped1, "\r\n\r\n"); + break; + case QUOTE_SPECIAL: + escaped1 = HTEscapeSP(name, URL_XALPHAS); + break; + } + return escaped1; +} + +static char *escape_or_quote_value(const char *value, + QuoteData quoting) +{ + char *escaped2 = NULL; + + switch (quoting) { + case NO_QUOTE: + case QUOTE_MULTI: + StrAllocCopy(escaped2, NonNull(value)); + break; + case QUOTE_BASE64: + /* FIXME: this is redundant */ + escaped2 = convert_to_base64(value, strlen(value)); + break; + case QUOTE_SPECIAL: + escaped2 = HTEscapeSP(value, URL_XALPHAS); + break; + } + return escaped2; +} + +/* + * Check if we should encode the data in base64. We can, only if we're using + * a multipart content type. We should, if we're sending mail and the data + * contains long lines or nonprinting characters. + */ +static int check_if_base64_needed(int submit_method, + bstring *data) +{ + int width = 0; + BOOL printable = TRUE; + char *text = BStrData(data); + + if (text != 0) { + int col = 0; + int n; + int length = BStrLen(data); + + for (n = 0; n < length; ++n) { + int ch = UCH(text[n]); + + if (is8bits(ch) || ((ch < 32 && ch != '\n'))) { + CTRACE((tfp, "nonprintable %d:%#x\n", n, (unsigned) ch)); + printable = FALSE; + } + if (ch == '\n' || ch == '\r') { + if (width < col) + width = col; + col = 0; + } else { + ++col; + } + } + if (width < col) + width = col; + } + return !printable && ((submit_method == URL_MAIL_METHOD) && (width > 72)); +} + +PerFormInfo *HText_PerFormInfo(int number) +{ + return (PerFormInfo *) HTList_objectAt(HTMainText->forms, number - 1); +} + +/* + * HText_SubmitForm - generate submit data from form fields. + * For mailto forms, send the data. + * For other methods, set fields in structure pointed to by doc + * appropriately for next request. + * Returns 1 if *doc set appropriately for next request, + * 0 otherwise. - kw + */ +int HText_SubmitForm(FormInfo * submit_item, DocInfo *doc, + const char *link_name, + const char *link_value) +{ + BOOL had_chartrans_warning = NO; + BOOL have_accept_cs = NO; + BOOL success; + BOOLEAN PlainText = FALSE; + BOOLEAN SemiColon = FALSE; + BOOL skip_field = FALSE; + const char *out_csname; + const char *target_csname = NULL; + PerFormInfo *thisform; + PostData *my_data = NULL; + TextAnchor *anchor_ptr; + bstring *my_query = NULL; + char *Boundary = NULL; + char *MultipartContentType = NULL; + char *content_type_out = NULL; + char *copied_name_used = NULL; + char *copied_val_used = NULL; + char *escaped1 = NULL; + char *escaped2 = NULL; + char *last_textarea_name = NULL; + const char *name_used; + char *previous_blanks = NULL; + const char *val_used = ""; + int anchor_count = 0; + int anchor_limit = 0; + int form_number = submit_item->number; + int result = 0; + int target_cs = -1; + int textarea_lineno = 0; + unsigned form_is_special = 0; + + CTRACE((tfp, "SubmitForm\n link_name=%s\n link_value=%s\n", link_name, link_value)); + if (!HTMainText) + return 0; + + thisform = HText_PerFormInfo(form_number); + /* Sanity check */ + if (!thisform) { + CTRACE((tfp, "SubmitForm: form %d not in HTMainText's list!\n", + form_number)); + } else if (thisform->number != form_number) { + CTRACE((tfp, "SubmitForm: failed sanity check, %d!=%d !\n", + thisform->number, form_number)); + thisform = NULL; + } + + if (isEmpty(submit_item->submit_action)) { + CTRACE((tfp, "SubmitForm: no action given\n")); + return 0; + } + + /* + * If we're mailing, make sure it's a mailto ACTION. -FM + */ + if ((submit_item->submit_method == URL_MAIL_METHOD) && + !isMAILTO_URL(submit_item->submit_action)) { + HTAlert(BAD_FORM_MAILTO); + return 0; + } + + /* + * Check the ENCTYPE and set up the appropriate variables. -FM + */ + if (submit_item->submit_enctype && + !strncasecomp(submit_item->submit_enctype, STR_PLAINTEXT, 10)) { + /* + * Do not hex escape, and use physical newlines + * to separate name=value pairs. -FM + */ + PlainText = TRUE; + } else if (submit_item->submit_enctype && + !strncasecomp(submit_item->submit_enctype, + "application/sgml-form-urlencoded", 32)) { + /* + * Use semicolons instead of ampersands as the + * separators for name=value pairs. -FM + */ + SemiColon = TRUE; + } else if (submit_item->submit_enctype && + !strncasecomp(submit_item->submit_enctype, + "multipart/form-data", 19)) { + /* + * Use the multipart MIME format. Later we will ensure it does not + * occur within the content. + */ + StrAllocCopy(Boundary, "xnyLAaB03X"); + } + + /* + * Determine in what character encoding (aka. charset) we should + * submit. We call this target_cs and the MIME name for it + * target_csname. + * TODO: - actually use ACCEPT-CHARSET stuff from FORM + * TODO: - deal with list in ACCEPT-CHARSET, find a "best" + * charset to submit + */ + + /* Look at ACCEPT-CHARSET on the submitting field if we have one. */ + if (thisform && submit_item->accept_cs && + strcasecomp(submit_item->accept_cs, "UNKNOWN")) { + have_accept_cs = YES; + target_cs = find_best_target_cs(&thisform->thisacceptcs, + current_char_set, + submit_item->accept_cs); + } + /* Look at ACCEPT-CHARSET on form as a whole if submitting field + * didn't have one. */ + if (thisform && !have_accept_cs && thisform->accept_cs && + strcasecomp(thisform->accept_cs, "UNKNOWN")) { + have_accept_cs = YES; + target_cs = find_best_target_cs(&thisform->thisacceptcs, + current_char_set, + thisform->accept_cs); + } + if (have_accept_cs && (target_cs >= 0) && thisform->thisacceptcs) { + target_csname = thisform->thisacceptcs; + } + + if (target_cs < 0 && + non_empty(HTMainText->node_anchor->charset)) { + target_cs = UCGetLYhndl_byMIME(HTMainText->node_anchor->charset); + if (target_cs >= 0) { + target_csname = HTMainText->node_anchor->charset; + } else { + target_cs = UCLYhndl_for_unspec; /* always >= 0 */ + target_csname = LYCharSet_UC[target_cs].MIMEname; + } + } + if (target_cs < 0) { + target_cs = UCLYhndl_for_unspec; /* always >= 0 */ + } + + /* + * Go through list of anchors and get a "max." charset parameter - kw + */ + for (anchor_ptr = HTMainText->first_anchor; + anchor_ptr != NULL; + anchor_ptr = anchor_ptr->next) { + + if (anchor_ptr->link_type != INPUT_ANCHOR) + continue; + + if (anchor_ptr->input_field->number == form_number && + !anchor_ptr->input_field->disabled) { + + FormInfo *form_ptr = anchor_ptr->input_field; + char *val = (form_ptr->cp_submit_value != NULL + ? form_ptr->cp_submit_value + : form_ptr->value); + + unsigned field_is_special = check_form_specialchars(val); + unsigned name_is_special = check_form_specialchars(form_ptr->name); + + form_is_special = (field_is_special | name_is_special); + + if (field_is_special == 0) { + /* already ok */ + } else if (target_cs < 0) { + /* already confused */ + } else if ((field_is_special & SPECIAL_8BIT) == 0 + && (LYCharSet_UC[target_cs].enc == UCT_ENC_8859 + || (LYCharSet_UC[target_cs].like8859 & UCT_R_8859SPECL))) { + /* those specials will be trivial */ + } else if (UCNeedNotTranslate(form_ptr->value_cs, target_cs)) { + /* already ok */ + } else if (UCCanTranslateFromTo(form_ptr->value_cs, target_cs)) { + /* also ok */ + } else if (UCCanTranslateFromTo(target_cs, form_ptr->value_cs)) { + target_cs = form_ptr->value_cs; /* try this */ + target_csname = NULL; /* will be set after loop */ + } else { + target_cs = -1; /* don't know what to do */ + } + + /* Same for name */ + if (name_is_special == 0) { + /* already ok */ + } else if (target_cs < 0) { + /* already confused */ + } else if ((name_is_special & SPECIAL_8BIT) == 0 + && (LYCharSet_UC[target_cs].enc == UCT_ENC_8859 + || (LYCharSet_UC[target_cs].like8859 & UCT_R_8859SPECL))) { + /* those specials will be trivial */ + } else if (UCNeedNotTranslate(form_ptr->name_cs, target_cs)) { + /* already ok */ + } else if (UCCanTranslateFromTo(form_ptr->name_cs, target_cs)) { + /* also ok */ + } else if (UCCanTranslateFromTo(target_cs, form_ptr->name_cs)) { + target_cs = form_ptr->value_cs; /* try this */ + target_csname = NULL; /* will be set after loop */ + } else { + target_cs = -1; /* don't know what to do */ + } + + ++anchor_limit; + } else if (anchor_ptr->input_field->number > form_number) { + break; + } + } + + /* + * If we have input fields (we expect this), make an array of them so we + * can organize the data. + */ + if (anchor_limit != 0) { + my_data = typecallocn(PostData, (size_t) anchor_limit); + if (my_data == 0) + outofmem(__FILE__, "HText_SubmitForm"); + } + + if (target_csname == NULL) { + if (target_cs >= 0) { + if ((form_is_special & SPECIAL_8BIT) != 0) { + target_csname = LYCharSet_UC[target_cs].MIMEname; + } else if ((form_is_special & SPECIAL_FORM) != 0) { + target_csname = LYCharSet_UC[target_cs].MIMEname; + } else { + target_csname = "us-ascii"; + } + } else { + target_csname = "us-ascii"; + target_cs = UCLYhndl_for_unspec; /* always >= 0 */ + } + } else if (target_cs < 0) { + target_cs = UCLYhndl_for_unspec; /* always >= 0 */ + } + + if (submit_item->submit_method == URL_GET_METHOD && Boundary == NULL) { + char *temp = NULL; + + StrAllocCopy(temp, submit_item->submit_action); + /* + * Method is GET. Clip out any anchor in the current URL. + */ + strtok(temp, "#"); + /* + * Clip out any old query in the current URL. + */ + strtok(temp, "?"); + /* + * Add the lead question mark for the new URL. + */ + StrAllocCat(temp, "?"); + BStrCat0(my_query, temp); + free(temp); + } else { + /* + * We are submitting POST content to a server, + * so load content_type_out. This will be put in + * the post_content_type element if all goes well. -FM, kw + */ + if (SemiColon == TRUE) { + StrAllocCopy(content_type_out, + "application/sgml-form-urlencoded"); + } else if (PlainText == TRUE) { + StrAllocCopy(content_type_out, + STR_PLAINTEXT); + } else if (Boundary != NULL) { + StrAllocCopy(content_type_out, + "multipart/form-data"); + } else { + StrAllocCopy(content_type_out, + "application/x-www-form-urlencoded"); + } + + /* + * If the ENCTYPE is not multipart/form-data, append the + * charset we'll be sending to the post_content_type, IF + * (1) there was an explicit accept-charset attribute, OR + * (2) we have 8-bit or special chars, AND the document had + * an explicit (recognized and accepted) charset parameter, + * AND it or target_csname is different from iso-8859-1, + * OR + * (3) we have 8-bit or special chars, AND the document had + * no explicit (recognized and accepted) charset parameter, + * AND target_cs is different from the currently effective + * assumed charset (which should have been set by the user + * so that it reflects what the server is sending, if the + * document is rendered correctly). + * For multipart/form-data the equivalent will be done later, + * separately for each form field. - kw + */ + if (have_accept_cs + || ((form_is_special & SPECIAL_8BIT) != 0 + || (form_is_special & SPECIAL_FORM) != 0)) { + if (target_cs >= 0 && target_csname) { + if (Boundary == NULL) { + if ((HTMainText->node_anchor->charset && + (strcmp(HTMainText->node_anchor->charset, + "iso-8859-1") || + strcmp(target_csname, "iso-8859-1"))) || + (!HTMainText->node_anchor->charset && + target_cs != UCLYhndl_for_unspec)) { + HTSprintf(&content_type_out, "; charset=%s", target_csname); + } + } + } else { + cannot_transcode(&had_chartrans_warning, target_csname); + } + } + } + + out_csname = target_csname; + + /* + * Build up a list of the input fields and their associated values. + */ + for (anchor_ptr = HTMainText->first_anchor; + anchor_ptr != NULL; + anchor_ptr = anchor_ptr->next) { + + if (anchor_ptr->link_type != INPUT_ANCHOR) + continue; + + if (anchor_ptr->input_field->number == form_number && + !anchor_ptr->input_field->disabled) { + + FormInfo *form_ptr = anchor_ptr->input_field; + int out_cs; + QuoteData quoting = (PlainText + ? NO_QUOTE + : (Boundary + ? QUOTE_MULTI + : QUOTE_SPECIAL)); + + assert(my_data != NULL); + + if (form_ptr->type != F_TEXTAREA_TYPE) + textarea_lineno = 0; + + CTRACE((tfp, "SubmitForm[%d/%d]: ", + anchor_count + 1, anchor_limit)); + + name_used = NonNull(form_ptr->name); + + switch (form_ptr->type) { + case F_RESET_TYPE: + CTRACE((tfp, "reset\n")); + break; +#ifdef USE_FILE_UPLOAD + case F_FILE_TYPE: + val_used = NonNull(form_ptr->value); + CTRACE((tfp, "I will submit \"%s\" (from %s)\n", + val_used, name_used)); + break; +#endif + case F_SUBMIT_TYPE: + case F_TEXT_SUBMIT_TYPE: + case F_IMAGE_SUBMIT_TYPE: + if (!(non_empty(form_ptr->name) && + !strcmp(form_ptr->name, link_name))) { + CTRACE((tfp, "skipping submit field with ")); + CTRACE((tfp, "name \"%s\" for link_name \"%s\", %s.\n", + form_ptr->name ? form_ptr->name : "???", + link_name ? link_name : "???", + non_empty(form_ptr->name) ? + "not current link" : "no field name")); + break; + } + if (!(form_ptr->type == F_TEXT_SUBMIT_TYPE || + (non_empty(form_ptr->value) && + !strcmp(form_ptr->value, link_value)))) { + CTRACE((tfp, "skipping submit field with ")); + CTRACE((tfp, "name \"%s\" for link_name \"%s\", %s!\n", + form_ptr->name ? form_ptr->name : "???", + link_name ? link_name : "???", + "values are different")); + break; + } + /* FALLTHRU */ + case F_RADIO_TYPE: + case F_CHECKBOX_TYPE: + case F_TEXTAREA_TYPE: + case F_PASSWORD_TYPE: + case F_TEXT_TYPE: + case F_OPTION_LIST_TYPE: + case F_HIDDEN_TYPE: + /* + * Be sure to actually look at the option submit value. + */ + if (form_ptr->cp_submit_value != NULL) { + val_used = form_ptr->cp_submit_value; + } else { + val_used = form_ptr->value; + } + + /* + * Charset-translate value now, because we need to know the + * charset parameter for multipart bodyparts. - kw + */ + if (check_form_specialchars(val_used) != 0) { + /* We should translate back. */ + StrAllocCopy(copied_val_used, val_used); + success = FALSE; + if (HTCJK == JAPANESE) { + if ((0 <= target_cs) && + !strcmp(LYCharSet_UC[target_cs].MIMEname, "euc-jp")) { + TO_EUC((const unsigned char *) val_used, + (unsigned char *) copied_val_used); + success = YES; + } else if ((0 <= target_cs) && + !strcmp(LYCharSet_UC[target_cs].MIMEname, + "shift_jis")) { + TO_SJIS((const unsigned char *) val_used, + (unsigned char *) copied_val_used); + success = YES; + } + } + if (!success) { + success = LYUCTranslateBackFormData(&copied_val_used, + form_ptr->value_cs, + target_cs, PlainText); + } + CTRACE((tfp, "field \"%s\" %d %s -> %d %s %s\n", + NonNull(form_ptr->name), + form_ptr->value_cs, + ((form_ptr->value_cs >= 0) + ? LYCharSet_UC[form_ptr->value_cs].MIMEname + : "???"), + target_cs, + target_csname ? target_csname : "???", + success ? "OK" : "FAILED")); + if (success) { + val_used = copied_val_used; + } + } else { /* We can use the value directly. */ + CTRACE((tfp, "field \"%s\" %d %s OK\n", + NonNull(form_ptr->name), + target_cs, + target_csname ? target_csname : "???")); + success = YES; + } + if (!success) { + cannot_transcode(&had_chartrans_warning, target_csname); + out_cs = form_ptr->value_cs; + } else { + out_cs = target_cs; + } + if (out_cs >= 0) + out_csname = LYCharSet_UC[out_cs].MIMEname; + if (Boundary) { + StrAllocCopy(MultipartContentType, + "\r\nContent-Type: %s"); + if (!success && form_ptr->value_cs < 0) { + /* This is weird. */ + out_csname = "UNKNOWN-8BIT"; + } else if (!success) { + target_csname = NULL; + } else { + if (!target_csname) { + target_csname = LYCharSet_UC[target_cs].MIMEname; + } + } + if (strcmp(out_csname, "iso-8859-1")) + HTSprintf(&MultipartContentType, "; charset=%s", out_csname); + } + + /* + * Charset-translate name now, because we need to know the + * charset parameter for multipart bodyparts. - kw + */ + if (form_ptr->type == F_TEXTAREA_TYPE) { + textarea_lineno++; + if (textarea_lineno > 1 && + last_textarea_name && form_ptr->name && + !strcmp(last_textarea_name, form_ptr->name)) { + break; + } + } + + if (check_form_specialchars(name_used) != 0) { + /* We should translate back. */ + StrAllocCopy(copied_name_used, name_used); + success = LYUCTranslateBackFormData(&copied_name_used, + form_ptr->name_cs, + target_cs, PlainText); + CTRACE((tfp, "name \"%s\" %d %s -> %d %s %s\n", + NonNull(form_ptr->name), + form_ptr->name_cs, + ((form_ptr->name_cs >= 0) + ? LYCharSet_UC[form_ptr->name_cs].MIMEname + : "???"), + target_cs, + target_csname ? target_csname : "???", + success ? "OK" : "FAILED")); + if (success) { + name_used = copied_name_used; + } + if (Boundary) { + if (!success) { + StrAllocCopy(MultipartContentType, ""); + target_csname = NULL; + } else { + if (!target_csname) + target_csname = LYCharSet_UC[target_cs].MIMEname; + } + } + } else { /* We can use the name directly. */ + CTRACE((tfp, "name \"%s\" %d %s OK\n", + NonNull(form_ptr->name), + target_cs, + target_csname ? target_csname : "???")); + success = YES; + if (Boundary) { + StrAllocCopy(copied_name_used, name_used); + } + } + if (!success) { + cannot_transcode(&had_chartrans_warning, target_csname); + } + if (Boundary) { + /* + * According to RFC 1867, Non-ASCII field names + * "should be encoded according to the prescriptions + * of RFC 1522 [...]. I don't think RFC 1522 actually + * is meant to apply to parameters like this, and it + * is unknown whether any server would make sense of + * it, so for now just use some quoting/escaping and + * otherwise leave 8-bit values as they are. + * Non-ASCII characters in form field names submitted + * as multipart/form-data can only occur if the form + * provider specifically asked for it anyway. - kw + */ + HTMake822Word(&copied_name_used, FALSE); + name_used = copied_name_used; + } + + break; + default: + CTRACE((tfp, "What type is %d?\n", form_ptr->type)); + break; + } + + skip_field = FALSE; + my_data[anchor_count].first = TRUE; + my_data[anchor_count].type = form_ptr->type; + + /* + * Using the values of 'name_used' and 'val_used' computed in the + * previous case-statement, compute the 'first' and 'data' values + * for the current input field. + */ + switch (form_ptr->type) { + + default: + skip_field = TRUE; + break; + +#ifdef USE_FILE_UPLOAD + case F_FILE_TYPE: + load_a_file(val_used, &(my_data[anchor_count].data)); + break; +#endif /* USE_FILE_UPLOAD */ + + case F_SUBMIT_TYPE: + case F_TEXT_SUBMIT_TYPE: + case F_IMAGE_SUBMIT_TYPE: + if ((non_empty(form_ptr->name) && + !strcmp(form_ptr->name, link_name)) && + (form_ptr->type == F_TEXT_SUBMIT_TYPE || + (non_empty(form_ptr->value) && + !strcmp(form_ptr->value, link_value)))) { + ; + } else { + skip_field = TRUE; + } + break; + + case F_RADIO_TYPE: + case F_CHECKBOX_TYPE: + /* + * Only add if selected. + */ + if (form_ptr->num_value) { + ; + } else { + skip_field = TRUE; + } + break; + + case F_TEXTAREA_TYPE: + if (!last_textarea_name || + strcmp(last_textarea_name, form_ptr->name)) { + textarea_lineno = 1; + last_textarea_name = form_ptr->name; + } else { + my_data[anchor_count].first = FALSE; + } + break; + + case F_PASSWORD_TYPE: + case F_TEXT_TYPE: + case F_OPTION_LIST_TYPE: + case F_HIDDEN_TYPE: + break; + } + + /* + * If we did not decide to skip the current field, populate the + * values in the array for it. + */ + if (!skip_field) { + StrAllocCopy(my_data[anchor_count].name, name_used); + StrAllocCopy(my_data[anchor_count].value, val_used); + if (my_data[anchor_count].data == 0) + BStrCat0(my_data[anchor_count].data, val_used); + my_data[anchor_count].quote = quoting; + if (quoting == QUOTE_MULTI + && check_if_base64_needed(submit_item->submit_method, + my_data[anchor_count].data)) { + CTRACE((tfp, "will encode as base64\n")); + my_data[anchor_count].quote = QUOTE_BASE64; + escaped2 = + convert_to_base64(BStrData(my_data[anchor_count].data), + (size_t) + BStrLen(my_data[anchor_count].data)); + BStrCopy0(my_data[anchor_count].data, escaped2); + FREE(escaped2); + } + } + ++anchor_count; + + FREE(copied_name_used); + FREE(copied_val_used); + + } else if (anchor_ptr->input_field->number > form_number) { + break; + } + } + + FREE(copied_name_used); + + if (my_data != 0) { + BOOL first_one = TRUE; + + /* + * If we're using a MIME-boundary, make it unique. + */ + if (content_type_out != 0 && Boundary != 0) { + Boundary = 0; + StrAllocCopy(Boundary, "LYNX"); + for (anchor_count = 0; anchor_count < anchor_limit; ++anchor_count) { + if (my_data[anchor_count].data != 0) { + UpdateBoundary(&Boundary, my_data[anchor_count].data); + } + } + HTSprintf(&content_type_out, "; boundary=%s", Boundary); + } + + for (anchor_count = 0; anchor_count < anchor_limit; ++anchor_count) { + + if (my_data[anchor_count].name != 0 + && my_data[anchor_count].value != 0) { + + CTRACE((tfp, + "processing [%d:%d] name=%s(first:%d, value=%s, data=%p)\n", + anchor_count + 1, + anchor_limit, + NonNull(my_data[anchor_count].name), + my_data[anchor_count].first, + NonNull(my_data[anchor_count].value), + (void *) my_data[anchor_count].data)); + + if (my_data[anchor_count].first) { + if (first_one) { + if (Boundary) { + HTBprintf(&my_query, "--%s\r\n", Boundary); + } + first_one = FALSE; + } else { + if (PlainText) { + BStrCat0(my_query, "\n"); + } else if (SemiColon) { + BStrCat0(my_query, ";"); + } else if (Boundary) { + HTBprintf(&my_query, "\r\n--%s\r\n", Boundary); + } else { + BStrCat0(my_query, "&"); + } + } + } + + /* append a null to the string */ + HTSABCat(&(my_data[anchor_count].data), "", 1); + name_used = my_data[anchor_count].name; + val_used = my_data[anchor_count].value; + + } else { + /* there is no data to send */ + continue; + } + + switch (my_data[anchor_count].type) { + case F_TEXT_TYPE: + case F_PASSWORD_TYPE: + case F_OPTION_LIST_TYPE: + case F_HIDDEN_TYPE: + escaped1 = escape_or_quote_name(my_data[anchor_count].name, + my_data[anchor_count].quote, + MultipartContentType); + + escaped2 = escape_or_quote_value(val_used, + my_data[anchor_count].quote); + + HTBprintf(&my_query, + "%s%s%s%s%s", + escaped1, + (Boundary ? "" : "="), + (PlainText ? "\n" : ""), + escaped2, + ((PlainText && *escaped2) ? "\n" : "")); + break; + case F_CHECKBOX_TYPE: + case F_RADIO_TYPE: + escaped1 = escape_or_quote_name(my_data[anchor_count].name, + my_data[anchor_count].quote, + MultipartContentType); + + escaped2 = escape_or_quote_value(val_used, + my_data[anchor_count].quote); + + HTBprintf(&my_query, + "%s%s%s%s%s", + escaped1, + (Boundary ? "" : "="), + (PlainText ? "\n" : ""), + escaped2, + ((PlainText && *escaped2) ? "\n" : "")); + break; + case F_SUBMIT_TYPE: + case F_TEXT_SUBMIT_TYPE: + case F_IMAGE_SUBMIT_TYPE: + /* + * If it has a non-zero length name (e.g., because + * its IMAGE_SUBMIT_TYPE is to be handled homologously + * to an image map, or a SUBMIT_TYPE in a set of + * multiple submit buttons, or a single type="text" + * that's been converted to a TEXT_SUBMIT_TYPE), + * include the name=value pair, or fake name.x=0 and + * name.y=0 pairs for IMAGE_SUBMIT_TYPE. -FM + */ + escaped1 = escape_or_quote_name(my_data[anchor_count].name, + my_data[anchor_count].quote, + MultipartContentType); + + escaped2 = escape_or_quote_value(val_used, + my_data[anchor_count].quote); + + if (my_data[anchor_count].type == F_IMAGE_SUBMIT_TYPE) { + /* + * It's a clickable image submit button. Fake a 0,0 + * coordinate pair, which typically returns the image's + * default. -FM + */ + if (Boundary) { + *(StrChr(escaped1, '=') + 1) = '\0'; + HTBprintf(&my_query, + "%s\"%s.x\"\r\n\r\n0\r\n--%s\r\n%s\"%s.y\"\r\n\r\n0", + escaped1, + my_data[anchor_count].name, + Boundary, + escaped1, + my_data[anchor_count].name); + } else { + HTBprintf(&my_query, + "%s.x=0%s%s.y=0%s", + escaped1, + (PlainText ? + "\n" : (SemiColon ? + ";" : "&")), + escaped1, + ((PlainText && *escaped1) ? + "\n" : "")); + } + } else { + /* + * It's a standard submit button. Use the name=value + * pair. = FM + */ + HTBprintf(&my_query, + "%s%s%s%s%s", + escaped1, + (Boundary ? "" : "="), + (PlainText ? "\n" : ""), + escaped2, + ((PlainText && *escaped2) ? "\n" : "")); + } + break; + case F_RESET_TYPE: + /* ignore */ + break; + case F_TEXTAREA_TYPE: + escaped2 = escape_or_quote_value(val_used, + my_data[anchor_count].quote); + + if (my_data[anchor_count].first) { + /* + * Names are different so this is the first textarea or a + * different one from any before it. + */ + if (PlainText) { + FREE(previous_blanks); + } else if (Boundary) { + StrAllocCopy(previous_blanks, "\r\n"); + } else { + StrAllocCopy(previous_blanks, "%0d%0a"); + } + escaped1 = escape_or_quote_name(name_used, + my_data[anchor_count].quote, + MultipartContentType); + + HTBprintf(&my_query, + "%s%s%s%s%s", + escaped1, + (Boundary ? "" : "="), + (PlainText ? "\n" : ""), + escaped2, + ((PlainText && *escaped2) ? "\n" : "")); + } else { + const char *marker = (PlainText + ? "\n" + : (Boundary + ? "\r\n" + : "%0d%0a")); + + /* + * This is a continuation of a previous textarea. + */ + if (escaped2[0] != '\0') { + if (previous_blanks) { + BStrCat0(my_query, previous_blanks); + FREE(previous_blanks); + } + BStrCat0(my_query, escaped2); + if (PlainText || Boundary) + BStrCat0(my_query, marker); + else + StrAllocCopy(previous_blanks, marker); + } else { + StrAllocCat(previous_blanks, marker); + } + } + break; + case F_RANGE_TYPE: + /* not implemented */ + break; +#ifdef USE_FILE_UPLOAD + case F_FILE_TYPE: + if (PlainText) { + StrAllocCopy(escaped1, my_data[anchor_count].name); + } else if (Boundary) { + const char *t = guess_content_type(val_used); + char *copied_fname = NULL; + + StrAllocCopy(escaped1, "Content-Disposition: form-data"); + HTSprintf(&escaped1, "; name=\"%s\"", + my_data[anchor_count].name); + + StrAllocCopy(copied_fname, val_used); + HTMake822Word(&copied_fname, FALSE); + HTSprintf(&escaped1, "; filename=\"%s\"", copied_fname); + FREE(copied_fname); + + /* Should we take into account the encoding? */ + HTSprintf(&escaped1, "\r\nContent-Type: %s", t); + if (my_data[anchor_count].quote == QUOTE_BASE64) + StrAllocCat(escaped1, + "\r\nContent-Transfer-Encoding: base64"); + StrAllocCat(escaped1, "\r\n\r\n"); + } else { + escaped1 = HTEscapeSP(my_data[anchor_count].name, URL_XALPHAS); + } + + HTBprintf(&my_query, + "%s%s%s", + escaped1, + (Boundary ? "" : "="), + (PlainText ? "\n" : "")); + /* + * If we have anything more than the trailing null we added, + * append the file-data to the query. + */ + if (BStrLen(my_data[anchor_count].data) > 1) { + HTSABCat(&my_query, + BStrData(my_data[anchor_count].data), + BStrLen(my_data[anchor_count].data) - 1); + if (PlainText) + HTBprintf(&my_query, "\n"); + } + break; +#endif /* USE_FILE_UPLOAD */ + case F_KEYGEN_TYPE: + case F_BUTTON_TYPE: + /* not implemented */ + break; + } + + FREE(escaped1); + FREE(escaped2); + } + if (Boundary) { + HTBprintf(&my_query, "\r\n--%s--\r\n", Boundary); + } + if (TRACE) { + CTRACE((tfp, "Query %d{", BStrLen(my_query))); + trace_bstring(my_query); + CTRACE((tfp, "}\n")); + } + } + + if (submit_item->submit_method == URL_MAIL_METHOD) { + HTUserMsg2(gettext("Submitting %s"), submit_item->submit_action); + HTSABCat(&my_query, "", 1); /* append null */ + mailform((submit_item->submit_action + 7), + (isEmpty(submit_item->submit_title) + ? NonNull(HText_getTitle()) + : submit_item->submit_title), + BStrData(my_query), + content_type_out); + result = 0; + BStrFree(my_query); + FREE(content_type_out); + } else { + _statusline(SUBMITTING_FORM); + + /* + * File-URLs (whether via GET or POST) cannot provide search queries. + * The relevant RFCs 1630, 1738 are silent on what to do with + * unexpected query parameters in a file-URL. + * + * Internet Explorer trims the query string here (after all, a "?" is + * not a legal part of a Windows filename), and other browsers copy the + * behavior. We do this for compatibility, in case someone cares. + */ + if (my_query != 0 && + my_query->len > 5 && + !strncmp(my_query->str, "file:", (size_t) 5)) { + strtok(my_query->str, "?"); + } + if (submit_item->submit_method == URL_POST_METHOD || Boundary) { + LYFreePostData(doc); + doc->post_data = my_query; + doc->post_content_type = content_type_out; /* don't free c_t_out */ + CTRACE((tfp, "GridText - post_data set:\n%s\n", content_type_out)); + StrAllocCopy(doc->address, submit_item->submit_action); + } else { /* GET_METHOD */ + HTSABCat(&my_query, "", 1); /* append null */ + StrAllocCopy(doc->address, BStrData(my_query)); + LYFreePostData(doc); + FREE(content_type_out); + HTSABFree(&my_query); + } + result = 1; + } + + FREE(MultipartContentType); + FREE(previous_blanks); + FREE(Boundary); + if (my_data != 0) { + for (anchor_count = 0; anchor_count < anchor_limit; ++anchor_count) { + FREE(my_data[anchor_count].name); + FREE(my_data[anchor_count].value); + BStrFree(my_data[anchor_count].data); + } + FREE(my_data); + } + + return (result); +} + +void HText_DisableCurrentForm(void) +{ + TextAnchor *anchor_ptr; + + HTFormDisabled = TRUE; + if (HTMainText != NULL) { + /* + * Go through list of anchors and set the disabled flag. + */ + for (anchor_ptr = HTMainText->first_anchor; + anchor_ptr != NULL; + anchor_ptr = anchor_ptr->next) { + + if (anchor_ptr->link_type == INPUT_ANCHOR && + anchor_ptr->input_field->number == HTFormNumber) { + + anchor_ptr->input_field->disabled = TRUE; + } + } + } + return; +} + +void HText_ResetForm(FormInfo * form) +{ + TextAnchor *anchor_ptr; + + _statusline(RESETTING_FORM); + if (HTMainText == 0) + return; + + /* + * Go through list of anchors and reset values. + */ + for (anchor_ptr = HTMainText->first_anchor; + anchor_ptr != NULL; + anchor_ptr = anchor_ptr->next) { + if (anchor_ptr->link_type == INPUT_ANCHOR) { + if (anchor_ptr->input_field->number == form->number) { + + if (anchor_ptr->input_field->type == F_RADIO_TYPE || + anchor_ptr->input_field->type == F_CHECKBOX_TYPE) { + + if (anchor_ptr->input_field->orig_value[0] == '0') + anchor_ptr->input_field->num_value = 0; + else + anchor_ptr->input_field->num_value = 1; + + } else if (anchor_ptr->input_field->type == + F_OPTION_LIST_TYPE) { + anchor_ptr->input_field->value = + anchor_ptr->input_field->orig_value; + + anchor_ptr->input_field->cp_submit_value = + anchor_ptr->input_field->orig_submit_value; + + } else { + StrAllocCopy(anchor_ptr->input_field->value, + anchor_ptr->input_field->orig_value); + } + } else if (anchor_ptr->input_field->number > form->number) { + break; + } + } + } +} + +/* + * This function is called before reloading/reparsing current document to find + * whether any forms content was changed by user so any information will be + * lost. + */ +BOOLEAN HText_HaveUserChangedForms(HText *text) +{ + TextAnchor *anchor_ptr; + + if (text == 0) + return FALSE; + + /* + * Go through list of anchors to check if any value was changed. + * This code based on HText_ResetForm() + */ + for (anchor_ptr = text->first_anchor; + anchor_ptr != NULL; + anchor_ptr = anchor_ptr->next) { + if (anchor_ptr->link_type == INPUT_ANCHOR) { + + if (anchor_ptr->input_field->type == F_RADIO_TYPE || + anchor_ptr->input_field->type == F_CHECKBOX_TYPE) { + + if ((anchor_ptr->input_field->orig_value[0] == '0' && + anchor_ptr->input_field->num_value == 1) || + (anchor_ptr->input_field->orig_value[0] != '0' && + anchor_ptr->input_field->num_value == 0)) + return TRUE; + + } else if (anchor_ptr->input_field->type == F_OPTION_LIST_TYPE) { + if (strcmp(anchor_ptr->input_field->value, + anchor_ptr->input_field->orig_value)) + return TRUE; + + if (strcmp(anchor_ptr->input_field->cp_submit_value, + anchor_ptr->input_field->orig_submit_value)) + return TRUE; + + } else { + if (strcmp(anchor_ptr->input_field->value, + anchor_ptr->input_field->orig_value)) + return TRUE; + } + } + } + return FALSE; +} + +void HText_activateRadioButton(FormInfo * form) +{ + TextAnchor *anchor_ptr; + int form_number = form->number; + + if (!HTMainText) + return; + for (anchor_ptr = HTMainText->first_anchor; + anchor_ptr != NULL; + anchor_ptr = anchor_ptr->next) { + if (anchor_ptr->link_type == INPUT_ANCHOR && + anchor_ptr->input_field->type == F_RADIO_TYPE) { + + if (anchor_ptr->input_field->number == form_number) { + + /* if it has the same name and its on */ + if (!strcmp(anchor_ptr->input_field->name, form->name) && + anchor_ptr->input_field->num_value) { + anchor_ptr->input_field->num_value = 0; + break; + } + } else if (anchor_ptr->input_field->number > form_number) { + break; + } + + } + } + + form->num_value = 1; +} + +#ifdef LY_FIND_LEAKS +/* + * Purpose: Free all currently loaded HText objects in memory. + * Arguments: void + * Return Value: void + * Remarks/Portability/Dependencies/Restrictions: + * Usage of this function should really be limited to program + * termination. + * Revision History: + * 05-27-94 created Lynx 2-3-1 Garrett Arch Blythe + */ +static void free_all_texts(void) +{ + HText *cur = NULL; + + if (!loaded_texts) + return; + + /* + * Simply loop through the loaded texts list killing them off. + */ + while (loaded_texts && !HTList_isEmpty(loaded_texts)) { + if ((cur = (HText *) HTList_removeLastObject(loaded_texts)) != NULL) { + HText_free(cur); + } + } + + /* + * Get rid of the text list. + */ + if (loaded_texts) { + HTList_delete(loaded_texts); + } + + /* + * Insurance for bad HTML. + */ + FREE(HTCurSelectGroup); + FREE(HTCurSelectGroupSize); + FREE(HTCurSelectedOptionValue); + PerFormInfo_free(HTCurrentForm); + + return; +} +#endif /* LY_FIND_LEAKS */ + +/* + * stub_HTAnchor_address is like HTAnchor_address, but it returns the + * parent address for child links. This is only useful for traversal's + * where one does not want to index a text file N times, once for each + * of N internal links. Since the parent link has already been taken, + * it won't go again, hence the (incorrect) links won't cause problems. + */ +char *stub_HTAnchor_address(HTAnchor * me) +{ + char *addr = NULL; + + if (me) + StrAllocCopy(addr, me->parent->address); + return addr; +} + +void HText_setToolbar(HText *text) +{ + if (text) + text->toolbar = TRUE; + return; +} + +BOOL HText_hasToolbar(HText *text) +{ + return (BOOL) ((text && text->toolbar) ? TRUE : FALSE); +} + +void HText_setNoCache(HText *text) +{ + if (text) + text->no_cache = TRUE; + return; +} + +BOOL HText_hasNoCacheSet(HText *text) +{ + return (BOOL) ((text && text->no_cache) ? TRUE : FALSE); +} + +BOOL HText_hasUTF8OutputSet(HText *text) +{ + return (BOOL) ((text && text->T.output_utf8) ? TRUE : FALSE); +} + +/* + * Check charset and set the kcode element. -FM + * Info on the input charset may be passed in in two forms, + * as a string (if given explicitly) and as a pointer to + * a LYUCcharset (from chartrans mechanism); either can be NULL. + * For Japanese the kcode will be reset at a space or explicit + * line or paragraph break, so what we set here may not last for + * long. It's potentially more important not to set HTCJK to + * NOCJK unless we are sure. - kw + */ +void HText_setKcode(HText *text, const char *charset, + LYUCcharset *p_in) +{ + BOOL charset_explicit; + + if (!text) + return; + + /* + * Check whether we have some kind of info. - kw + */ + if (!charset && !p_in) { + return; + } + charset_explicit = (BOOLEAN) (charset ? TRUE : FALSE); + /* + * If no explicit charset string, use the implied one. - kw + */ + if (isEmpty(charset)) { + charset = p_in->MIMEname; + } + /* + * Check whether we have a specified charset. -FM + */ + if (isEmpty(charset)) { + return; + } + + /* + * We've included the charset, and not forced a download offer, + * only if the currently selected character set can handle it, + * so check the charset value and set the text->kcode element + * appropriately. -FM + */ + /* If charset isn't specified explicitly nor assumed, + * p_in->MIMEname would be set as display charset. + * So text->kcode sholud be set as SJIS or EUC here only if charset + * is specified explicitly, otherwise text->kcode would cause + * mishandling Japanese strings. -- TH + */ + if (charset_explicit && (!strcmp(charset, "shift_jis") || + !strcmp(charset, "x-sjis") || /* 1997/11/28 (Fri) 18:11:33 */ + !strcmp(charset, "x-shift-jis"))) { + text->kcode = SJIS; + } else if (charset_explicit +#ifdef USE_JAPANESEUTF8_SUPPORT + && strcmp(charset, "utf-8") +#endif + && ((p_in && (p_in->enc == UCT_ENC_CJK)) || + !strcmp(charset, "x-euc") || /* 1997/11/28 (Fri) 18:11:24 */ + !strcmp(charset, "euc-jp") || + !StrNCmp(charset, "x-euc-", 6) || + !strcmp(charset, "euc-kr") || + !strcmp(charset, "iso-2022-kr") || + !strcmp(charset, "big5") || + !strcmp(charset, "cn-big5") || + !strcmp(charset, "euc-cn") || + !strcmp(charset, "gb2312") || + !StrNCmp(charset, "cn-gb", 5) || + !strcmp(charset, "iso-2022-cn"))) { + text->kcode = EUC; + } else { + /* + * If we get to here, it's not CJK, so disable that if + * it is enabled. But only if we are quite sure. -FM & kw + */ + text->kcode = NOKANJI; + if (IS_CJK_TTY) { + if (!p_in || ((p_in->enc != UCT_ENC_CJK) +#ifdef USE_JAPANESEUTF8_SUPPORT + && (p_in->enc != UCT_ENC_UTF8) +#endif + )) { + HTCJK = NOCJK; + } + } + } + + if (charset_explicit +#ifdef USE_JAPANESEUTF8_SUPPORT + && strcmp(charset, "utf-8") +#endif + ) { + text->specified_kcode = text->kcode; + } else { + if (UCAssume_MIMEcharset) { + if (!strcmp(UCAssume_MIMEcharset, "euc-jp")) + text->kcode = text->specified_kcode = EUC; + else if (!strcmp(UCAssume_MIMEcharset, "shift_jis")) + text->kcode = text->specified_kcode = SJIS; + } + } + + return; +} + +/* + * Set a permissible split at the current end of the last line. -FM + */ +void HText_setBreakPoint(HText *text) +{ + if (!text) + return; + + /* + * Can split here. -FM + */ + text->permissible_split = text->last_line->size; + + return; +} + +/* + * This function determines whether a document which + * would be sought via the a URL that has a fragment + * directive appended is otherwise identical to the + * currently loaded document, and if so, returns + * FALSE, so that any no_cache directives can be + * overridden "safely", on the grounds that we are + * simply acting on the equivalent of a paging + * command. Otherwise, it returns TRUE, i.e, that + * the target document might differ from the current, + * based on any caching directives or analyses which + * claimed or suggested this. -FM + */ +BOOL HText_AreDifferent(HTParentAnchor *anchor, + const char *full_address) +{ + HTParentAnchor *MTanc; + char *MTaddress; + char *MTpound; + + /* + * Do we have a loaded document and both + * arguments for this function? + */ + if (!(HTMainText && anchor && full_address)) + return TRUE; + + /* + * Do we have both URLs? + */ + MTanc = HTMainText->node_anchor; + if (!(MTanc->address && anchor->address)) + return (TRUE); + + /* + * Do we have a fragment associated with the target? + */ + if (findPoundSelector(full_address) == NULL) + return (TRUE); + + /* + * Always treat client-side image map menus + * as potentially stale, so we'll create a + * fresh menu from the LynxMaps HTList. + */ + if (isLYNXIMGMAP(anchor->address)) + return (TRUE); + + /* + * Do the docs differ in the type of request? + */ + if (MTanc->isHEAD != anchor->isHEAD) + return (TRUE); + + /* + * Are the actual URLs different, after factoring + * out a "LYNXIMGMAP:" leader in the MainText URL + * and its fragment, if present? + */ + MTaddress = (isLYNXIMGMAP(MTanc->address) + ? MTanc->address + LEN_LYNXIMGMAP + : MTanc->address); + MTpound = trimPoundSelector(MTaddress); + if (strcmp(MTaddress, anchor->address)) { + restorePoundSelector(MTpound); + return (TRUE); + } + restorePoundSelector(MTpound); + + /* + * If the MainText is not an image map menu, + * do the docs have different POST contents? + */ + if (MTaddress == MTanc->address) { + if (MTanc->post_data) { + if (anchor->post_data) { + if (!BINEQ(MTanc->post_data, anchor->post_data)) { + /* + * Both have contents, and they differ. + */ + return (TRUE); + } + } else { + /* + * The loaded document has content, but the + * target doesn't, so they're different. + */ + return (TRUE); + } + } else if (anchor->post_data) { + /* + * The loaded document does not have content, but + * the target does, so they're different. + */ + return (TRUE); + } + } + + /* + * We'll assume the target is a position in the currently + * displayed document, and thus can ignore any header, META, + * or other directives not to use a cached rendition. -FM + */ + return (FALSE); +} + +#define CanTrimTextArea(c) \ + (LYtrimInputFields ? isspace(c) : ((c) == '\r' || (c) == '\n')) + +/* + * Re-render the text of a tagged ("[123]") HTLine (arg1), with the tag + * number incremented by some value (arg5). The re-rendered string may + * be allowed to expand in the event of a tag width change (eg, 99 -> 100) + * as controlled by arg6 (CHOP or NOCHOP). Arg4 is either (the address + * of) a value which must match, in order for the tag to be incremented, + * or (the address of) a 0-value, which will match any value, and cause + * any valid tag to be incremented. Arg2 is a pointer to the first/only + * anchor that exists on the line; we may need to adjust their position(s) + * on the line. Arg3 when non-0 indicates the number of new digits that + * were added to the 2nd line in a line crossing pair. + * + * All tags fields in a line which individually match an expected new value, + * are incremented. Line crossing [tags] are handled (PITA). + * + * Untagged or improperly tagged lines are not altered. + * + * Returns the number of chars added to the original string's length, if + * any. + * + * --KED 02/03/99 + */ +static int increment_tagged_htline(HTLine *ht, TextAnchor *a, int *lx_val, + int *old_val, + int incr, + int mode) +{ + char buf[MAX_LINE]; + char lxbuf[MAX_LINE * 2]; + + TextAnchor *st_anchor = a; + TextAnchor *nxt_anchor; + + char *p = ht->data; + char *s = buf; + char *lx = lxbuf; + char *t; + + BOOLEAN plx = FALSE; + BOOLEAN valid; + + int val; + int n; + int new_n; + int pre_n; + int post_n; + int fixup = 0; + + /* + * Cleanup for the 2nd half of a line crosser, whose number of tag + * digits grew by some number of places (usually 1 when it does + * happen, though it *could* be more). The tag chars were already + * rendered into the 2nd line of the pair, but the positioning and + * other effects haven't been rippled through any other anchors on + * the (2nd) line. So we do that here, as a special case, since + * the part of the tag that's in the 2nd line of the pair, will not + * be found by the tag string parsing code. Double PITA. + * + * [see comments below on line crosser caused problems] + */ + if (*lx_val != 0) { + nxt_anchor = st_anchor; + while ((nxt_anchor) && (nxt_anchor->line_num == a->line_num)) { + nxt_anchor->line_pos = (short) (nxt_anchor->line_pos + *lx_val); + nxt_anchor = nxt_anchor->next; + } + fixup = *lx_val; + *lx_val = 0; + if (st_anchor) + st_anchor = st_anchor->next; + } + + /* + * Walk thru the line looking for tags (ie, "[nnn]" strings). + */ + while (*p != '\0') { + if (*p != '[') { + *s++ = *p++; + continue; + + } else { + *s++ = *p++; + t = p; + n = 0; + valid = TRUE; /* p = t = byte after '[' */ + + /* + * Make sure there are only digits between "[" and "]". + */ + while (*t != ']') { + if (*t == '\0') { /* uhoh - we have a potential line crosser */ + valid = FALSE; + plx = TRUE; + break; + } + if (isdigit(UCH(*t++))) { + n++; + continue; + } else { + valid = FALSE; + break; + } + } + + /* + * If the format is OK, we check to see if the value is what + * we expect. If not, we have a random [nn] string in the text, + * and leave it alone. + * + * [It is *possible* to have a false match here, *if* there are + * two identical [nn] strings (including the numeric value of + * nn), one of which is the [tag], and the other being part of + * a document. In such a case, the 1st [nn] string will get + * incremented; the 2nd one won't, which makes it a 50-50 chance + * of being correct, if and when such an unlikely juxtaposition + * of text ever occurs. Further validation tests of the [nnn] + * string are probably not possible, since little of the actual + * anchor-associated-text is retained in the TextAnchor or the + * FormInfo structs. Fortunately, I think the current method is + * more than adequate to weed out 99.999% of any possible false + * matches, just as it stands. Caveat emptor.] + */ + if ((valid) && (n > 0)) { + val = atoi(p); + if ((val == *old_val) || (*old_val == 0)) { /* 0 matches all */ + if (*old_val != 0) + (*old_val)++; + val += incr; + sprintf(s, "%d", val); + new_n = (int) strlen(s); + s += new_n; + p += n; + + /* + * If the number of digits in an existing [tag] increased + * (eg, [99] --> [100], etc), we need to "adjust" its + * horizontal position, and that of all subsequent tags + * that may be on the same line. PITA. + * + * [This seems to work as long as a tag isn't a line + * crosser; when it is, the position of anchors on either + * side of the split tag, seem to "float" and try to be + * as "centered" as possible. Which means that simply + * incrementing the line_pos by the fixed value of the + * number of digits that got added to some tag in either + * line doesn't work quite right, and the text for (say) + * a button may get stomped on by another copy of itself, + * but offset by a few chars, when it is selected (eg, + * "Box Office" may end up looking like "BoBox Office" or + * "Box Officece", etc. + * + * Dunno how to fix that behavior ATT, but at least the + * tag numbers themselves are correct. -KED /\oo/\ ] + */ + if ((new_n -= n) != 0) { + nxt_anchor = st_anchor; + while ((nxt_anchor) && + (nxt_anchor->line_num == a->line_num)) { + nxt_anchor->line_pos = (short) (nxt_anchor->line_pos + + new_n); + nxt_anchor = nxt_anchor->next; + } + if (st_anchor) + st_anchor = st_anchor->next; + } + } + } + + /* + * Unfortunately, valid [tag] strings *can* be split across two + * lines. Perhaps it would be best to just prevent that from + * happening, but a look into that code, makes me wonder. Anyway, + * we can handle such tags without *too* much trouble in here [I + * think], though since such animals are rather rare, it makes it + * a bit difficult to test thoroughly (ie, Beyond here, there be + * Dragons). + * + * We use lxbuf[] to deal with the two lines involved. + */ + pre_n = (int) strlen(p); /* count of 1st part chars in this line */ + post_n = (int) strlen(ht->next->data); + if (plx + && (pre_n + post_n + 2 < (int) sizeof(lxbuf))) { + strcpy(lx, p); /* <- 1st part of a possible lx'ing tag */ + strcat(lx, ht->next->data); /* tack on NEXT line */ + + t = lx; + n = 0; + valid = TRUE; + + /* + * Go hunting again for just digits, followed by tag end ']'. + */ + while (*t != ']') { + if (isdigit(UCH(*t++))) { + n++; + continue; + } else { + valid = FALSE; + break; + } + } + + /* + * It *looks* like a line crosser; now we value test it to + * find out for sure [but see the "false match" warning, + * above], and if it matches, increment it into the buffer, + * along with the 2nd line's text. + */ + if ((valid) + && (n > 0) + && (n + post_n + 2) < MAX_LINE) { + val = atoi(lx); + if ((val == *old_val) || (*old_val == 0)) { + const char *r; + + if (*old_val != 0) + (*old_val)++; + val += incr; + sprintf(lx, "%d", val); + new_n = (int) strlen(lx); + if ((r = StrChr(ht->next->data, ']')) == 0) { + r = ""; + } + strcat(lx, r); + + /* + * We keep the the same number of chars from the + * adjusted tag number in the current line; any + * extra chars due to a digits increase, will be + * stuffed into the next line. + * + * Keep track of any digits added, for the next + * pass through. + */ + s = StrNCpy(s, lx, pre_n) + pre_n; + lx += pre_n; + strcpy(ht->next->data, lx); + + *lx_val = new_n - n; + } + } + break; /* had an lx'er, so we're done with this line */ + } + } + } + + *s = '\0'; + + n = (int) strlen(ht->data); + if (mode == CHOP) { + *(buf + n) = '\0'; + } else if (strlen(buf) > ht->size) { + /* we didn't allocate enough space originally - increase it */ + HTLine *temp; + + allocHTLine(temp, strlen(buf)); + if (!temp) + outofmem(__FILE__, "increment_tagged_htline"); + + memcpy(temp, ht, LINE_SIZE(0)); +#if defined(USE_COLOR_STYLE) + POOLallocstyles(temp->styles, ht->numstyles); + if (!temp->styles) + outofmem(__FILE__, "increment_tagged_htline"); + memcpy(temp->styles, ht->styles, sizeof(HTStyleChange) * ht->numstyles); +#endif + ht = temp; + ht->prev->next = ht; /* Link in new line */ + ht->next->prev = ht; /* Could be same node of course */ + } + strcpy(ht->data, buf); + + return ((int) strlen(buf) - n + fixup); +} + +/* + * Creates a new anchor and associated struct's appropriate for a form + * TEXTAREA, and links them into the lists following the current anchor + * position (as specified by arg1). + * + * Exits with arg1 now pointing at the new TextAnchor, and arg2 pointing + * at the new, associated HTLine. + * + * --KED 02/13/99 + */ +static void insert_new_textarea_anchor(TextAnchor **curr_anchor, HTLine **exit_htline) +{ + TextAnchor *anchor = *curr_anchor; + HTLine *htline; + + TextAnchor *a = 0; + FormInfo *f = 0; + HTLine *l = 0; + + int curr_tag = 0; /* 0 ==> match any [tag] number */ + int lx = 0; /* 0 ==> no line crossing [tag]; it's a new line */ + int i; + + /* + * Find line in the text that matches ending anchorline of + * the TEXTAREA. + * + * [Yes, Virginia ... we *do* have to go thru this for each + * anchor being added, since there is NOT a 1-to-1 mapping + * between anchors and htlines. I suppose we could create + * YAS (Yet Another Struct), but there are too many structs{} + * floating around in here, as it is. IMNSHO.] + */ + for (htline = FirstHTLine(HTMainText), i = 0; anchor->line_num != i; i++) { + htline = htline->next; + if (htline == HTMainText->last_line) + break; + } + + /* + * Clone and initialize the struct's needed to add a new TEXTAREA + * anchor. + */ + allocHTLine(l, MAX_LINE); + POOLtypecalloc(TextAnchor, a); + + POOLtypecalloc(FormInfo, f); + if (a == NULL || l == NULL || f == NULL) + outofmem(__FILE__, "insert_new_textarea_anchor"); + + /* Init all the fields in the new TextAnchor. */ + /* [anything "special" needed based on ->show_anchor value ?] */ + a->next = anchor->next; + a->number = anchor->number; + a->line_pos = anchor->line_pos; + a->extent = anchor->extent; + a->sgml_offset = SGML_offset(); + a->line_num = anchor->line_num + 1; + LYCopyHiText(a, anchor); + a->link_type = anchor->link_type; + a->input_field = f; + a->show_anchor = anchor->show_anchor; + a->inUnderline = anchor->inUnderline; + a->expansion_anch = TRUE; + a->anchor = NULL; + + /* Just the (seemingly) relevant fields in the new FormInfo. */ + /* [do we need to do anything "special" based on ->disabled] */ + StrAllocCopy(f->name, anchor->input_field->name); + f->number = anchor->input_field->number; + f->type = anchor->input_field->type; + StrAllocCopy(f->orig_value, ""); + f->size = anchor->input_field->size; + f->maxlength = anchor->input_field->maxlength; + f->no_cache = anchor->input_field->no_cache; + f->disabled = anchor->input_field->disabled; + f->readonly = anchor->input_field->readonly; + f->value_cs = current_char_set; /* use current setting - kw */ + + /* Init all the fields in the new HTLine (but see the #if). */ + l->next = htline->next; + l->prev = htline; + l->offset = htline->offset; + l->size = htline->size; +#if defined(USE_COLOR_STYLE) + /* dup styles[] if needed [no need in TEXTAREA (?); leave 0's] */ + l->numstyles = htline->numstyles; + /*we fork the pointers! */ + l->styles = htline->styles; +#endif + strcpy(l->data, htline->data); + + /* + * Link in the new HTLine. + */ + htline->next->prev = l; + htline->next = l; + + if (fields_are_numbered()) { + a->number++; + increment_tagged_htline(l, a, &lx, &curr_tag, 1, CHOP); + } + + /* + * If we're at the tail end of the TextAnchor or HTLine list(s), + * the new node becomes the last node. + */ + if (anchor == HTMainText->last_anchor) + HTMainText->last_anchor = a; + if (htline == HTMainText->last_line) + HTMainText->last_line = l; + + /* + * Link in the new TextAnchor and point the entry anchor arg at it; + * point the entry HTLine arg at it, too. + */ + anchor->next = a; + *curr_anchor = a; + + *exit_htline = l->next; + + return; +} + +/* + * If new anchors were added to expand a TEXTAREA, we need to ripple the + * new line numbers [and char counts ?] thru the subsequent anchors. + * + * If form lines are getting [nnn] tagged, we need to update the displayed + * tag values to match (which means rerendering them ... sigh). + * + * Finally, we need to update various HTMainText and other counts, etc. + * + * [dunno if the char counts really *need* to be done, or if we're using + * the exactly proper values/algorithms ... seems to be OK though ...] + * + * --KED 02/13/99 + */ +static void update_subsequent_anchors(int newlines, + TextAnchor *start_anchor, + HTLine *start_htline, + int start_tag) +{ + TextAnchor *anchor; + HTLine *htline = start_htline; + + int line_adj = 0; + int lx = 0; + int hang = 0; /* for HANG detection of a nasty intermittent */ + int hang_detect = 100000; /* ditto */ + + CTRACE((tfp, "GridText: adjusting struct's to add %d new line(s)\n", newlines)); + + /* + * Update numeric fields of the rest of the anchors. + * + * [We bypass bumping ->number if it has a value of 0, which takes care + * of the ->input_field->type == F_HIDDEN_TYPE (as well as any other + * "hidden" anchors, if such things exist). Seems like the "right + * thing" to do. I think.] + */ + anchor = start_anchor->next; /* begin updating with the NEXT anchor */ + while (anchor) { + if (fields_are_numbered() && + (anchor->number != 0)) + anchor->number += newlines; + anchor->line_num += newlines; + anchor = anchor->next; + } + + /* + * Update/rerender anchor [tags], if they are being numbered. + * + * [If a number tag (eg, "[177]") is itself broken across a line + * boundary, this fixup only partially works. While the tag + * numbering is done properly across the pair of lines, the + * horizontal positioning on *either* side of the split, can get + * out of sync by a char or two when it gets selected. See the + * [comments] in increment_tagged_htline() for some more detail. + * + * I suppose THE fix is to prevent such tag-breaking in the first + * place (dunno where yet, though). Ah well ... at least the tag + * numbers themselves are correct from top to bottom now. + * + * All that said, about the only time this will be a problem in + * *practice*, is when a page has near 1000 links or more (possibly + * after a TEXTAREA expansion), and has line crossing tag(s), and + * the tag numbers in a line crosser go from initially all 3 digit + * numbers, to some mix of 3 and 4 digits (or all 4 digits) as a + * result of the expansion process. Oh, you also need a "clump" of + * anchors all on the same lines. + * + * Yes, it *can* happen, but in real life, it probably won't be + * seen very much ...] + * + * [This may also be an artifact of bumping into the right hand + * screen edge (or RHS margin), since we don't even *think* about + * relocating an anchor to the following line, when [tag] digits + * expansion pushes things too far in that direction.] + */ + if (fields_are_numbered()) { + anchor = start_anchor->next; + while (htline != FirstHTLine(HTMainText)) { + + while (anchor) { + if ((anchor->number - newlines) == start_tag) + break; + + /*** A HANG (infinite loop) *has* occurred here, with */ + /*** the values of anchor and anchor->next being the */ + /*** the same, OR with anchor->number "magically" and */ + /*** suddenly taking on an anchor-pointer-like value. */ + /*** */ + /*** The same code and same doc have both passed and */ + /*** failed at different times, which indicates some */ + /*** sort of content/html dependency, or some kind of */ + /*** a "race" condition, but I'll be damned if I can */ + /*** find it after tons of CTRACE's, printf()'s, gdb */ + /*** breakpoints and watchpoints, etc. */ + /*** */ + /*** I have added a hang detector (with error msg and */ + /*** beep) here, to break the loop and warn the user, */ + /*** until it can be isolated and fixed. */ + /*** */ + /*** [One UGLY intermittent .. gak ..! 02/22/99 KED] */ + + hang++; + if ((anchor == anchor->next) || (hang >= hang_detect)) + goto hang_detected; + + anchor = anchor->next; + } + + if (anchor) { + line_adj = increment_tagged_htline(htline, anchor, &lx, + &start_tag, newlines, + NOCHOP); + htline->size = (unsigned short) (htline->size + line_adj); + + } else { + + break; /* out of anchors ... we're done */ + } + + htline = htline->next; + } + } + + finish: + /* + * Fixup various global variables. + */ + nlinks += newlines; + HTMainText->Lines += newlines; + HTMainText->last_anchor_number += newlines; + + more_text = HText_canScrollDown(); + + CTRACE((tfp, "GridText: TextAnchor and HTLine struct's adjusted\n")); + + return; + + hang_detected: /* ugliness has happened; inform user and do the best we can */ + + HTAlert(gettext("Hang Detect: TextAnchor struct corrupted - suggest aborting!")); + goto finish; +} + +/* + * Check if the given anchor is a TEXTAREA belonging to the given form. + * + * KED's note - + * [Finding the TEXTAREA we're actually *in* with these attributes isn't + * foolproof. The form number isn't unique to a given TEXTAREA, and there + * *could* be TEXTAREA's with the same "name". If that should ever be true, + * we'll actually get the data from the *1st* TEXTAREA in the page that + * matches. We should probably assign a unique id to each TEXTAREA in a page, + * and match on that, to avoid this (potential) problem. + * + * Since the odds of "false matches" *actually* happening in real life seem + * rather small though, we'll hold off doing this, for a rainy day ...] + */ +static BOOLEAN IsFormsTextarea(FormInfo * form, TextAnchor *anchor_ptr) +{ + return (BOOLEAN) ((anchor_ptr->link_type == INPUT_ANCHOR) && + (anchor_ptr->input_field->type == F_TEXTAREA_TYPE) && + (anchor_ptr->input_field->number == form->number) && + !strcmp(anchor_ptr->input_field->name, form->name)); +} + +static char *readEditedFile(char *ed_temp) +{ + struct stat stat_info; + size_t size; + + FILE *fp; + + char *ebuf; + + CTRACE((tfp, "GridText: entered HText_EditTextArea()\n")); + + /* + * Read back the edited temp file into our buffer. + */ + if ((stat(ed_temp, &stat_info) < 0) || + !S_ISREG(stat_info.st_mode) || + ((size = (size_t) stat_info.st_size) == 0)) { + size = 0; + ebuf = typecalloc(char); + + if (!ebuf) + outofmem(__FILE__, "HText_EditTextArea"); + } else { + ebuf = typecallocn(char, size + 1); + + if (!ebuf) { + /* + * This could be huge - don't exit if we don't have enough + * memory for it. With some luck, the user may be even able + * to recover the file manually from the temp space while + * the lynx session is not over. - kw + */ + HTAlwaysAlert(NULL, MEMORY_EXHAUSTED_FILE); + return 0; + } + + if ((fp = fopen(ed_temp, "r")) != 0) { + size = fread(ebuf, (size_t) 1, size, fp); + LYCloseInput(fp); + ebuf[size] = '\0'; /* Terminate! - kw */ + } else { + size = 0; + } + } + + /* + * Nuke any blank lines from the end of the edited data. + */ + while ((size != 0) + && (CanTrimTextArea(UCH(ebuf[size - 1])) || (ebuf[size - 1] == '\0'))) + ebuf[--size] = '\0'; + + return ebuf; +} + +static int finish_ExtEditForm(LinkInfo *form_link, TextAnchor *start_anchor, + char *ed_temp, + int orig_cnt) +{ + TextAnchor *anchor_ptr; + TextAnchor *end_anchor = NULL; + BOOLEAN wrapalert = FALSE; + + int entry_line = form_link->anchor_line_num; + int exit_line = 0; + int line_cnt = 1; + + HTLine *htline = NULL; + + char *ebuf; + char *line; + char *lp; + char *cp; + int match_tag = 0; + int newlines = 0; + int len, len0; + int display_size; + int wanted_fieldlen_wrap = -1; /* not yet asked; 0 means don't. */ + char *skip_at = NULL; + int skip_num = 0, i; + size_t line_used = MAX_LINE; + + CTRACE((tfp, "GridText: entered HText_EditTextArea()\n")); + + if ((ebuf = readEditedFile(ed_temp)) == 0) { + return 0; + } + + /* + * Copy each line from the temp file into the corresponding anchor + * struct. Add new lines to the TEXTAREA if needed. (Always leave + * the user with a blank line at the end of the TEXTAREA.) + */ + if ((line = typeMallocn(char, line_used)) == 0) + outofmem(__FILE__, "HText_EditTextArea"); + + anchor_ptr = start_anchor; + display_size = anchor_ptr->input_field->size; + if (display_size <= 4 || + display_size >= MAX_LINE) + wanted_fieldlen_wrap = 0; + + len = 0; + lp = ebuf; + + while ((line_cnt <= orig_cnt) || (*lp) || ((len != 0) && (*lp == '\0'))) { + + if (skip_at) { + len0 = (int) (skip_at - lp); + LYStrNCpy(line, lp, len0); + lp = skip_at + skip_num; + skip_at = NULL; + skip_num = 0; + + assert(lp != NULL); + } else { + len0 = 0; + } + line[len0] = '\0'; + + if ((cp = StrChr(lp, '\n')) != 0) + len = (int) (cp - lp); + else + len = (int) strlen(lp); + + if (wanted_fieldlen_wrap < 0 && + !wrapalert && + len0 + len >= display_size && + (cp = StrChr(lp, ' ')) != NULL && + (cp - lp) < display_size - 1) { + + LYFixCursesOn("ask for confirmation:"); + LYerase(); /* don't show previous state */ + if (HTConfirmDefault(gettext("Wrap lines to fit displayed area?"), + NO)) { + wanted_fieldlen_wrap = display_size - 1; + } else { + wanted_fieldlen_wrap = 0; + } + } + + if (wanted_fieldlen_wrap > 0 && + len0 + len > wanted_fieldlen_wrap) { + + for (i = wanted_fieldlen_wrap - len0; + i + len0 >= wanted_fieldlen_wrap / 4; i--) { + + if (isspace(UCH(lp[i]))) { + len = i + 1; + cp = lp + i; + if (cp[1] != '\n' && + isspace(UCH(cp[1])) && + !isspace(UCH(cp[2]))) { + len++; + cp++; + } + if (!isspace(UCH(cp[1]))) { + while (*cp && *cp != '\r' && *cp != '\n' && + (cp - lp) <= len + (3 * wanted_fieldlen_wrap / 4)) + cp++; /* search for next line break */ + if (*cp == '\r' && cp[1] == '\n') + cp++; + if (*cp == '\n' && + (cp[1] == '\r' || cp[1] == '\n' || + !isspace(UCH(cp[1])))) { + *cp = ' '; + while (isspace(UCH(*(cp - 1)))) { + skip_num++; + cp--; + } + skip_at = cp; + } + } + break; + } + } + } + + if (wanted_fieldlen_wrap > 0 && + (len0 + len) > wanted_fieldlen_wrap) { + + i = len - 1; + while (len0 + i + 1 > wanted_fieldlen_wrap && + isspace(UCH(lp[i]))) + i--; + if (len0 + i + 1 > wanted_fieldlen_wrap) + len = wanted_fieldlen_wrap - len0; + } + + /* + * Check if the new text will fit in the buffer. HTML does not define + * a "maxlength" attribute for TEXTAREA; its data can grow as needed. + * Lynx will not adjust the display to reflect larger amounts of text; + * it relies on the rows/cols attributes as well as the initial content + * of the text area for the layout. + */ + if ((size_t) (len0 + len) >= line_used) { + line_used = (size_t) (3 * (len0 + len)) / 2; + if ((line = typeRealloc(char, line, line_used)) == 0) + outofmem(__FILE__, "HText_EditTextArea"); + } + + strncat(line, lp, (size_t) len); + *(line + len0 + len) = '\0'; + + /* + * If there are more lines in the edit buffer than were in the + * original TEXTAREA, we need to add a new line/anchor, continuing + * on until the edit buffer is empty. + */ + if (line_cnt > orig_cnt) { + insert_new_textarea_anchor(&end_anchor, &htline); + + assert(end_anchor != NULL); + assert(end_anchor->input_field != NULL); + + anchor_ptr = end_anchor; /* make the new anchor current */ + newlines++; + } + + assert(anchor_ptr != NULL); + + /* + * Finally copy the new line from the edit buffer into the anchor. + */ + StrAllocCopy(anchor_ptr->input_field->value, line); + + /* + * Keep track of 1st blank line in any trailing blank lines, for + * later cursor repositioning. + */ + if (len0 + len > 0) + exit_line = 0; + else if (exit_line == 0) + exit_line = anchor_ptr->line_num; + + /* + * And do the next line of edited text, for the next anchor ... + */ + lp += len; + if (*lp && isspace(UCH(*lp))) + lp++; + + end_anchor = anchor_ptr; + anchor_ptr = anchor_ptr->next; + + if (anchor_ptr) + match_tag = anchor_ptr->number; + + line_cnt++; + } + + CTRACE((tfp, "GridText: edited text inserted into lynx struct's\n")); + + /* + * If we've added any new lines/anchors, we need to adjust various + * things in all anchor-bearing lines following the last newly added + * line/anchor. The fun stuff starts here ... + */ + if (newlines > 0) + update_subsequent_anchors(newlines, end_anchor, htline, match_tag); + + /* + * Cleanup time. + */ + FREE(line); + FREE(ebuf); + + /* + * Return the offset needed to move the cursor from its current + * (on entry) line number, to the 1st blank line of the trailing + * (group of) blank line(s), which is where we want to be. Let + * the caller deal with moving us there, however ... :-) ... + */ + return (exit_line - entry_line); +} + +/* + * Transfer the initial contents of a TEXTAREA to a temp file, invoke the + * user's editor on that file, then transfer the contents of the resultant + * edited file back into the TEXTAREA (expanding the size of the area, if + * required). + * + * Returns the number of lines that the cursor should be moved so that it + * will end up on the 1st blank line of whatever number of trailing blank + * lines there are in the TEXTAREA (there will *always* be at least one). + * + * --KED 02/01/99 + */ +int HText_EditTextArea(LinkInfo *form_link) +{ + char *ed_temp; + FILE *fp; + + TextAnchor *anchor_ptr; + TextAnchor *start_anchor = NULL; + BOOLEAN firstanchor = TRUE; + + char ed_offset[DigitsOf(int) + 3]; + int start_line = 0; + int entry_line = form_link->anchor_line_num; + int orig_cnt = 0; + int offset = 0; + + FormInfo *form = form_link->l_form; + + CTRACE((tfp, "GridText: entered HText_EditTextArea()\n")); + + if ((ed_temp = typeMallocn(char, LY_MAXPATH)) == 0) { + outofmem(__FILE__, "HText_EditTextArea"); + } else if ((fp = LYOpenTemp(ed_temp, "", "w")) != 0) { + + /* + * Begin at the beginning, to find 1st anchor in the TEXTAREA, then + * write all of its lines (anchors) out to the edit temp file. + */ + anchor_ptr = HTMainText->first_anchor; + + while (anchor_ptr) { + + if (IsFormsTextarea(form, anchor_ptr)) { + + if (firstanchor) { + firstanchor = FALSE; + start_anchor = anchor_ptr; + start_line = anchor_ptr->line_num; + } + orig_cnt++; + + /* + * Write the anchors' text to the temp edit file. + */ + fputs(anchor_ptr->input_field->value, fp); + fputc('\n', fp); + + } else { + + if (!firstanchor) + break; + } + anchor_ptr = anchor_ptr->next; + } + LYCloseTempFP(fp); + + if (start_anchor != 0) { + CTRACE((tfp, "GridText: TEXTAREA name=|%s| dumped to tempfile\n", form->name)); + CTRACE((tfp, "GridText: invoking editor (%s) on tempfile\n", editor)); + + /* + * Go edit the TEXTAREA temp file, with the initial editor line + * corresponding to the TEXTAREA line the cursor is on (if such + * positioning is supported by the editor [as lynx knows it]). + */ + ed_offset[0] = 0; /* pre-ANSI compilers don't initialize aggregates - TD */ + if (((entry_line - start_line) > 0) && editor_can_position()) + sprintf(ed_offset, "%d", ((entry_line - start_line) + 1)); + + edit_temporary_file(ed_temp, ed_offset, NULL); + + CTRACE((tfp, "GridText: returned from editor (%s)\n", editor)); + + if (!form->disabled) + offset = finish_ExtEditForm(form_link, start_anchor, ed_temp, orig_cnt); + + CTRACE((tfp, "GridText: exiting HText_EditTextArea()\n")); + } + (void) LYRemoveTemp(ed_temp); + FREE(ed_temp); + } + + /* + * Return the offset needed to move the cursor from its current + * (on entry) line number, to the 1st blank line of the trailing + * (group of) blank line(s), which is where we want to be. Let + * the caller deal with moving us there, however ... :-) ... + */ + return offset; +} + +/* + * Similar to HText_EditTextArea, but assume a single-line text field -TD + */ +void HText_EditTextField(LinkInfo *form_link) +{ + char *ed_temp; + FILE *fp; + + FormInfo *form = form_link->l_form; + + CTRACE((tfp, "GridText: entered HText_EditTextField()\n")); + + ed_temp = typeMallocn(char, LY_MAXPATH); + + if ((fp = LYOpenTemp(ed_temp, "", "w")) == 0) { + FREE(ed_temp); + return; + } + + /* + * Write the anchors' text to the temp edit file. + */ + fputs(form->value, fp); + fputc('\n', fp); + + LYCloseTempFP(fp); + + CTRACE((tfp, "GridText: text field |%s| dumped to tempfile\n", form_link->lname)); + CTRACE((tfp, "GridText: invoking editor (%s) on tempfile\n", editor)); + + edit_temporary_file(ed_temp, "", NULL); + + CTRACE((tfp, "GridText: returned from editor (%s)\n", editor)); + + if (!form->disabled) { + char *ebuf; + char *p; + + if ((ebuf = readEditedFile(ed_temp)) != 0) { + /* + * Only use the first line of the result. + */ + for (p = ebuf; *p != '\0'; ++p) { + if (*p == '\n' || *p == '\r') { + *p = '\0'; + break; + } + } + StrAllocCopy(form->value, ebuf); + FREE(ebuf); + } + } + + (void) LYRemoveTemp(ed_temp); + FREE(ed_temp); + + CTRACE((tfp, "GridText: exiting HText_EditTextField()\n")); +} + +/* + * Expand the size of a TEXTAREA by a fixed number of lines (as specified + * by arg2). + * + * --KED 02/14/99 + */ +void HText_ExpandTextarea(LinkInfo *form_link, int newlines) +{ + TextAnchor *anchor_ptr; + TextAnchor *end_anchor = NULL; + BOOLEAN firstanchor = TRUE; + + FormInfo *form = form_link->l_form; + + HTLine *htline = NULL; + + int match_tag = 0; + int i; + + CTRACE((tfp, "GridText: entered HText_ExpandTextarea()\n")); + + if (newlines < 1) + return; + + /* + * Begin at the beginning, to find the TEXTAREA, then on to find + * the last line (anchor) in it. + */ + anchor_ptr = HTMainText->first_anchor; + + while (anchor_ptr) { + + if (IsFormsTextarea(form, anchor_ptr)) { + + if (firstanchor) + firstanchor = FALSE; + + end_anchor = anchor_ptr; + + } else { + + if (!firstanchor) + break; + } + anchor_ptr = anchor_ptr->next; + } + + if (end_anchor == NULL) + return; + + for (i = 1; i <= newlines; i++) { + insert_new_textarea_anchor(&end_anchor, &htline); + + /* + * Make the new line blank. + */ + StrAllocCopy(end_anchor->input_field->value, ""); + + /* + * And go add another line ... + */ + if (end_anchor->next) + match_tag = end_anchor->next->number; + } + + CTRACE((tfp, "GridText: %d blank line(s) added to TEXTAREA name=|%s|\n", + newlines, form->name)); + + /* + * We need to adjust various things in all anchor bearing lines + * following the last newly added line/anchor. Fun stuff. + */ + update_subsequent_anchors(newlines, end_anchor, htline, match_tag); + + CTRACE((tfp, "GridText: exiting HText_ExpandTextarea()\n")); + + return; +} + +/* + * Insert the contents of a file into a TEXTAREA between the cursor line, + * and the line preceding it. + * + * Returns the number of lines that the cursor should be moved so that it + * will end up on the 1st line in the TEXTAREA following the inserted file + * (if we decide to do that). + * + * --KED 02/21/99 + */ +int HText_InsertFile(LinkInfo *form_link) +{ + struct stat stat_info; + size_t size; + + FILE *fp; + char *fn; + + TextAnchor *anchor_ptr; + TextAnchor *prev_anchor = NULL; + TextAnchor *end_anchor = NULL; + BOOLEAN firstanchor = TRUE; + BOOLEAN truncalert = FALSE; + + FormInfo *form = form_link->l_form; + + HTLine *htline = NULL; + + TextAnchor *a = 0; + FormInfo *f = 0; + HTLine *l = 0; + + char *fbuf = 0; + char *line = 0; + char *lp; + char *cp; + int entry_line = form_link->anchor_line_num; + int file_cs; + int match_tag = 0; + int newlines = 0; + int len; + int i; + + CTRACE((tfp, "GridText: entered HText_InsertFile()\n")); + + /* + * Get the filename of the insert file. + */ + if (!(fn = GetFileName())) { + HTInfoMsg(FILE_INSERT_CANCELLED); + CTRACE((tfp, + "GridText: file insert cancelled - no filename provided\n")); + return (0); + } + if (no_dotfiles || !show_dotfiles) { + if (*LYPathLeaf(fn) == '.') { + HTUserMsg(FILENAME_CANNOT_BE_DOT); + return (0); + } + } + + /* + * Read it into our buffer (abort on 0-length file). + */ + if ((stat(fn, &stat_info) < 0) || + ((size = (size_t) stat_info.st_size) == 0)) { + HTInfoMsg(FILE_INSERT_0_LENGTH); + CTRACE((tfp, + "GridText: file insert aborted - file=|%s|- was 0-length\n", + fn)); + FREE(fn); + return (0); + + } else { + + if ((fbuf = typecallocn(char, size + 1)) == NULL) { + /* + * This could be huge - don't exit if we don't have enough + * memory for it. - kw + */ + free(fn); + HTAlert(MEMORY_EXHAUSTED_FILE); + return 0; + } + + /* Try to make the same assumption for the charset of the inserted + * file as we would for normal loading of that file, i.e. taking + * assume_local_charset and suffix mappings into account. + * If there is a mismatch with the display character set, characters + * may be displayed wrong, too bad; but the user has a chance to + * correct this by editing the lines, which will update f->value_cs + * again. - kw + */ + LYGetFileInfo(fn, 0, 0, 0, 0, 0, &file_cs); + + fp = fopen(fn, "r"); + if (!fp) { + free(fbuf); + free(fn); + HTAlert(FILE_CANNOT_OPEN_R); + return 0; + } + size = fread(fbuf, (size_t) 1, size, fp); + LYCloseInput(fp); + FREE(fn); + fbuf[size] = '\0'; /* Terminate! - kw */ + } + + /* + * Begin at the beginning, to find the TEXTAREA we're in, then + * the current cursorline. + */ + anchor_ptr = HTMainText->first_anchor; + + while (anchor_ptr) { + + if (IsFormsTextarea(form, anchor_ptr)) { + if (anchor_ptr->line_num == entry_line) + break; + } + prev_anchor = anchor_ptr; + anchor_ptr = anchor_ptr->next; + } + + if (anchor_ptr == NULL) { + CTRACE((tfp, "BUG: could not find anchor for TEXTAREA.\n")); + FREE(line); + FREE(fbuf); + return 0; + } + + /* + * Clone a new TEXTAREA line/anchor using the cursorline anchor as + * a template, but link it in BEFORE the cursorline anchor/htline. + * + * [We can probably combine this with insert_new_textarea_anchor() + * along with a flag to indicate "insert before" as we do here, + * or the "normal" mode of operation (add after "current" anchor/ + * line). Beware of the differences ... some are a bit subtle to + * notice.] + */ + for (htline = FirstHTLine(HTMainText), i = 0; + anchor_ptr->line_num != i; i++) { + htline = htline->next; + if (htline == HTMainText->last_line) + break; + } + + allocHTLine(l, MAX_LINE); + POOLtypecalloc(TextAnchor, a); + + POOLtypecalloc(FormInfo, f); + if (a == NULL || l == NULL || f == NULL) + outofmem(__FILE__, "HText_InsertFile"); + + /* Init all the fields in the new TextAnchor. */ + /* [anything "special" needed based on ->show_anchor value ?] */ + /* *INDENT-EQLS* */ + a->next = anchor_ptr; + a->number = anchor_ptr->number; + a->show_number = anchor_ptr->show_number; + a->line_pos = anchor_ptr->line_pos; + a->extent = anchor_ptr->extent; + a->sgml_offset = SGML_offset(); + a->line_num = anchor_ptr->line_num; + LYCopyHiText(a, anchor_ptr); + a->link_type = anchor_ptr->link_type; + a->input_field = f; + a->show_anchor = anchor_ptr->show_anchor; + a->inUnderline = anchor_ptr->inUnderline; + a->expansion_anch = TRUE; + a->anchor = NULL; + + /* Just the (seemingly) relevant fields in the new FormInfo. */ + /* [do we need to do anything "special" based on ->disabled] */ + StrAllocCopy(f->name, anchor_ptr->input_field->name); + f->number = anchor_ptr->input_field->number; + f->type = anchor_ptr->input_field->type; + StrAllocCopy(f->orig_value, ""); + f->size = anchor_ptr->input_field->size; + f->maxlength = anchor_ptr->input_field->maxlength; + f->no_cache = anchor_ptr->input_field->no_cache; + f->disabled = anchor_ptr->input_field->disabled; + f->readonly = anchor_ptr->input_field->readonly; + f->value_cs = (file_cs >= 0) ? file_cs : current_char_set; + + /* Init all the fields in the new HTLine (but see the #if). */ + l->offset = htline->offset; + l->size = htline->size; +#if defined(USE_COLOR_STYLE) + /* dup styles[] if needed [no need in TEXTAREA (?); leave 0's] */ + l->numstyles = htline->numstyles; + /*we fork the pointers! */ + l->styles = htline->styles; +#endif + strcpy(l->data, htline->data); + + /* + * If we're at the head of the TextAnchor list, the new node becomes + * the first node. + */ + if (anchor_ptr == HTMainText->first_anchor) + HTMainText->first_anchor = a; + + /* + * Link in the new TextAnchor, and corresponding HTLine. + */ + if (prev_anchor) + prev_anchor->next = a; + + htline = htline->prev; + l->next = htline->next; + l->prev = htline; + htline->next->prev = l; + htline->next = l; + + /* + * update_subsequent_anchors() expects htline to point to 1st potential + * line needing fixup; we need to do this just in case the inserted file + * was only a single line (yes, it's pathological ... ). + */ + htline = htline->next; /* ->new (current) htline, for 1st inserted line */ + htline = htline->next; /* ->1st potential (following) [tag] fixup htline */ + + anchor_ptr = a; + newlines++; + + /* + * Copy each line from the insert file into the corresponding anchor + * struct. + * + * Begin with the new line/anchor we just added (above the cursorline). + */ + if ((line = typeMallocn(char, MAX_LINE)) == 0) + outofmem(__FILE__, "HText_InsertFile"); + + match_tag = anchor_ptr->number; + + lp = fbuf; + + while (*lp) { + + if ((cp = StrChr(lp, '\n')) != 0) + len = (int) (cp - lp); + else + len = (int) strlen(lp); + + if (len >= MAX_LINE) { + if (!truncalert) { + HTAlert(gettext("Very long lines have been truncated!")); + truncalert = TRUE; + } + len = MAX_LINE - 1; + if (lp[len]) + lp[len + 1] = '\0'; /* prevent next iteration */ + } + LYStrNCpy(line, lp, len); + + /* + * If not the first line from the insert file, we need to add + * a new line/anchor, continuing on until the buffer is empty. + */ + if (!firstanchor) { + insert_new_textarea_anchor(&end_anchor, &htline); + anchor_ptr = end_anchor; /* make the new anchor current */ + newlines++; + } + + /* + * Copy the new line from the buffer into the anchor. + */ + StrAllocCopy(anchor_ptr->input_field->value, line); + + /* + * insert_new_textarea_anchor always uses current_char_set, + * we may want something else, so fix it up. - kw + */ + if (file_cs >= 0) + anchor_ptr->input_field->value_cs = file_cs; + + /* + * And do the next line of insert text, for the next anchor ... + */ + lp += len; + if (*lp) + lp++; + + firstanchor = FALSE; + end_anchor = anchor_ptr; + anchor_ptr = anchor_ptr->next; + } + + CTRACE((tfp, "GridText: file inserted into lynx struct's\n")); + + /* + * Now adjust various things in all anchor-bearing lines following the + * last newly added line/anchor. Some say this is the fun part ... + */ + update_subsequent_anchors(newlines, end_anchor, htline, match_tag); + + /* + * Cleanup time. + */ + FREE(line); + FREE(fbuf); + + CTRACE((tfp, "GridText: exiting HText_InsertFile()\n")); + + return (newlines); +} + +#ifdef USE_COLOR_STYLE +static int GetColumn(void) +{ + int result; + +#ifdef USE_SLANG + result = SLsmg_get_column(); +#else + int y, x; + + LYGetYX(y, x); + result = x; + (void) y; +#endif + return result; +} + +static BOOL DidWrap(int y0, int x0) +{ + BOOL result = NO; + +#ifndef USE_SLANG + int y, x; + + LYGetYX(y, x); + (void) x0; + if (x >= DISPLAY_COLS || ((x == 0) && (y != y0))) + result = YES; +#endif + return result; +} +#endif /* USE_COLOR_STYLE */ + +/* + * This function draws the part of line 'line', pointed by 'str' (which can be + * non terminated with null - i.e., is line->data+N) drawing 'len' bytes (not + * characters) of it. It doesn't check whether the 'len' bytes crosses a + * character boundary (if multibyte chars are in string). Assumes that the + * cursor is positioned in the place where the 1st char of string should be + * drawn. + * + * This code is based on display_line. This code was tested with ncurses only + * (since no support for lss is available for Slang) -HV. + */ +#ifdef USE_COLOR_STYLE +static void redraw_part_of_line(HTLine *line, const char *str, + int len, + HText *text) +{ + register int i; + char buffer[7]; + const char *data, *end_of_data; + size_t utf_extra = 0; + +#ifdef USE_COLOR_STYLE + int current_style = 0; + int tcols, scols; +#endif + char LastDisplayChar = ' '; + int YP, XP; + + LYGetYX(YP, XP); + + i = XP; + + /* Set up the multibyte character buffer */ + buffer[0] = buffer[1] = buffer[2] = '\0'; + + data = str; + end_of_data = data + len; + i++; + + /* this assumes that the part of line to be drawn fits in the screen */ + while (data < end_of_data) { + buffer[0] = *data; + data++; + +#if defined(USE_COLOR_STYLE) +#define CStyle line->styles[current_style] + + tcols = GetColumn(); + scols = StyleToCols(text, line, current_style); + + while (current_style < line->numstyles && + tcols >= scols) { + LynxChangeStyle(CStyle.sc_style, CStyle.sc_direction); + current_style++; + scols = StyleToCols(text, line, current_style); + } +#endif + switch (buffer[0]) { + +#ifndef USE_COLOR_STYLE + case LY_UNDERLINE_START_CHAR: + if (dump_output_immediately && use_underscore) { + LYaddch('_'); + i++; + } else { + lynx_start_underline(); + } + break; + + case LY_UNDERLINE_END_CHAR: + if (dump_output_immediately && use_underscore) { + LYaddch('_'); + i++; + } else { + lynx_stop_underline(); + } + break; + + case LY_BOLD_START_CHAR: + lynx_start_bold(); + break; + + case LY_BOLD_END_CHAR: + lynx_stop_bold(); + break; + +#endif + case LY_SOFT_NEWLINE: + if (!dump_output_immediately) { + LYaddch('+'); + i++; + } + break; + + case LY_SOFT_HYPHEN: + if (*data != '\0' || + isspace(UCH(LastDisplayChar)) || + LastDisplayChar == '-') { + /* + * Ignore the soft hyphen if it is not the last character in + * the line. Also ignore it if it is first character following + * the margin, or if it is preceded by a white character (we + * loaded 'M' into LastDisplayChar if it was a multibyte + * character) or hyphen, though it should have been excluded by + * HText_appendCharacter() or by split_line() in those cases. + * -FM + */ + break; + } else { + /* + * Make it a hard hyphen and fall through. -FM + */ + buffer[0] = '-'; + } + /* FALLTHRU */ + + default: + if (text->T.output_utf8 && is8bits(buffer[0])) { + utf_extra = utf8_length(text->T.output_utf8, data - 1); + LastDisplayChar = 'M'; + } + if (utf_extra) { + LYStrNCpy(&buffer[1], data, utf_extra); + LYaddstr(buffer); + buffer[1] = '\0'; + data += utf_extra; + utf_extra = 0; + } else if (is_CJK2(buffer[0])) { + /* + * For CJK strings, by Masanobu Kimura. + */ + if (i <= DISPLAY_COLS) { + buffer[1] = *data; + buffer[2] = '\0'; + data++; + i++; + LYaddstr(buffer); + buffer[1] = '\0'; + /* + * For now, load 'M' into LastDisplayChar, but we should + * check whether it's white and if so, use ' '. I don't + * know if there actually are white CJK characters, and + * we're loading ' ' for multibyte spacing characters in + * this code set, but this will become an issue when the + * development code set's multibyte character handling is + * used. -FM + */ + LastDisplayChar = 'M'; + } + } else { + LYaddstr(buffer); + LastDisplayChar = buffer[0]; + } + if (DidWrap(YP, XP)) + break; + i++; + } /* end of switch */ + } /* end of while */ + +#ifndef USE_COLOR_STYLE + lynx_stop_underline(); + lynx_stop_bold(); +#else + + while (current_style < line->numstyles) { + LynxChangeStyle(CStyle.sc_style, CStyle.sc_direction); + current_style++; + } + +#undef CStyle +#endif + return; +} +#endif /* USE_COLOR_STYLE */ + +#ifndef USE_COLOR_STYLE +/* + * Function move_to_glyph is called from LYMoveToLink and does all + * the real work for it. + * The pair LYMoveToLink()/move_to_glyph() is similar to the pair + * redraw_lines_of_link()/redraw_part_of_line(), some key differences: + * LYMoveToLink/move_to_glyph redraw_* + * ----------------------------------------------------------------- + * - used without color style - used with color style + * - handles showing WHEREIS target - WHEREIS handled elsewhere + * - handles only one line - handles first two lines for + * hypertext anchors + * - right columns position for UTF-8 + * by redrawing as necessary + * - currently used for highlight - currently used for highlight + * ON and OFF OFF + * + * Eventually the two sets of function should be unified, and should handle + * UTF-8 positioning, both lines of hypertext anchors, and WHEREIS in all + * cases. If possible. The complex WHEREIS target logic in LYhighlight() + * could then be completely removed. - kw + */ +static void move_to_glyph(int YP, + int XP, + int XP_draw_min, + const char *data, + int datasize, + unsigned offset, + const char *target, + const char *hightext, + int flags, + int utf_flag) +{ + char buffer[7]; + const char *end_of_data; + size_t utf_extra = 0; + +#if defined(SHOW_WHEREIS_TARGETS) + const char *cp_tgt; + int i_start_tgt = 0, i_after_tgt; + int HitOffset, LenNeeded; +#endif /* SHOW_WHEREIS_TARGETS */ + BOOL intarget = NO; + BOOL inunderline = NO; + BOOL inbold = NO; + BOOL drawing = NO; + BOOL inU = NO; + BOOL hadutf8 = NO; + BOOL incurlink = NO; + BOOL drawingtarget = NO; + BOOL flag = NO; + const char *sdata = data; + char LastDisplayChar = ' '; + + int i = (int) offset; /* FIXME: should be columns, not offset? */ + int last_i = DISPLAY_COLS; + int XP_link = XP; /* column of link */ + int XP_next = XP; /* column to move to when done drawing */ + int linkvlen; + + int len; + + if (no_title) + YP -= TITLE_LINES; + + if (flags & 1) + flag = YES; + if (flags & 2) + inU = YES; + /* Set up the multibyte character buffer */ + buffer[0] = buffer[1] = buffer[2] = '\0'; + /* + * Add offset, making sure that we do not + * go over the COLS limit on the display. + */ + if (hightext != 0) { +#ifdef WIDEC_CURSES + last_i = i + LYstrExtent2(data, datasize); +#endif + linkvlen = LYmbcsstrlen(hightext, utf_flag, YES); + } else { + linkvlen = 0; + } + if (i >= last_i) + i = last_i - 1; + + /* + * Scan through the data, making sure that we do not + * go over the COLS limit on the display etc. + */ + len = datasize; + end_of_data = data + len; + +#if defined(SHOW_WHEREIS_TARGETS) + /* + * If the target overlaps with the part of this line that + * we are drawing, it will be emphasized. + */ + i_after_tgt = i; + if (target) { + cp_tgt = LYno_attr_mb_strstr(sdata, + target, + utf_flag, YES, + &HitOffset, + &LenNeeded); + if (cp_tgt) { + if ((int) offset + LenNeeded > last_i || + ((int) offset + HitOffset >= XP + linkvlen)) { + cp_tgt = NULL; + } else { + i_start_tgt = i + HitOffset; + i_after_tgt = i + LenNeeded; + } + } + } else { + cp_tgt = NULL; + } +#endif /* SHOW_WHEREIS_TARGETS */ + + /* + * Iterate through the line data from the start, keeping track of + * the display ("glyph") position in i. Drawing will be turned + * on when either the first UTF-8 sequence (that occurs after + * XP_draw_min) is found, or when we reach the link itself (if + * highlight is non-NULL). - kw + */ + while ((i <= last_i) && data < end_of_data && (*data != '\0')) { + + if (hightext && i >= XP && !incurlink) { + + /* + * We reached the position of link itself, and hightext is + * non-NULL. We switch data from being a pointer into the HTLine + * to being a pointer into hightext. Normally (as long as this + * routine is applied to normal hyperlink anchors) the text in + * hightext will be identical to that part of the HTLine that + * data was already pointing to, except that special attribute + * chars LY_BOLD_START_CHAR etc., have been stripped out (see + * HText_trimHightext). So the switching should not result in + * any different display, but it ensures that it doesn't go + * unnoticed if somehow hightext got messed up somewhere else. + * This is also useful in preparation for using this function + * for something else than normal hyperlink anchors, i.e., form + * fields. + * Turn on drawing here or make sure it gets turned on before the + * next actual normal character is handled. - kw + */ + data = hightext; + len = (int) strlen(hightext); + end_of_data = hightext + len; + last_i = i + len; + XP_next += linkvlen; + incurlink = YES; +#ifdef SHOW_WHEREIS_TARGETS + if (cp_tgt) { + if (flag && i_after_tgt >= XP) + i_after_tgt = XP - 1; + } +#endif + /* + * The logic of where to set in-target drawing target etc. + * and when to react to it should be cleaned up (here and + * further below). For now this seems to work but isn't + * very clear. The complications arise from reproducing + * the behavior (previously done in LYhighlight()) for target + * strings that fall into or overlap a link: use target + * emphasis for the target string, except for the first + * and last character of the anchor text if the anchor is + * highlighted as "current link". - kw + */ + if (!drawing) { +#ifdef SHOW_WHEREIS_TARGETS + if (intarget) { + if (i_after_tgt > i) { + LYmove(YP, i); + if (flag) { + drawing = YES; + drawingtarget = NO; + if (inunderline) + inU = YES; + lynx_start_link_color(flag, inU); + } else { + drawing = YES; + drawingtarget = YES; + LYstartTargetEmphasis(); + } + } + } +#endif /* SHOW_WHEREIS_TARGETS */ + } else { +#ifdef SHOW_WHEREIS_TARGETS + if (intarget && i_after_tgt > i) { + if (flag && (data == hightext)) { + drawingtarget = NO; + LYstopTargetEmphasis(); + } + } else if (!intarget) +#endif /* SHOW_WHEREIS_TARGETS */ + { + if (inunderline) + inU = YES; + if (inunderline) + lynx_stop_underline(); + if (inbold) + lynx_stop_bold(); + lynx_start_link_color(flag, inU); + } + + } + } + if (i >= last_i || data >= end_of_data) + break; + if ((buffer[0] = *data) == '\0') + break; +#if defined(SHOW_WHEREIS_TARGETS) + /* + * Look for a subsequent occurrence of the target string, + * if we had a previous one and have now stepped past it. - kw + */ + if (cp_tgt && i >= i_after_tgt) { + if (intarget) { + + if (incurlink && flag && i == last_i - 1) + cp_tgt = NULL; + else + cp_tgt = LYno_attr_mb_strstr(sdata, + target, + utf_flag, YES, + &HitOffset, + &LenNeeded); + if (cp_tgt) { + i_start_tgt = i + HitOffset; + i_after_tgt = i + LenNeeded; + if (incurlink) { + if (flag && i_start_tgt == XP_link) + i_start_tgt++; + if (flag && i_start_tgt == last_i - 1) + i_start_tgt++; + if (flag && i_after_tgt >= last_i) + i_after_tgt = last_i - 1; + if (flag && i_start_tgt >= last_i) + cp_tgt = NULL; + } else if (i_start_tgt == last_i) { + if (flag) + i_start_tgt++; + } + } + if (!cp_tgt || i_start_tgt != i) { + intarget = NO; + if (drawing) { + if (drawingtarget) { + drawingtarget = NO; + LYstopTargetEmphasis(); + if (incurlink) { + lynx_start_link_color(flag, inU); + } + } + if (!incurlink) { + if (inbold) + lynx_start_bold(); + if (inunderline) + lynx_start_underline(); + } + } + } + } + } +#endif /* SHOW_WHEREIS_TARGETS */ + + /* + * Advance data to point to the next input char (for the + * next round). Advance sdata, used for searching for a + * target string, so that they stay in synch. As long + * as we are not within the highlight text, data and sdata + * have identical values. After we have switched data to + * point into hightext, sdata remains a pointer into the + * HTLine (so that we don't miss a partial target match at + * the end of the anchor text). So sdata has to sometimes + * skip additional special attribute characters that are + * not present in highlight in order to stay in synch. - kw + */ + data++; + if (incurlink) { + while (IsNormalChar(*sdata)) { + ++sdata; + } + } + + switch (buffer[0]) { + + case LY_UNDERLINE_START_CHAR: + if (!drawing || !incurlink) + inunderline = YES; + if (drawing && !intarget && !incurlink) + lynx_start_underline(); + break; + + case LY_UNDERLINE_END_CHAR: + inunderline = NO; + if (drawing && !intarget && !incurlink) + lynx_stop_underline(); + break; + + case LY_BOLD_START_CHAR: + if (!drawing || !incurlink) + inbold = YES; + if (drawing && !intarget && !incurlink) + lynx_start_bold(); + break; + + case LY_BOLD_END_CHAR: + inbold = NO; + if (drawing && !intarget && !incurlink) + lynx_stop_bold(); + break; + + case LY_SOFT_NEWLINE: + if (drawing) { + LYaddch('+'); + } + i++; + break; + + case LY_SOFT_HYPHEN: + if (*data != '\0' || + isspace(UCH(LastDisplayChar)) || + LastDisplayChar == '-') { + /* + * Ignore the soft hyphen if it is not the last + * character in the line. Also ignore it if it + * first character following the margin, or if it + * is preceded by a white character (we loaded 'M' + * into LastDisplayChar if it was a multibyte + * character) or hyphen, though it should have + * been excluded by HText_appendCharacter() or by + * split_line() in those cases. -FM + */ + break; + } else { + /* + * Make it a hard hyphen and fall through. -FM + */ + buffer[0] = '-'; + } + /* FALLTHRU */ + + default: + /* + * We have got an actual normal displayable character, or + * the start of one. Before proceeding check whether + * drawing needs to be turned on now. - kw + */ +#if defined(SHOW_WHEREIS_TARGETS) + if (incurlink && intarget && flag && i_after_tgt > i) { + if (i == last_i - 1) { + i_after_tgt = i; + } else if (i == last_i - 2 && IS_CJK_TTY && + is8bits(buffer[0])) { + i_after_tgt = i; + cp_tgt = NULL; + if (drawing) { + if (drawingtarget) { + LYstopTargetEmphasis(); + drawingtarget = NO; + lynx_start_link_color(flag, inU); + } + } + } + } + if (cp_tgt && i >= i_start_tgt && sdata > cp_tgt) { + if (!intarget || + (intarget && incurlink && !drawingtarget)) { + + if (incurlink && drawing && + !(flag && + (i == XP_link || i == last_i - 1))) { + lynx_stop_link_color(flag, inU); + } + if (incurlink && !drawing) { + LYmove(YP, i); + if (inunderline) + inU = YES; + if (flag && (i == XP_link || i == last_i - 1)) { + lynx_start_link_color(flag, inU); + drawingtarget = NO; + } else { + LYstartTargetEmphasis(); + drawingtarget = YES; + } + drawing = YES; + } else if (incurlink && drawing && + intarget && !drawingtarget && + (flag && + (i == XP_link))) { + if (inunderline) + inU = YES; + lynx_start_link_color(flag, inU); + } else if (drawing && + !(flag && + (i == XP_link || (incurlink && i == last_i - 1)))) { + LYstartTargetEmphasis(); + drawingtarget = YES; + } + intarget = YES; + } + } else +#endif /* SHOW_WHEREIS_TARGETS */ + if (incurlink) { + if (!drawing) { + LYmove(YP, i); + if (inunderline) + inU = YES; + lynx_start_link_color(flag, inU); + drawing = YES; + } + } + + i++; +#ifndef WIDEC_CURSES + if (utf_flag && is8bits(buffer[0])) { + hadutf8 = YES; + utf_extra = utf8_length(utf_flag, data - 1); + LastDisplayChar = 'M'; + } +#endif + if (utf_extra) { + LYStrNCpy(&buffer[1], data, utf_extra); + if (!drawing && i >= XP_draw_min) { + LYmove(YP, i - 1); + drawing = YES; +#if defined(SHOW_WHEREIS_TARGETS) + if (intarget) { + drawingtarget = YES; + LYstartTargetEmphasis(); + } else +#endif /* SHOW_WHEREIS_TARGETS */ + { + if (inbold) + lynx_start_bold(); + if (inunderline) + lynx_start_underline(); + } + } + LYaddstr(buffer); + buffer[1] = '\0'; + sdata += utf_extra; + data += utf_extra; + utf_extra = 0; + } else if (IS_CJK_TTY && is8bits(buffer[0]) + && (!conv_jisx0201kana && (kanji_code != SJIS))) { + /* + * For CJK strings, by Masanobu Kimura. + */ + if (drawing && (i <= last_i)) { + buffer[1] = *data; + LYaddstr(buffer); + buffer[1] = '\0'; + } + i++; + sdata++; + data++; + /* + * For now, load 'M' into LastDisplayChar, but we should + * check whether it's white and if so, use ' '. I don't + * know if there actually are white CJK characters, and + * we're loading ' ' for multibyte spacing characters in + * this code set, but this will become an issue when the + * development code set's multibyte character handling is + * used. -FM + */ + LastDisplayChar = 'M'; + } else { + if (drawing) { + LYaddstr(buffer); + } + LastDisplayChar = buffer[0]; + } + } /* end of switch */ + } /* end of while */ + + if (!drawing) { + LYmove(YP, XP_next); + lynx_start_link_color(flag, inU); + } else { +#if defined(SHOW_WHEREIS_TARGETS) + if (drawingtarget) { + LYstopTargetEmphasis(); + lynx_start_link_color(flag, inU); + } +#endif /* SHOW_WHEREIS_TARGETS */ + if (hadutf8) { + LYtouchline(YP); + } + } + return; +} +#endif /* !USE_COLOR_STYLE */ + +#ifndef USE_COLOR_STYLE +/* + * Move cursor position to a link's place in the display. + * The "moving to" is done by scanning through the line's + * character data in the corresponding HTLine of HTMainText, + * and starting to draw when a UTF-8 encoded non-ASCII character + * is encountered before the link (with some protection against + * overwriting form fields). This refreshing of preceding data is + * necessary for preventing curses's or slang's display logic from + * getting too clever; their logic counts character positions wrong + * since they don't know about multi-byte characters that take up + * only one screen position. So we have to make them forget their + * idea of what's in a screen line drawn previously. + * If hightext is non-NULL, it should be the anchor text for a normal + * link as stored in a links[] element, and the anchor text will be + * drawn too, with appropriate attributes. - kw + */ +void LYMoveToLink(int cur, + const char *target, + const char *hightext, + int flag, + int inU, + int utf_flag) +{ +#define pvtTITLE_HEIGHT 1 + HTLine *todr; + int i, n = 0; + int XP_draw_min = 0; + int flags = ((flag == TRUE) ? 1 : 0) | (inU ? 2 : 0); + + /* + * We need to protect changed form text fields preceding this + * link on the same line against overwriting. - kw + */ + for (i = cur - 1; i >= 0; i++) { + if (links[i].ly < links[cur].ly) + break; + if (links[i].type == WWW_FORM_LINK_TYPE) { + XP_draw_min = links[i].ly + links[i].l_form->size; + break; + } + } + + /* Find the right HTLine. */ + if (!HTMainText) { + todr = NULL; + } else if (HTMainText->stale) { + todr = FirstHTLine(HTMainText); + n = links[cur].ly - pvtTITLE_HEIGHT + HTMainText->top_of_screen; + } else { + todr = HTMainText->top_of_screen_line; + n = links[cur].ly - pvtTITLE_HEIGHT; + } + for (i = 0; i < n && todr; i++) { + todr = (todr == HTMainText->last_line) ? NULL : todr->next; + } + if (todr) { + if (target && *target == '\0') + target = NULL; + move_to_glyph(links[cur].ly, links[cur].lx, XP_draw_min, + todr->data, todr->size, todr->offset, + target, hightext, flags, utf_flag); + } else { + /* This should not happen. */ + move_to_glyph(links[cur].ly, links[cur].lx, XP_draw_min, + "", 0, (unsigned) links[cur].lx, + target, hightext, flags, utf_flag); + } +} +#endif /* !USE_COLOR_STYLE */ + +/* + * This is used only if compiled with lss support. It's called to redraw a + * regular link when it's being unhighlighted in LYhighlight(). + */ +#ifdef USE_COLOR_STYLE +void redraw_lines_of_link(int cur) +{ +#define pvtTITLE_HEIGHT 1 + HTLine *todr1; + int lines_back; + int row, col, count; + const char *text; + + if (HTMainText->next_line == HTMainText->last_line) { + /* we are at the last page - that is partially filled */ + lines_back = HTMainText->Lines - (links[cur].ly - pvtTITLE_HEIGHT + + HTMainText->top_of_screen); + } else { + lines_back = display_lines - (links[cur].ly - pvtTITLE_HEIGHT); + } + todr1 = HTMainText->next_line; + while (lines_back-- > 0) + todr1 = todr1->prev; + + row = links[cur].ly; + if (no_title) + row -= TITLE_LINES; + + for (count = 0; + row <= display_lines && (text = LYGetHiliteStr(cur, count)) != NULL; + ++count) { + col = LYGetHilitePos(cur, count); + if (col >= 0) { + LYmove(row, col); + redraw_part_of_line(todr1, text, (int) strlen(text), HTMainText); + } + todr1 = todr1->next; + row++; + } +#undef pvtTITLE_HEIGHT + return; +} +#endif + +#ifdef USE_PRETTYSRC +void HTMark_asSource(void) +{ + if (HTMainText) + HTMainText->source = TRUE; +} +#endif + +HTkcode HText_getKcode(HText *text) +{ + return text->kcode; +} + +void HText_updateKcode(HText *text, HTkcode kcode) +{ + text->kcode = kcode; +} + +HTkcode HText_getSpecifiedKcode(HText *text) +{ + return text->specified_kcode; +} + +void HText_updateSpecifiedKcode(HText *text, HTkcode kcode) +{ + text->specified_kcode = kcode; +} + +int HTMainText_Get_UCLYhndl(void) +{ + return (HTMainText ? + HTAnchor_getUCLYhndl(HTMainText->node_anchor, UCT_STAGE_MIME) + : -1); +} + +#ifdef USE_CACHEJAR +static int LYHandleCache(const char *arg, + HTParentAnchor *anAnchor, + HTFormat format_out, + HTStream *sink) +{ + HTFormat format_in = WWW_HTML; + HTStream *target = NULL; + char c; + char *buf = NULL; + char *title = NULL; + char *address = NULL; + char *content_type = NULL; + char *content_language = NULL; + char *content_encoding = NULL; + char *content_location = NULL; + char *content_disposition = NULL; + char *content_md5 = NULL; + char *message_id = NULL; + char *date = NULL; + char *owner = NULL; + char *subject = NULL; + char *expires = NULL; + char *ETag = NULL; + char *server = NULL; + char *FileCache = NULL; + char *last_modified = NULL; + char *cache_control = NULL; + +#ifdef USE_SOURCE_CACHE + char *source_cache_file = NULL; +#endif + off_t Size = 0; + int x = -1; + + /* + * Check if there is something to do. + */ + if (HTList_count(loaded_texts) == 0) { + HTProgress(CACHE_JAR_IS_EMPTY); + LYSleepMsg(); + HTNoDataOK = 1; + return (HT_NO_DATA); + } + + /* + * If # of LYNXCACHE:/# is number ask user if he/she want to delete it. + */ + if (sscanf(arg, STR_LYNXCACHE "/%d", &x) == 1 && x > 0) { + CTRACE((tfp, "LYNXCACHE number is %d\n", x)); + _statusline(CACHE_D_OR_CANCEL); + c = (char) LYgetch_single(); + if (c == 'D') { + HText *t = (HText *) HTList_objectAt(loaded_texts, x - 1); + + HTList_removeObjectAt(loaded_texts, x - 1); + HText_free(t); + } + return (HT_NO_DATA); + } + + /* + * If we get to here, it was a LYNXCACHE:/ URL for creating and displaying + * the Cache Jar Page. + * Set up an HTML stream and return an updated Cache Jar Page. + */ + target = HTStreamStack(format_in, + format_out, + sink, anAnchor); + if (target == NULL) { + HTSprintf0(&buf, CANNOT_CONVERT_I_TO_O, + HTAtom_name(format_in), HTAtom_name(format_out)); + HTAlert(buf); + FREE(buf); + return (HT_NOT_LOADED); + } + + /* + * Load HTML strings into buf and pass buf to the target for parsing and + * rendering. + */ +#define PUTS(buf) (*target->isa->put_block)(target, buf, (int) strlen(buf)) + + HTSprintf0(&buf, + "\n\n%s\n\n\n", + CACHE_JAR_TITLE); + PUTS(buf); + HTSprintf0(&buf, "

    %s (%s)%s%s

    \n", + LYNX_NAME, LYNX_VERSION, + HELP_ON_SEGMENT, + helpfilepath, CACHE_JAR_HELP, CACHE_JAR_TITLE); + PUTS(buf); + + /* + * Max number of cached documents is always same as HTCacheSize. + * We count them from oldest to newest. Currently cached document + * is *never* listed, resulting in maximal entries of Cache Jar + * to be HTCacheSize - 1 + */ + for (x = HTList_count(loaded_texts) - 1; x > 0; x--) { + /* + * The number of the document in the cache list, its title in a link, + * and its address and memory allocated for each cached document. + */ + HText *cachedoc = (HText *) HTList_objectAt(loaded_texts, x); + + if (cachedoc != 0) { + HTParentAnchor *docanchor = cachedoc->node_anchor; + + if (docanchor != 0) { +#ifdef USE_SOURCE_CACHE + source_cache_file = docanchor->source_cache_file; +#endif + Size = docanchor->content_length; + StrAllocCopy(title, docanchor->title); + StrAllocCopy(address, docanchor->address); + content_type = docanchor->content_type; + content_language = docanchor->content_language; + content_encoding = docanchor->content_encoding; + content_location = docanchor->content_location; + content_disposition = docanchor->content_disposition; + content_md5 = docanchor->content_md5; + message_id = docanchor->message_id; + owner = docanchor->owner; + StrAllocCopy(subject, docanchor->subject); + date = docanchor->date; + expires = docanchor->expires; + ETag = docanchor->ETag; + StrAllocCopy(server, docanchor->server); + FileCache = docanchor->FileCache; + last_modified = docanchor->last_modified; + cache_control = docanchor->cache_control; + } + } + + LYEntify(&address, TRUE); + if (isEmpty(title)) + StrAllocCopy(title, NO_TITLE); + else + LYEntify(&title, TRUE); + + HTSprintf0(&buf, + "

    %d. Title: %s
    URL: %s
    ", + x, STR_LYNXCACHE, x, title, address, address); + PUTS(buf); + if (Size > 0) { + HTSprintf0(&buf, "Size: %" PRI_off_t " ", CAST_off_t (Size)); + + PUTS(buf); + } + if (cachedoc != NULL && cachedoc->Lines > 0) { + HTSprintf0(&buf, "Lines: %d ", cachedoc->Lines); + PUTS(buf); + } + if (FileCache != NULL) { + HTSprintf0(&buf, "File-Cache: %s ", + FileCache, FileCache); + PUTS(buf); + } + if (cache_control != NULL) { + HTSprintf0(&buf, "Cache-Control: %s ", cache_control); + PUTS(buf); + } + if (content_type != NULL) { + HTSprintf0(&buf, "Content-Type: %s ", content_type); + PUTS(buf); + } + if (content_language != NULL) { + HTSprintf0(&buf, "Content-Language: %s ", content_language); + PUTS(buf); + } + if (content_encoding != NULL) { + HTSprintf0(&buf, "Content-Encoding: %s ", content_encoding); + PUTS(buf); + } + if (content_location != NULL) { + HTSprintf0(&buf, "Content-Location: %s ", content_location); + PUTS(buf); + } + if (content_disposition != NULL) { + HTSprintf0(&buf, "Content-Disposition: %s ", content_disposition); + PUTS(buf); + } + if (content_md5 != NULL) { + HTSprintf0(&buf, "Content-MD5: %s ", content_md5); + PUTS(buf); + } + if (message_id != NULL) { + HTSprintf0(&buf, "Message-ID: %s ", message_id); + PUTS(buf); + } + if (subject != NULL) { + LYEntify(&subject, TRUE); + HTSprintf0(&buf, "Subject: %s ", subject); + PUTS(buf); + } + if (owner != NULL) { + HTSprintf0(&buf, "Owner: %s ", owner, owner); + PUTS(buf); + } + if (date != NULL) { + HTSprintf0(&buf, "Date: %s ", date); + PUTS(buf); + } + if (expires != NULL) { + HTSprintf0(&buf, "Expires: %s ", expires); + PUTS(buf); + } + if (last_modified != NULL) { + HTSprintf0(&buf, "Last-Modified: %s ", last_modified); + PUTS(buf); + } + if (ETag != NULL) { + HTSprintf0(&buf, "ETag: %s ", ETag); + PUTS(buf); + } + if (server != NULL) { + LYEntify(&server, TRUE); + HTSprintf0(&buf, "Server: %s ", server); + PUTS(buf); + } +#ifdef USE_SOURCE_CACHE + if (source_cache_file != NULL) { + HTSprintf0(&buf, + "Source-Cache-File: %s", + source_cache_file, source_cache_file); + PUTS(buf); + } +#endif + HTSprintf0(&buf, "
    "); + PUTS(buf); + } + HTSprintf0(&buf, ""); + PUTS(buf); + FREE(subject); + FREE(title); + FREE(address); + FREE(server); + + /* + * Free the target to complete loading of the Cache Jar Page, and report a + * successful load. + */ + (*target->isa->_free) (target); + FREE(buf); + return (HT_LOADED); +} + +#ifdef GLOBALDEF_IS_MACRO +#define _LYCACHE_C_GLOBALDEF_1_INIT { "LYNXCACHE",LYHandleCache,0} +GLOBALDEF(HTProtocol, LYLynxCache, _LYCACHE_C_GLOBALDEF_1_INIT); +#else +GLOBALDEF HTProtocol LYLynxCache = +{"LYNXCACHE", LYHandleCache, 0}; +#endif /* GLOBALDEF_IS_MACRO */ +#endif /* USE_CACHEJAR */ diff --git a/src/GridText.h b/src/GridText.h new file mode 100644 index 0000000..9c68d3d --- /dev/null +++ b/src/GridText.h @@ -0,0 +1,302 @@ +/* + * $LynxId: GridText.h,v 1.70 2022/06/12 16:38:03 KIHARA.Hideto Exp $ + * + * Specialities of GridText as subclass of HText + */ +#ifndef LYGRIDTEXT_H +#define LYGRIDTEXT_H + +#include /* Superclass */ + +#ifndef HTFORMS_H +#include +#endif /* HTFORMS_H */ + +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif +#define TABSTOP 8 +#define SPACES " " /* must be at least TABSTOP spaces long */ +#define SPLAT '.' +#define NOCHOP 0 +#define CHOP 1 +/* just for information: +US-ASCII control characters <32 which are not defined in Unicode standard +=00 U+0000 NULL +=01 U+0001 START OF HEADING +=02 U+0002 START OF TEXT +=03 U+0003 END OF TEXT +=04 U+0004 END OF TRANSMISSION +=05 U+0005 ENQUIRY +=06 U+0006 ACKNOWLEDGE +=07 U+0007 BELL +=08 U+0008 BACKSPACE +=09 U+0009 HORIZONTAL TABULATION +=0A U+000A LINE FEED +=0B U+000B VERTICAL TABULATION +=0C U+000C FORM FEED +=0D U+000D CARRIAGE RETURN +=0E U+000E SHIFT OUT +=0F U+000F SHIFT IN +=10 U+0010 DATA LINK ESCAPE +=11 U+0011 DEVICE CONTROL ONE +=12 U+0012 DEVICE CONTROL TWO +=13 U+0013 DEVICE CONTROL THREE +=14 U+0014 DEVICE CONTROL FOUR +=15 U+0015 NEGATIVE ACKNOWLEDGE +=16 U+0016 SYNCHRONOUS IDLE +=17 U+0017 END OF TRANSMISSION BLOCK +=18 U+0018 CANCEL +=19 U+0019 END OF MEDIUM +=1A U+001A SUBSTITUTE +=1B U+001B ESCAPE +=1C U+001C FILE SEPARATOR +=1D U+001D GROUP SEPARATOR +=1E U+001E RECORD SEPARATOR +=1F U+001F UNIT SEPARATOR +=7F U+007F DELETE +*/ extern int HTCurSelectGroupType; + extern char *HTCurSelectGroupSize; + +#if defined(VMS) && defined(VAXC) && !defined(__DECC) + extern int HTVirtualMemorySize; +#endif /* VMS && VAXC && !__DECC */ + + extern HTChildAnchor *HText_childNextNumber(int n, void **prev); + extern int HText_findAnchorNumber(void *avoid); + extern void HText_FormDescNumber(int n, const char **desc); + +/* Is there any file left? +*/ + extern BOOL HText_canScrollUp(HText *text); + extern BOOL HText_canScrollDown(void); + +/* Move display within window +*/ + extern void HText_scrollUp(HText *text); /* One page */ + extern void HText_scrollDown(HText *text); /* One page */ + extern void HText_scrollTop(HText *text); + extern void HText_scrollBottom(HText *text); + extern void HText_pageDisplay(int line_num, char *target); + extern BOOL HText_pageHasPrevTarget(void); + + extern int HText_LinksInLines(HText *text, int line_num, int Lines); + + extern int HText_getAbsLineNumber(HText *text, int anchor_number); + extern int HText_closestAnchor(HText *text, int offset); + extern int HText_locateAnchor(HText *text, int anchor_number); + extern int HText_anchorRelativeTo(HText *text, int top_lineno, int anchor_num); + + extern void HText_setLastChar(HText *text, int ch); + extern char HText_getLastChar(HText *text); +#ifdef EXP_JAPANESE_SPACES + extern BOOL HText_checkLastChar_needSpaceOnJoinLines(HText *text); +#endif + + extern int HText_sourceAnchors(HText *text); + extern void HText_setStale(HText *text); + extern void HText_refresh(HText *text); + extern const char *HText_getTitle(void); + extern const char *HText_getSugFname(void); + extern void HTCheckFnameForCompression(char **fname, + HTParentAnchor *anchor, + int strip_ok); + extern const char *HText_getLastModified(void); + extern const char *HText_getDate(void); + extern const char *HText_getHttpHeaders(void); + extern const char *HText_getServer(void); + extern const char *HText_getOwner(void); + extern const char *HText_getContentBase(void); + extern const char *HText_getContentLocation(void); + extern const char *HText_getMessageID(void); + extern const char *HText_getRevTitle(void); + +#ifdef USE_COLOR_STYLE + extern const char *HText_getStyle(void); +#endif + extern void HText_setMainTextOwner(const char *owner); + extern void print_wwwfile_to_fd(FILE *fp, int is_email, int is_reply); + extern BOOL HText_select(HText *text); + extern BOOL HText_POSTReplyLoaded(DocInfo *doc); + extern BOOL HTFindPoundSelector(const char *selector); + extern int HTGetRelLinkNum(int num, int rel, int cur); + extern int HTGetLinkInfo(int number, + int want_go, + int *go_line, + int *linknum, + char **hightext, + char **lname); + extern BOOL HText_TAHasMoreLines(int curlink, + int direction); + extern int HTGetLinkOrFieldStart(int curlink, + int *go_line, + int *linknum, + int direction, + int ta_skip); + extern BOOL HText_getFirstTargetInLine(HText *text, + int line_num, + int utf_flag, + int *offset, + int *tLen, + char **data, + const char *target); + extern int HTisDocumentSource(void); + extern void HTuncache_current_document(void); + +#ifdef USE_SOURCE_CACHE + extern BOOLEAN HTreparse_document(void); + extern BOOLEAN HTcan_reparse_document(void); + extern BOOLEAN HTdocument_settings_changed(void); +#endif + + extern BOOL HTLoadedDocumentEightbit(void); + extern BOOL HText_LastLineEmpty(HText *me, int IgnoreSpaces); + extern BOOL HText_PreviousLineEmpty(HText *me, int IgnoreSpaces); + extern BOOL HText_inLineOne(HText *text); + extern BOOLEAN HTLoadedDocumentIsHEAD(void); + extern BOOLEAN HTLoadedDocumentIsSafe(void); + extern bstring *HTLoadedDocumentPost_data(void); + extern const char *HTLoadedDocumentBookmark(void); + extern const char *HTLoadedDocumentCharset(void); + extern const char *HTLoadedDocumentTitle(void); + extern const char *HTLoadedDocumentURL(void); + extern const char *HText_HiddenLinkAt(HText *text, int number); + extern int HText_HiddenLinkCount(HText *text); + extern int HText_LastLineOffset(HText *me); + extern int HText_LastLineSize(HText *me, int IgnoreSpaces); + extern int HText_PreviousLineSize(HText *me, int IgnoreSpaces); + extern int HText_getCurrentColumn(HText *text); + extern int HText_getLines(HText *text); + extern int HText_getMaximumColumn(HText *text); + extern int HText_getNumOfBytes(void); + extern int HText_getNumOfLines(void); + extern int HText_getPreferredTopLine(HText *text, int line_number); + extern int HText_getTabIDColumn(HText *text, const char *name); + extern int HText_getTopOfScreen(void); + extern int do_www_search(DocInfo *doc); + extern void HText_NegateLineOne(HText *text); + extern void HText_RemovePreviousLine(HText *text); + extern void HText_setNodeAnchorBookmark(const char *bookmark); + extern void HText_setTabID(HText *text, const char *name); + extern void *HText_pool_calloc(HText *text, unsigned size); + +/* "simple table" stuff */ + extern BOOLEAN HText_endStblTABLE(HText *); + extern int HText_trimCellLines(HText *text); + extern void HText_cancelStbl(HText *); + extern void HText_endStblCOLGROUP(HText *); + extern void HText_endStblTD(HText *); + extern void HText_endStblTR(HText *); + extern void HText_startStblCOL(HText *, int, int, int); + extern void HText_startStblRowGroup(HText *, int); + extern void HText_startStblTABLE(HText *, int); + extern void HText_startStblTD(HText *, int, int, int, int); + extern void HText_startStblTR(HText *, int); + +/* forms stuff */ + extern void HText_beginForm(char *action, + char *method, + char *enctype, + char *title, + const char *accept_cs); + extern void HText_endForm(HText *text); + extern void HText_beginSelect(char *name, + int name_cs, + int multiple, + char *len); + extern int HText_getOptionNum(HText *text); + extern char *HText_setLastOptionValue(HText *text, + char *value, + char *submit_value, + int order, + int checked, + int val_cs, + int submit_val_cs); + extern int HText_beginInput(HText *text, + int underline, + InputFieldData * I); + extern void HText_endInput(HText *text); + extern PerFormInfo *HText_PerFormInfo(int number); + extern int HText_SubmitForm(FormInfo * submit_item, DocInfo *doc, + const char *link_name, + const char *link_value); + extern void HText_DisableCurrentForm(void); + extern void HText_ResetForm(FormInfo * form); + extern void HText_activateRadioButton(FormInfo * form); + extern BOOLEAN HText_HaveUserChangedForms(HText *text); + + extern HTList *search_queries; /* Previous isindex and whereis queries */ + extern void HTSearchQueries_free(void); + extern void HTAddSearchQuery(char *query); + + extern void user_message(const char *message, + const char *argument); + +#define _user_message(msg, arg) mustshow = TRUE, user_message(msg, arg) + + extern void www_user_search(int start_line, + DocInfo *doc, + char *target, + int direction); + + extern void print_crawl_to_fd(FILE *fp, + char *thelink, + char *thetitle); + extern char *stub_HTAnchor_address(HTAnchor * me); + + extern void HText_setToolbar(HText *text); + extern BOOL HText_hasToolbar(HText *text); + + extern void HText_setNoCache(HText *text); + extern BOOL HText_hasNoCacheSet(HText *text); + + extern BOOL HText_hasUTF8OutputSet(HText *text); + extern void HText_setKcode(HText *text, + const char *charset, + LYUCcharset *p_in); + + extern void HText_setBreakPoint(HText *text); + + extern BOOL HText_AreDifferent(HTParentAnchor *anchor, + const char *full_address); + + extern int HText_EditTextArea(LinkInfo * form_link); + extern void HText_EditTextField(LinkInfo * form_link); + extern void HText_ExpandTextarea(LinkInfo * form_link, int newlines); + extern int HText_InsertFile(LinkInfo * form_link); + + extern void redraw_lines_of_link(int cur); + extern void LYMoveToLink(int cur, + const char *target, + const char *hightext, + int flag, + int inU, + int utf_flag); + +#ifdef USE_PRETTYSRC + extern void HTMark_asSource(void); +#endif + + extern int HTMainText_Get_UCLYhndl(void); + +#ifdef KANJI_CODE_OVERRIDE + extern HTkcode last_kcode; +#endif + + extern HTkcode HText_getKcode(HText *text); + extern void HText_updateKcode(HText *text, HTkcode kcode); + extern HTkcode HText_getSpecifiedKcode(HText *text); + extern void HText_updateSpecifiedKcode(HText *text, HTkcode kcode); + +#if defined(EXP_WCWIDTH_SUPPORT) || defined(EXP_JAPANESE_SPACES) + extern BOOL isUTF8CJChar(const char *s); +#endif + +#ifdef __cplusplus +} +#endif +#endif /* LYGRIDTEXT_H */ diff --git a/src/HTAlert.c b/src/HTAlert.c new file mode 100644 index 0000000..81594cf --- /dev/null +++ b/src/HTAlert.c @@ -0,0 +1,1201 @@ +/* + * $LynxId: HTAlert.c,v 1.103 2017/07/02 19:54:30 tom Exp $ + * + * Displaying messages and getting input for Lynx Browser + * ========================================================== + * + * REPLACE THIS MODULE with a GUI version in a GUI environment! + * + * History: + * Jun 92 Created May 1992 By C.T. Barker + * Feb 93 Simplified, portablised TBL + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* store statusline messages */ + +#include + +#include + +#undef timezone /* U/Win defines this in time.h, hides implementation detail */ + +#if defined(HAVE_FTIME) && defined(HAVE_SYS_TIMEB_H) +#include +#endif + +/* + * 'napms()' is preferable to 'sleep()' in any case because it does not + * interfere with output, but also because it can be less than a second. + */ +#ifdef HAVE_NAPMS +#define LYSleep(n) napms(n) +#else +#define LYSleep(n) sleep((unsigned)n) +#endif + +/* Issue a message about a problem. HTAlert() + * -------------------------------- + */ +void HTAlert(const char *Msg) +{ + CTRACE((tfp, "\nAlert!: %s\n\n", Msg)); + CTRACE_FLUSH(tfp); + _user_message(ALERT_FORMAT, Msg); + LYstore_message2(ALERT_FORMAT, Msg); + + if (dump_output_immediately && dump_to_stderr) { + fflush(stdout); + fprintf(stderr, ALERT_FORMAT, Msg); + fputc('\n', stderr); + fflush(stderr); + } + + LYSleepAlert(); +} + +void HTAlwaysAlert(const char *extra_prefix, + const char *Msg) +{ + if (!dump_output_immediately && LYCursesON) { + HTAlert(Msg); + } else { + if (extra_prefix) { + fprintf(((TRACE) ? stdout : stderr), + "%s %s!\n", + extra_prefix, Msg); + fflush(stdout); + LYstore_message2(ALERT_FORMAT, Msg); + LYSleepAlert(); + } else { + fprintf(((TRACE) ? stdout : stderr), ALERT_FORMAT, NonNull(Msg)); + fflush(stdout); + LYstore_message2(ALERT_FORMAT, Msg); + LYSleepAlert(); + fprintf(((TRACE) ? stdout : stderr), "\n"); + } + CTRACE((tfp, "\nAlert!: %s\n\n", Msg)); + CTRACE_FLUSH(tfp); + } +} + +/* Issue an informational message. HTInfoMsg() + * -------------------------------- + */ +void HTInfoMsg(const char *Msg) +{ + _statusline(Msg); + if (non_empty(Msg)) { + CTRACE((tfp, "Info message: %s\n", Msg)); + LYstore_message(Msg); + LYSleepInfo(); + } +} + +void HTInfoMsg2(const char *Msg2, const char *Arg) +{ + _user_message(Msg2, Arg); + if (non_empty(Msg2)) { + CTRACE((tfp, "Info message: ")); + CTRACE((tfp, Msg2, Arg)); + CTRACE((tfp, "\n")); + LYstore_message2(Msg2, Arg); + LYSleepInfo(); + } +} + +/* Issue an important message. HTUserMsg() + * -------------------------------- + */ +void HTUserMsg(const char *Msg) +{ + _statusline(Msg); + if (non_empty(Msg)) { + CTRACE((tfp, "User message: %s\n", Msg)); + LYstore_message(Msg); +#if !(defined(USE_SLANG) || defined(WIDEC_CURSES)) + if (IS_CJK_TTY) { + clearok(curscr, TRUE); + LYrefresh(); + } +#endif + LYSleepMsg(); + } +} + +void HTUserMsg2(const char *Msg2, const char *Arg) +{ + _user_message(Msg2, Arg); + if (non_empty(Msg2)) { + CTRACE((tfp, "User message: ")); + CTRACE((tfp, Msg2, Arg)); + CTRACE((tfp, "\n")); + LYstore_message2(Msg2, Arg); + LYSleepMsg(); + } +} + +/* Issue a progress message. HTProgress() + * ------------------------- + */ +void HTProgress(const char *Msg) +{ + statusline(Msg); + LYstore_message(Msg); + CTRACE((tfp, "%s\n", Msg)); + LYSleepDelay(); +} + +const char *HTProgressUnits(int rate) +{ + static const char *bunits = 0; + static const char *kbunits = 0; + + if (!bunits) { + bunits = gettext("bytes"); + kbunits = gettext(LYTransferName); + } + return ((rate == rateKB) +#ifdef USE_READPROGRESS + || (rate == rateEtaKB) + || (rate == rateEtaKB2) +#endif + )? kbunits : bunits; +} + +static const char *sprint_bytes(char *s, off_t n, const char *was_units) +{ + static off_t kb_units = 1024; + const char *u = HTProgressUnits(LYTransferRate); + + if (isRateInKB(LYTransferRate)) { + if (n >= 10 * kb_units) { + sprintf(s, "%" PRI_off_t, CAST_off_t (n / kb_units)); + } else if (n > 999) { /* Avoid switching between 1016b/s and 1K/s */ + sprintf(s, "%.2g", ((double) n) / (double) kb_units); + } else { + sprintf(s, "%" PRI_off_t, CAST_off_t (n)); + + u = HTProgressUnits(rateBYTES); + } + } else { + sprintf(s, "%" PRI_off_t, CAST_off_t (n)); + } + + if (!was_units || was_units != u) + sprintf(s + strlen(s), " %s", u); + return u; +} + +#ifdef USE_READPROGRESS +#define TIME_HMS_LENGTH (36) +static char *sprint_tbuf(char *s, long t) +{ + const char *format = ((LYTransferRate == rateEtaBYTES2 || + LYTransferRate == rateEtaKB2) + ? "% 2ld%c" + : "%ld%c"); + char *base = s; + + if (t < 0) { + strcpy(s, "forever"); + } else { + if (t > (3600 * 24)) { + sprintf(s, format, t / (3600 * 24), 'd'); + s += strlen(s); + t %= (3600 * 24); + } + if (t > 3600) { + sprintf(s, format, t / 3600, 'h'); + s += strlen(s); + t %= 3600; + } + if (t > 60) { + sprintf(s, format, t / 60, 'm'); + s += strlen(s); + t %= 60; + } + if (s == base) { + sprintf(s, "% 2ld sec", t); + } else if (t != 0) { + sprintf(s, format, t, 's'); + } + } + return base; +} +#endif /* USE_READPROGRESS */ + +/* Issue a read-progress message. HTReadProgress() + * ------------------------------ + */ +void HTReadProgress(off_t bytes, off_t total) +{ + static off_t bytes_last, total_last; + static off_t transfer_rate = 0; + static char *line = NULL; + char bytesp[80], totalp[80], transferp[80]; + int renew = 0; + const char *was_units; + +#ifdef HAVE_GETTIMEOFDAY + struct timeval tv; + double now; + static double first, last, last_active; + + gettimeofday(&tv, (struct timezone *) 0); + now = (double) tv.tv_sec + (double) tv.tv_usec / 1000000.; +#else +#if defined(HAVE_FTIME) && defined(HAVE_SYS_TIMEB_H) + static double now, first, last, last_active; + struct timeb tb; + + ftime(&tb); + now = tb.time + (double) tb.millitm / 1000; +#else + time_t now = time((time_t *) 0); /* once per second */ + static time_t first, last, last_active; +#endif +#endif + + if (!LYShowTransferRate) + LYTransferRate = rateOFF; + + if (bytes == 0) { + first = last = last_active = now; + bytes_last = bytes; + } else if (bytes < 0) { /* stalled */ + bytes = bytes_last; + total = total_last; + } + + /* 1 sec delay for transfer_rate calculation without g-t-o-d */ + if ((bytes > 0) && + (now > first)) { + if (transfer_rate <= 0) { /* the very first time */ + transfer_rate = (off_t) ((double) (bytes) / (now - first)); + /* bytes/sec */ + } + total_last = total; + + /* + * Optimal refresh time: every 0.2 sec + */ +#if defined(HAVE_GETTIMEOFDAY) || (defined(HAVE_FTIME) && defined(HAVE_SYS_TIMEB_H)) + if (now >= last + 0.2) + renew = 1; +#else + /* + * Use interpolation. (The transfer rate may be not constant + * when we have partial content in a proxy. We adjust transfer_rate + * once a second to minimize interpolation error below.) + */ + if ((now != last) || ((bytes - bytes_last) > (transfer_rate / 5))) { + renew = 1; + bytes_last += (transfer_rate / 5); /* until we got next second */ + } +#endif + if (renew) { + if (now > last) { + last = now; + if (bytes_last != bytes) + last_active = now; + bytes_last = bytes; + transfer_rate = (off_t) ((double) bytes / (now - first)); /* more accurate value */ + } + + if (total > 0) + was_units = sprint_bytes(totalp, total, 0); + else + was_units = 0; + sprint_bytes(bytesp, bytes, was_units); + + switch ((TransferRate) LYTransferRate) { +#ifdef USE_PROGRESSBAR + case rateBAR: + /* + * If we know the total size of the file, we can compute + * a percentage, and show a corresponding progress bar. + */ + HTSprintf0(&line, gettext("Read %s of data"), bytesp); + + if (total > 0) { + float percent = (float) bytes / (float) total; + int meter = (int) (((float) LYcolLimit * percent) - 5); + + CTRACE((tfp, "rateBAR: bytes: %" PRI_off_t ", total: " + "%" PRI_off_t "\n", + CAST_off_t (bytes), + CAST_off_t (total))); + CTRACE((tfp, "meter = %d\n", meter)); + + HTSprintf0(&line, "%d%% ", (int) (percent * 100)); + while (meter-- > 0) + StrAllocCat(line, "I"); + + CTRACE((tfp, "%s\n", line)); + CTRACE_FLUSH(tfp); + } + break; +#endif + default: + if (total > 0) { + HTSprintf0(&line, gettext("Read %s of %s of data"), + bytesp, totalp); + } else { + HTSprintf0(&line, gettext("Read %s of data"), bytesp); + } + + if (LYTransferRate != rateOFF + && transfer_rate > 0) { + sprint_bytes(transferp, transfer_rate, 0); + HTSprintf(&line, gettext(", %s/sec"), transferp); + } + break; + } + +#ifdef USE_READPROGRESS + if (LYTransferRate == rateEtaBYTES + || LYTransferRate == rateEtaKB + || LYTransferRate == rateEtaBYTES2 + || LYTransferRate == rateEtaKB2) { + char tbuf[TIME_HMS_LENGTH]; + + if (now - last_active >= 5) + HTSprintf(&line, + gettext(" (stalled for %s)"), + sprint_tbuf(tbuf, (long) (now - last_active))); + if (total > 0 && transfer_rate) + HTSprintf(&line, + gettext(", ETA %s"), + sprint_tbuf(tbuf, (long) ((total - bytes) / transfer_rate))); + } +#endif + + switch ((TransferRate) LYTransferRate) { +#ifdef USE_PROGRESSBAR + case rateBAR: + /* + * If we were not able to show a progress bar, just show + * a "." for progress. + */ + if (total <= 0) + StrAllocCat(line, "."); + break; +#endif + default: + StrAllocCat(line, "."); + break; + } + + if (total < -1) + StrAllocCat(line, gettext(" (Press 'z' to abort)")); + + /* do not store the message for history page. */ + statusline(line); + CTRACE((tfp, "%s\n", line)); + } + } +#ifdef LY_FIND_LEAKS + FREE(line); +#endif +} + +static BOOL conf_cancelled = NO; /* used by HTConfirm only - kw */ + +BOOL HTLastConfirmCancelled(void) +{ + if (conf_cancelled) { + conf_cancelled = NO; /* reset */ + return (YES); + } else { + return (NO); + } +} + +/* + * Prompt for yes/no response, but let a configuration variable override + * the prompt entirely. + */ +int HTForcedPrompt(int option, const char *msg, int dft) +{ + int result = FALSE; + const char *show = NULL; + char *msg2 = NULL; + + if (option == FORCE_PROMPT_DFT) { + result = HTConfirmDefault(msg, dft); + } else { + if (option == FORCE_PROMPT_YES) { + show = gettext("yes"); + result = YES; + } else if (option == FORCE_PROMPT_NO) { + show = gettext("no"); + result = NO; + } else { + return HTConfirmDefault(msg, dft); /* bug... */ + } + HTSprintf(&msg2, "%s %s", msg, show); + HTUserMsg(msg2); + free(msg2); + } + return result; +} + +#define DFT_CONFIRM ~(YES|NO) + +/* Seek confirmation with default answer. HTConfirmDefault() + * -------------------------------------- + */ +int HTConfirmDefault(const char *Msg, int Dft) +{ +/* Meta-note: don't move the following note from its place right + in front of the first gettext(). As it is now, it should + automatically appear in generated lynx.pot files. - kw + */ + +/* NOTE TO TRANSLATORS: If you provide a translation for "yes", lynx + * will take the first byte of the translation as a positive response + * to Yes/No questions. If you provide a translation for "no", lynx + * will take the first byte of the translation as a negative response + * to Yes/No questions. For both, lynx will also try to show the + * first byte in the prompt as a character, instead of (y) or (n), + * respectively. This will not work right for multibyte charsets! + * Don't translate "yes" and "no" for CJK character sets (or translate + * them to "yes" and "no"). For a translation using UTF-8, don't + * translate if the translation would begin with anything but a 7-bit + * (US_ASCII) character. That also means do not translate if the + * translation would begin with anything but a 7-bit character, if + * you use a single-byte character encoding (a charset like ISO-8859-n) + * but anticipate that the message catalog may be used re-encoded in + * UTF-8 form. + * For translations using other character sets, you may also wish to + * leave "yes" and "no" untranslated, if using (y) and (n) is the + * preferred behavior. + * Lynx will also accept y Y n N as responses unless there is a conflict + * with the first letter of the "yes" or "no" translation. + */ + const char *msg_yes = gettext("yes"); + const char *msg_no = gettext("no"); + int result = -1; + + /* If they're not really distinct in the first letter, revert to English */ + if (TOUPPER(*msg_yes) == TOUPPER(*msg_no)) { + msg_yes = "yes"; + msg_no = "no"; + } + + conf_cancelled = NO; + if (dump_output_immediately) { /* Non-interactive, can't respond */ + if (Dft == DFT_CONFIRM) { + CTRACE((tfp, "Confirm: %s (%c/%c) ", Msg, *msg_yes, *msg_no)); + } else { + CTRACE((tfp, "Confirm: %s (%c) ", Msg, (Dft == YES) ? *msg_yes : *msg_no)); + } + CTRACE((tfp, "- NO, not interactive.\n")); + result = NO; + } else { + char *msg = NULL; + char fallback_y = 'y'; /* English letter response as fallback */ + char fallback_n = 'n'; /* English letter response as fallback */ + + if (fallback_y == *msg_yes || fallback_y == *msg_no) + fallback_y = '\0'; /* conflict or duplication, don't use */ + if (fallback_n == *msg_yes || fallback_n == *msg_no) + fallback_n = '\0'; /* conflict or duplication, don't use */ + + if (Dft == DFT_CONFIRM) + HTSprintf0(&msg, "%s (%c/%c) ", Msg, *msg_yes, *msg_no); + else + HTSprintf0(&msg, "%s (%c) ", Msg, (Dft == YES) ? *msg_yes : *msg_no); + if (LYTraceLogFP) { + CTRACE((tfp, "Confirm: %s", msg)); + } + _statusline(msg); + FREE(msg); + + while (result < 0) { + int c = LYgetch_single(); + +#ifdef VMS + if (HadVMSInterrupt) { + HadVMSInterrupt = FALSE; + c = TOUPPER(*msg_no); + } +#endif /* VMS */ + if (c == TOUPPER(*msg_yes)) { + result = YES; + } else if (c == TOUPPER(*msg_no)) { + result = NO; + } else if (fallback_y && c == fallback_y) { + result = YES; + } else if (fallback_n && c == fallback_n) { + result = NO; + } else if (LYCharIsINTERRUPT(c)) { /* remember we had ^G or ^C */ + conf_cancelled = YES; + result = NO; + } else if (Dft != DFT_CONFIRM) { + result = Dft; + break; + } + } + CTRACE((tfp, "- %s%s.\n", + (result != NO) ? "YES" : "NO", + conf_cancelled ? ", cancelled" : "")); + } + return (result); +} + +/* Seek confirmation. HTConfirm() + * ------------------ + */ +BOOL HTConfirm(const char *Msg) +{ + return (BOOL) HTConfirmDefault(Msg, DFT_CONFIRM); +} + +/* + * Ask a post resubmission prompt with some indication of what would + * be resubmitted, useful especially for going backward in history. + * Try to use parts of the address or, if given, the title, depending + * on how much fits on the statusline. + * if_imgmap and if_file indicate how to handle an address that is + * a "LYNXIMGMAP:", or a "file:" URL (presumably the List Page file), + * respectively: 0: auto-deny, 1: auto-confirm, 2: prompt. + * - kw + */ + +BOOL confirm_post_resub(const char *address, + const char *title, + int if_imgmap, + int if_file) +{ + size_t len1; + const char *msg = CONFIRM_POST_RESUBMISSION_TO; + char buf[240]; + char *temp = NULL; + BOOL res; + size_t maxlen = (size_t) (LYcolLimit - 5); + + if (!address) { + return (NO); + } else if (isLYNXIMGMAP(address)) { + if (if_imgmap <= 0) + return (NO); + else if (if_imgmap == 1) + return (YES); + else + msg = CONFIRM_POST_LIST_RELOAD; + } else if (isFILE_URL(address)) { + if (if_file <= 0) + return (NO); + else if (if_file == 1) + return (YES); + else + msg = CONFIRM_POST_LIST_RELOAD; + } else if (dump_output_immediately) { + return (NO); + } + if (maxlen >= sizeof(buf)) + maxlen = sizeof(buf) - 1; + if ((len1 = strlen(msg)) + + strlen(address) <= maxlen) { + sprintf(buf, msg, address); + return HTConfirm(buf); + } + if (len1 + strlen(temp = HTParse(address, "", + PARSE_ACCESS + PARSE_HOST + PARSE_PATH + + PARSE_PUNCTUATION)) <= maxlen) { + sprintf(buf, msg, temp); + res = HTConfirm(buf); + FREE(temp); + return (res); + } + FREE(temp); + if (title && (len1 + strlen(title) <= maxlen)) { + sprintf(buf, msg, title); + return HTConfirm(buf); + } + if (len1 + strlen(temp = HTParse(address, "", + PARSE_ACCESS + PARSE_HOST + + PARSE_PUNCTUATION)) <= maxlen) { + sprintf(buf, msg, temp); + res = HTConfirm(buf); + FREE(temp); + return (res); + } + FREE(temp); + if ((temp = HTParse(address, "", PARSE_HOST)) && *temp && + len1 + strlen(temp) <= maxlen) { + sprintf(buf, msg, temp); + res = HTConfirm(buf); + FREE(temp); + return (res); + } + FREE(temp); + return HTConfirm(CONFIRM_POST_RESUBMISSION); +} + +/* Prompt for answer and get text back. HTPrompt() + * ------------------------------------ + */ +char *HTPrompt(const char *Msg, const char *deflt) +{ + char *rep = NULL; + bstring *data = NULL; + + _statusline(Msg); + BStrCopy0(data, deflt ? deflt : ""); + + if (!dump_output_immediately) + (void) LYgetBString(&data, FALSE, 0, NORECALL); + + StrAllocCopy(rep, data->str); + + BStrFree(data); + return rep; +} + +/* + * Prompt for password without echoing the reply. HTPromptPassword() + * ---------------------------------------------- + */ +char *HTPromptPassword(const char *Msg, const char *given) +{ + char *result = NULL; + bstring *data = NULL; + + if (isEmpty(given)) + given = ""; + if (!dump_output_immediately) { + _statusline(Msg ? Msg : PASSWORD_PROMPT); + BStrCopy0(data, given); + (void) LYgetBString(&data, TRUE, 0, NORECALL); + StrAllocCopy(result, data->str); + BStrFree(data); + } else { + printf("\n%s\n", PASSWORD_REQUIRED); + StrAllocCopy(result, given); + } + return result; +} + +/* Prompt both username and password. HTPromptUsernameAndPassword() + * ---------------------------------- + * + * On entry, + * Msg is the prompting message. + * *username and + * *password are char pointers which contain default + * or zero-length strings; they are changed + * to point to result strings. + * IsProxy should be TRUE if this is for + * proxy authentication. + * + * If *username is not NULL, it is taken + * to point to a default value. + * Initial value of *password is + * completely discarded. + * + * On exit, + * *username and *password point to newly allocated + * strings -- original strings pointed to by them + * are NOT freed. + * + */ +void HTPromptUsernameAndPassword(const char *Msg, + char **username, + char **password, + int IsProxy) +{ + if ((IsProxy == FALSE && + authentication_info[0] && authentication_info[1]) || + (IsProxy == TRUE && + proxyauth_info[0] && proxyauth_info[1])) { + /* + * The -auth or -pauth parameter gave us both the username + * and password to use for the first realm or proxy server, + * respectively, so just use them without any prompting. - FM + */ + StrAllocCopy(*username, (IsProxy ? + proxyauth_info[0] : authentication_info[0])); + if (IsProxy) { + FREE(proxyauth_info[0]); + } else { + FREE(authentication_info[0]); + } + StrAllocCopy(*password, (IsProxy ? + proxyauth_info[1] : authentication_info[1])); + if (IsProxy) { + FREE(proxyauth_info[1]); + } else { + FREE(authentication_info[1]); + } + } else if (dump_output_immediately) { + /* + * We are not interactive and don't have both the + * username and password from the command line, + * but might have one or the other. - FM + */ + if ((IsProxy == FALSE && authentication_info[0]) || + (IsProxy == TRUE && proxyauth_info[0])) { + /* + * Use the command line username. - FM + */ + StrAllocCopy(*username, (IsProxy ? + proxyauth_info[0] : authentication_info[0])); + if (IsProxy) { + FREE(proxyauth_info[0]); + } else { + FREE(authentication_info[0]); + } + } else if (isEmpty(*username)) { + /* + * Default to "WWWuser". - FM + */ + StrAllocCopy(*username, "WWWuser"); + } + if ((IsProxy == FALSE && authentication_info[1]) || + (IsProxy == TRUE && proxyauth_info[1])) { + /* + * Use the command line password. - FM + */ + StrAllocCopy(*password, (IsProxy ? + proxyauth_info[1] : authentication_info[1])); + if (IsProxy) { + FREE(proxyauth_info[1]); + } else { + FREE(authentication_info[1]); + } + } else if (isEmpty(*password)) { + /* + * Default to a zero-length string. - FM + */ + StrAllocCopy(*password, ""); + } + printf("\n%s\n", USERNAME_PASSWORD_REQUIRED); + + } else { + /* + * We are interactive and don't have both the + * username and password from the command line, + * but might have one or the other. - FM + */ + if ((IsProxy == FALSE && authentication_info[0]) || + (IsProxy == TRUE && proxyauth_info[0])) { + /* + * Offer the command line username in the + * prompt for the first realm. - FM + */ + StrAllocCopy(*username, (IsProxy ? + proxyauth_info[0] : authentication_info[0])); + if (IsProxy) { + FREE(proxyauth_info[0]); + } else { + FREE(authentication_info[0]); + } + } + /* + * Prompt for confirmation or entry of the username. - FM + */ + if (Msg != NULL) { + *username = HTPrompt(Msg, *username); + } else { + *username = HTPrompt(USERNAME_PROMPT, *username); + } + if ((IsProxy == FALSE && authentication_info[1]) || + (IsProxy == TRUE && proxyauth_info[1])) { + /* + * Use the command line password for the first realm. - FM + */ + StrAllocCopy(*password, (IsProxy ? + proxyauth_info[1] : authentication_info[1])); + if (IsProxy) { + FREE(proxyauth_info[1]); + } else { + FREE(authentication_info[1]); + } + } else if (non_empty(*username)) { + *password = HTPromptPassword(PASSWORD_PROMPT, *password); + } else { + /* + * Return a zero-length password. - FM + */ + StrAllocCopy(*password, ""); + } + } +} + +/* Confirm a cookie operation. HTConfirmCookie() + * --------------------------- + * + * On entry, + * server is the server sending the Set-Cookie. + * domain is the domain of the cookie. + * path is the path of the cookie. + * name is the name of the cookie. + * value is the value of the cookie. + * + * On exit, + * Returns FALSE on cancel, + * TRUE if the cookie should be set. + */ +BOOL HTConfirmCookie(domain_entry * de, const char *server, + const char *name, + const char *value) +{ + int ch; + const char *prompt = ADVANCED_COOKIE_CONFIRMATION; + + if (de == NULL) + return FALSE; + + /* If the user has specified a list of domains to allow or deny + * from the config file, then they'll already have de->bv set to + * ACCEPT_ALWAYS or REJECT_ALWAYS so we can relax and let the + * default cookie handling code cope with this fine. + */ + + /* + * If the user has specified a constant action, don't prompt at all. + */ + if (de->bv == ACCEPT_ALWAYS) + return TRUE; + if (de->bv == REJECT_ALWAYS) + return FALSE; + + if (dump_output_immediately) { + /* + * Non-interactive, can't respond. Use the LYSetCookies value + * based on its compilation or configuration setting, or on the + * command line toggle. - FM + */ + return LYSetCookies; + } + + /* + * Estimate how much of the cookie we can show. + */ + if (!LYAcceptAllCookies) { + int namelen, valuelen, space_free, percentage; + char *message = 0; + + space_free = (LYcolLimit + - (LYstrCells(prompt) + - 10) /* %s and %.*s and %.*s chars */ + -(int) strlen(server)); + if (space_free < 0) + space_free = 0; + namelen = (int) strlen(name); + valuelen = (int) strlen(value); + if ((namelen + valuelen) > space_free) { + /* + * Argh... there isn't enough space on our single line for + * the whole cookie. Reduce them both by a percentage. + * This should be smarter. + */ + percentage = (100 * space_free) / (namelen + valuelen); + namelen = (percentage * namelen) / 100; + valuelen = (percentage * valuelen) / 100; + } + HTSprintf(&message, prompt, server, namelen, name, valuelen, value); + _statusline(message); + FREE(message); + } + for (;;) { + if (LYAcceptAllCookies) { + ch = 'A'; + } else { + ch = LYgetch_single(); +#if defined(LOCALE) && defined(HAVE_GETTEXT) + { +#define L_PAREN '(' +#define R_PAREN ')' + /* + * Special-purpose workaround for gettext support (we should do + * this in a more general way) -TD + * + * NOTE TO TRANSLATORS: If the prompt has been rendered into + * another language, and if yes/no are distinct, assume the + * translator can make an ordered list in parentheses with one + * capital letter for each as we assumed in HTConfirmDefault(). + * The list has to be in the same order as in the original message, + * and the four capital letters chosen to not match those in the + * original unless they have the same position. + * + * Example: + * (Y/N/Always/neVer) - English (original) + * (O/N/Toujours/Jamais) - French + */ + char *p = gettext("Y/N/A/V"); /* placeholder for comment */ + const char *s = "YNAV\007\003"; /* see ADVANCED_COOKIE_CONFIRMATION */ + + if (StrChr(s, ch) == 0 + && isalpha(ch) + && (p = strrchr(prompt, L_PAREN)) != 0) { + + CTRACE((tfp, "Looking for %c in %s\n", ch, p)); + while (*p != R_PAREN && *p != 0 && isalpha(UCH(*s))) { + if (isalpha(UCH(*p)) && (*p == TOUPPER(*p))) { + CTRACE((tfp, "...testing %c/%c\n", *p, *s)); + if (*p == ch) { + ch = *s; + break; + } + ++s; + } + ++p; + } + } + } +#endif + } +#ifdef VMS + if (HadVMSInterrupt) { + HadVMSInterrupt = FALSE; + ch = 'N'; + } +#endif /* VMS */ + switch (ch) { + case 'A': + /* + * Set to accept all cookies for this domain. + */ + de->bv = ACCEPT_ALWAYS; + HTUserMsg2(ALWAYS_ALLOWING_COOKIES, de->domain); + return TRUE; + + case 'N': + /* + * Reject the cookie. + */ + reject: + HTUserMsg(REJECTING_COOKIE); + return FALSE; + + case 'V': + /* + * Set to reject all cookies from this domain. + */ + de->bv = REJECT_ALWAYS; + HTUserMsg2(NEVER_ALLOWING_COOKIES, de->domain); + return FALSE; + + case 'Y': + /* + * Accept the cookie. + */ + HTInfoMsg(ALLOWING_COOKIE); + return TRUE; + + default: + if (LYCharIsINTERRUPT(ch)) + goto reject; + continue; + } + } +} + +/* Confirm redirection of POST. HTConfirmPostRedirect() + * ---------------------------- + * + * On entry, + * Redirecting_url is the Location. + * server_status is the server status code. + * + * On exit, + * Returns 0 on cancel, + * 1 for redirect of POST with content, + * 303 for redirect as GET without content + */ +int HTConfirmPostRedirect(const char *Redirecting_url, int server_status) +{ + int result = -1; + char *show_POST_url = NULL; + char *StatusInfo = 0; + char *url = 0; + int on_screen = 0; /* 0 - show menu + + * 1 - show url + * 2 - menu is already on screen */ + + if (server_status == 303 || + server_status == 302) { + /* + * HTTP.c should not have called us for either of + * these because we're treating 302 as historical, + * so just return 303. - FM + */ + return 303; + } + + if (dump_output_immediately) { + if (server_status == 301) { + /* + * Treat 301 as historical, i.e., like 303 (GET + * without content), when not interactive. - FM + */ + return 303; + } else { + /* + * Treat anything else (e.g., 305, 306 or 307) as too + * dangerous to redirect without confirmation, and thus + * cancel when not interactive. - FM + */ + return 0; + } + } + + if (user_mode == NOVICE_MODE) { + on_screen = 2; + LYmove(LYlines - 2, 0); + HTSprintf0(&StatusInfo, SERVER_ASKED_FOR_REDIRECTION, server_status); + LYaddstr(StatusInfo); + LYclrtoeol(); + LYmove(LYlines - 1, 0); + HTSprintf0(&url, "URL: %.*s", + (LYcols < 250 ? LYcolLimit - 5 : 250), Redirecting_url); + LYaddstr(url); + LYclrtoeol(); + if (server_status == 301) { + _statusline(PROCEED_GET_CANCEL); + } else { + _statusline(PROCEED_OR_CANCEL); + } + } else { + HTSprintf0(&StatusInfo, "%d %.*s", + server_status, + 251, + ((server_status == 301) ? + ADVANCED_POST_GET_REDIRECT : + ADVANCED_POST_REDIRECT)); + StrAllocCopy(show_POST_url, LOCATION_HEADER); + StrAllocCat(show_POST_url, Redirecting_url); + } + while (result < 0) { + int c; + + switch (on_screen) { + case 0: + _statusline(StatusInfo); + break; + case 1: + _statusline(show_POST_url); + } + c = LYgetch_single(); + switch (c) { + case 'P': + /* + * Proceed with 301 or 307 redirect of POST + * with same method and POST content. - FM + */ + FREE(show_POST_url); + result = 1; + break; + + case 7: + case 'C': + /* + * Cancel request. + */ + FREE(show_POST_url); + result = 0; + break; + + case 'U': + /* + * Show URL for intermediate or advanced mode. + */ + if (user_mode != NOVICE_MODE) { + if (on_screen == 1) { + on_screen = 0; + } else { + on_screen = 1; + } + } + break; + + case 'G': + if (server_status == 301) { + /* + * Treat as 303 (GET without content). + */ + FREE(show_POST_url); + result = 303; + break; + } + /* FALLTHRU */ + + default: + /* + * Get another character. + */ + if (on_screen == 1) { + on_screen = 0; + } else { + on_screen = 2; + } + } + } + FREE(StatusInfo); + FREE(url); + return (result); +} + +#define okToSleep() (!crawl && !traversal && LYCursesON && !no_pause) + +/* + * Sleep for the given message class's time. + */ +void LYSleepAlert(void) +{ + if (okToSleep()) + LYSleep(AlertSecs); +} + +void LYSleepDelay(void) +{ + if (okToSleep()) + LYSleep(DelaySecs); +} + +void LYSleepInfo(void) +{ + if (okToSleep()) + LYSleep(InfoSecs); +} + +void LYSleepMsg(void) +{ + if (okToSleep()) + LYSleep(MessageSecs); +} + +#ifdef USE_CMD_LOGGING +void LYSleepReplay(void) +{ + if (okToSleep()) + LYSleep(ReplaySecs); +} +#endif /* USE_CMD_LOGGING */ + +/* + * LYstrerror emulates the ANSI strerror() function. + */ +#ifndef LYStrerror +char *LYStrerror(int code) +{ + static char temp[80]; + + sprintf(temp, "System errno is %d.\r\n", code); + return temp; +} +#endif /* HAVE_STRERROR */ diff --git a/src/HTAlert.h b/src/HTAlert.h new file mode 100644 index 0000000..03106f5 --- /dev/null +++ b/src/HTAlert.h @@ -0,0 +1,168 @@ +/* + * $LynxId: HTAlert.h,v 1.35 2016/11/24 23:44:49 tom Exp $ + * + * Displaying messages and getting input for WWW Library + * ===================================================== + * + * May 92 Created By C.T. Barker + * Feb 93 Portablized etc TBL + */ + +#ifndef HTALERT_H +#define HTALERT_H 1 + +#include + +#ifdef __cplusplus +extern "C" { +#endif +#define ALERT_PREFIX_LEN 5 +/* Display a message and get the input + * + * On entry, + * Msg is the message. + * + * On exit, + * Return value is malloc'd string which must be freed. + */ extern char *HTPrompt(const char *Msg, const char *deflt); + +/* Display a message, don't wait for input + * + * On entry, + * The input is a list of parameters for printf. + */ + extern void HTAlert(const char *Msg); + extern void HTAlwaysAlert(const char *extra_prefix, const char *Msg); + extern void HTInfoMsg(const char *Msg); + extern void HTInfoMsg2(const char *Msg, const char *Arg); + extern void HTUserMsg(const char *Msg); + extern void HTUserMsg2(const char *Msg, const char *Arg); + +/* Display a progress message for information (and diagnostics) only + * + * On entry, + * The input is a list of parameters for printf. + */ + extern const char *HTProgressUnits(int kilobytes); + extern void HTProgress(const char *Msg); + extern void HTReadProgress(off_t bytes, off_t total); + +#define _HTProgress(msg) mustshow = TRUE, HTProgress(msg) + +/* + * Indicates whether last HTConfirm was cancelled (^G or ^C) and + * resets flag. (so only call once!) - kw + */ + extern BOOL HTLastConfirmCancelled(void); + +/* + * Supports logic for forced yes/no prompt results. + */ + extern int HTForcedPrompt(int Opt, const char *Msg, int Dft); + +/* Display a message, then wait for 'yes' or 'no', allowing default + * response if a return or left-arrow is used. + * + * On entry, + * Takes a list of parameters for printf. + * + * On exit, + * If the user enters 'YES', returns TRUE, returns FALSE + * otherwise. + */ + extern int HTConfirmDefault(const char *Msg, int Dft); + +/* Display a message, then wait for 'yes' or 'no'. + * + * On entry, + * Takes a list of parameters for printf. + * + * On exit, + * If the user enters 'YES', returns TRUE, returns FALSE + * otherwise. + */ + extern BOOL HTConfirm(const char *Msg); + + extern BOOL confirm_post_resub(const char *address, + const char *title, + int if_imgmap, + int if_file); + +/* Prompt for password without echoing the reply + */ + extern char *HTPromptPassword(const char *Msg, const char *given); + +/* Prompt both username and password HTPromptUsernameAndPassword() + * --------------------------------- + * On entry, + * Msg is the prompting message. + * *username and + * *password are char pointers; they are changed + * to point to result strings. + * IsProxy should be TRUE if this is for + * proxy authentication. + * + * If *username is not NULL, it is taken + * to point to a default value. + * Initial value of *password is + * completely discarded. + * + * On exit, + * *username and *password point to newly allocated + * strings -- original strings pointed to by them + * are NOT freed. + * + */ + extern void HTPromptUsernameAndPassword(const char *Msg, + char **username, + char **password, + int IsProxy); + +/* Confirm a cookie operation. HTConfirmCookie() + * --------------------------- + * + * On entry, + * server is the server sending the Set-Cookie. + * domain is the domain of the cookie. + * path is the path of the cookie. + * name is the name of the cookie. + * value is the value of the cookie. + * + * On exit, + * Returns FALSE on cancel, + * TRUE if the cookie should be set. + */ + extern BOOL HTConfirmCookie(domain_entry * dp, const char *server, + const char *name, + const char *value); + +/* Confirm redirection of POST. HTConfirmPostRedirect() + * ---------------------------- + * On entry, + * Redirecting_url is the Location. + * server_status is the server status code. + * + * On exit, + * Returns 0 on cancel, + * 1 for redirect of POST with content, + * 303 for redirect as GET without content + */ + extern int HTConfirmPostRedirect(const char *Redirecting_url, + int server_status); + + extern void LYSleepAlert(void); + extern void LYSleepDelay(void); + extern void LYSleepInfo(void); + extern void LYSleepMsg(void); + extern void LYSleepReplay(void); + +#ifdef HAVE_STRERROR +#define LYStrerror strerror +#else + extern char *LYStrerror(int code); +#endif /* HAVE_STRERROR */ + +#ifdef __cplusplus +} +#endif +#endif /* HTALERT_H */ diff --git a/src/HTFWriter.c b/src/HTFWriter.c new file mode 100644 index 0000000..6e961e2 --- /dev/null +++ b/src/HTFWriter.c @@ -0,0 +1,1665 @@ +/* + * $LynxId: HTFWriter.c,v 1.129 2024/05/27 17:02:24 tom Exp $ + * + * FILE WRITER HTFWrite.h + * =========== + * + * This version of the stream object just writes to a C file. + * The file is assumed open and left open. + * + * Bugs: + * strings written must be less than buffer size. + */ + +#define HTSTREAM_INTERNAL 1 + +#include +#include +#include +#include + +#ifdef WIN_EX +#include +#endif + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* store statusline messages */ + +#ifdef USE_PERSISTENT_COOKIES +#include +#endif + +#ifdef USE_BROTLI +#include +#endif + +/* contains the name of the temp file which is being downloaded into */ +char *WWW_Download_File = NULL; +BOOLEAN LYCancelDownload = FALSE; /* exported to HTFormat.c in libWWW */ + +#ifdef VMS +static char *FIXED_RECORD_COMMAND = NULL; + +#ifdef USE_COMMAND_FILE /* Keep this as an option. - FM */ +#define FIXED_RECORD_COMMAND_MASK "@Lynx_Dir:FIXED512 %s" +#else +#define FIXED_RECORD_COMMAND_MASK "%s" +static unsigned long LYVMS_FixedLengthRecords(char *filename); +#endif /* USE_COMMAND_FILE */ +#endif /* VMS */ + +HTStream *HTSaveToFile(HTPresentation *pres, + HTParentAnchor *anchor, + HTStream *sink); + +/* Stream Object + * ------------- + */ +struct _HTStream { + const HTStreamClass *isa; + + FILE *fp; /* The file we've opened */ + char *end_command; /* What to do on _free. */ + char *remove_command; /* What to do on _abort. */ + char *viewer_command; /* Saved external viewer */ + HTFormat input_format; /* Original pres->rep */ + HTFormat output_format; /* Original pres->rep_out */ + HTParentAnchor *anchor; /* Original stream's anchor. */ + HTStream *sink; /* Original stream's sink. */ +#ifdef FNAMES_8_3 + BOOLEAN idash; /* remember position to become '.' */ +#endif +}; + +/*_________________________________________________________________________ + * + * A C T I O N R O U T I N E S + * Bug: + * Most errors are ignored. + */ + +/* Error handling + * ------------------ + */ +static void HTFWriter_error(HTStream *me, const char *id) +{ + char buf[200]; + + sprintf(buf, "%.60s: %.60s: %.60s", + id, + me->isa->name, + LYStrerror(errno)); + HTAlert(buf); +/* + * Only disaster results from: + * me->isa->_abort(me, NULL); + */ +} + +/* Character handling + * ------------------ + */ +static void HTFWriter_put_character(HTStream *me, int c) +{ + if (me->fp) { + putc(c, me->fp); + } +} + +/* String handling + * --------------- + */ +static void HTFWriter_put_string(HTStream *me, const char *s) +{ + if (me->fp) { + fputs(s, me->fp); + } +} + +/* Buffer write. Buffers can (and should!) be big. + * ------------ + */ +static void HTFWriter_write(HTStream *me, const char *s, int l) +{ + size_t result; + + if (me->fp) { + result = fwrite(s, (size_t) 1, (size_t) l, me->fp); + if (result != (size_t) l) { + HTFWriter_error(me, "HTFWriter_write"); + } + } +} + +static void decompress_gzip(HTStream *me) +{ + char *in_name = me->anchor->FileCache; + char copied[LY_MAXPATH]; + FILE *fp = LYOpenTemp(copied, ".tmp.gz", BIN_W); + + if (fp != 0) { +#ifdef USE_ZLIB + char buffer[BUFSIZ]; + gzFile gzfp; + int status; + + CTRACE((tfp, "decompressing '%s'\n", in_name)); + if ((gzfp = gzopen(in_name, BIN_R)) != 0) { + BOOL success = TRUE; + size_t actual = 0; + + CTRACE((tfp, "...opened '%s'\n", copied)); + while ((status = gzread(gzfp, buffer, sizeof(buffer))) > 0) { + size_t want = (size_t) status; + size_t have = fwrite(buffer, sizeof(char), want, fp); + + actual += have; + if (want != have) { + success = FALSE; + break; + } + } + gzclose(gzfp); + LYCloseTempFP(fp); + CTRACE((tfp, "...decompress %" PRI_off_t " to %lu\n", + CAST_off_t (me->anchor->actual_length), + (unsigned long)actual)); + if (success) { + if (LYRenameFile(copied, in_name) == 0) + me->anchor->actual_length = (off_t) actual; + (void) LYRemoveTemp(copied); + } + } +#else +#define FMT "%s %s" + const char *program; + + if ((program = HTGetProgramPath(ppUNCOMPRESS)) == NULL) { + HTAlert(ERROR_UNCOMPRESSING_TEMP); + } else if (LYCopyFile(in_name, copied) == 0) { + char expanded[LY_MAXPATH]; + char *command = NULL; + + HTAddParam(&command, FMT, 1, program); + HTAddParam(&command, FMT, 2, copied); + HTEndParam(&command, FMT, 2); + if (LYSystem(command) == 0) { + struct stat stat_buf; + + strcpy(expanded, copied); + *strrchr(expanded, '.') = '\0'; + if (LYRenameFile(expanded, in_name) != 0) { + CTRACE((tfp, "rename failed %s to %s\n", expanded, in_name)); + } else if (stat(in_name, &stat_buf) != 0) { + CTRACE((tfp, "stat failed for %s\n", in_name)); + } else { + me->anchor->actual_length = stat_buf.st_size; + } + } else { + CTRACE((tfp, "command failed: %s\n", command)); + } + free(command); + (void) LYRemoveTemp(copied); + } +#undef FMT +#endif + } +} + +static void decompress_br(HTStream *me) +{ +#undef THIS_FUNC +#define THIS_FUNC "decompress_br\n" + char *in_name = me->anchor->FileCache; + char copied[LY_MAXPATH]; + FILE *fp = LYOpenTemp(copied, ".br", BIN_W); + + if (fp != 0) { +#ifdef USE_BROTLI +#define INPUT_BUFFER_SIZE BUFSIZ + char *brotli_buffer = NULL; + char *normal_buffer = NULL; + size_t brotli_size; + size_t brotli_limit = 0; + size_t brotli_offset; + size_t normal_size; + size_t normal_limit = 0; + BrotliDecoderResult status2 = BROTLI_DECODER_RESULT_ERROR; + int status; + off_t bytes = 0; + FILE *ifp = fopen(in_name, BIN_R); + + if (ifp != NULL) { + CTRACE((tfp, "reading '%s'\n", in_name)); + for (;;) { + size_t input_chunk = INPUT_BUFFER_SIZE; + + brotli_offset = brotli_limit; + brotli_limit += input_chunk; + brotli_buffer = realloc(brotli_buffer, brotli_limit); + if (brotli_buffer == NULL) + outofmem(__FILE__, THIS_FUNC); + status = (int) fread(brotli_buffer + brotli_offset, + sizeof(char), + input_chunk, + ifp); + + if (status <= 0) { /* EOF or error */ + if (status == 0) { + break; + } + CTRACE((tfp, THIS_FUNC ": Read error, fread returns %d\n", status)); + break; + } + bytes += status; + } + fclose(ifp); + + CTRACE((tfp, "decompressing '%s'\n", in_name)); + /* + * next, unless we encountered an error (and have no data), try + * decompressing with increasing output buffer sizes until the brotli + * library succeeds. + */ + if (bytes > 0) { + do { + if (normal_limit == 0) + normal_limit = (10 * brotli_limit) + INPUT_BUFFER_SIZE; + else + normal_limit *= 2; + normal_buffer = realloc(normal_buffer, normal_limit); + if (normal_buffer == NULL) + outofmem(__FILE__, THIS_FUNC); + brotli_size = (size_t) bytes; + normal_size = normal_limit; + status2 = BrotliDecoderDecompress(brotli_size, + (uint8_t *) brotli_buffer, + &normal_size, + (uint8_t *) normal_buffer); + /* + * brotli library should return + * BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT, + * but actually returns + * BROTLI_DECODER_RESULT_ERROR + * + * Accommodate possible improvements... + */ + } while (status2 != BROTLI_DECODER_RESULT_SUCCESS); + } + + /* + * finally, pump that data into the output stream. + */ + if (status2 == BROTLI_DECODER_RESULT_SUCCESS) { + CTRACE((tfp, THIS_FUNC ": decompressed %ld -> %ld (1:%.1f)\n", + brotli_size, normal_size, + (double) normal_size / (double) brotli_size)); + fwrite(normal_buffer, sizeof(char), normal_size, fp); + } + free(brotli_buffer); + free(normal_buffer); + + LYCloseTempFP(fp); + + CTRACE((tfp, "...decompress %" PRI_off_t " to %lu\n", + CAST_off_t (me->anchor->actual_length), + (unsigned long)normal_size)); + if (status2 == BROTLI_DECODER_RESULT_SUCCESS) { + if (LYRenameFile(copied, in_name) == 0) + me->anchor->actual_length = (off_t) normal_size; + } + (void) LYRemoveTemp(copied); + } +#else +#define FMT "%s -j -d %s" + const char *program; + + if ((program = HTGetProgramPath(ppBROTLI)) == NULL) { + HTAlert(ERROR_UNCOMPRESSING_TEMP); + } else if (LYCopyFile(in_name, copied) == 0) { + char expanded[LY_MAXPATH]; + char *command = NULL; + + HTAddParam(&command, FMT, 1, program); + HTAddParam(&command, FMT, 2, copied); + HTEndParam(&command, FMT, 2); + if (LYSystem(command) == 0) { + struct stat stat_buf; + + strcpy(expanded, copied); + *strrchr(expanded, '.') = '\0'; + if (LYRenameFile(expanded, in_name) != 0) { + CTRACE((tfp, "rename failed %s to %s\n", expanded, in_name)); + } else if (stat(in_name, &stat_buf) != 0) { + CTRACE((tfp, "stat failed for %s\n", in_name)); + } else { + me->anchor->actual_length = stat_buf.st_size; + } + } else { + CTRACE((tfp, "command failed: %s\n", command)); + } + free(command); + (void) LYRemoveTemp(copied); + } +#undef FMT +#endif + } +} + +/* Free an HTML object + * ------------------- + * + * Note that the SGML parsing context is freed, but the created + * object is not, + * as it takes on an existence of its own unless explicitly freed. + */ +static void HTFWriter_free(HTStream *me) +{ + int len; + char *path = NULL; + char *addr = NULL; + BOOL use_zread = NO; + BOOLEAN found = FALSE; + +#ifdef WIN_EX + HANDLE cur_handle; + + cur_handle = GetForegroundWindow(); +#endif + + if (me->fp) + fflush(me->fp); + if (me->end_command) { /* Temp file */ + LYCloseTempFP(me->fp); + /* + * Handle a special case where the server used "Content-Type: gzip". + * Normally that feeds into the presentation stages, but if the link + * happens to point to something that will not be presented, but + * instead offered as a download, it comes here. In that case, ungzip + * the content before prompting the user for the place to store it. + */ + if (me->anchor->FileCache != NULL + && me->anchor->no_content_encoding == FALSE + && IsCompressionFormat(me->input_format, cftGzip) + && !strcmp(me->anchor->content_encoding, "gzip")) { + decompress_gzip(me); + } else if (me->anchor->FileCache != NULL + && me->anchor->no_content_encoding == FALSE + && IsCompressionFormat(me->input_format, cftBrotli) + && !strcmp(me->anchor->content_encoding, "br")) { + decompress_br(me); + } +#ifdef VMS + else if (0 == strcmp(me->end_command, "SaveVMSBinaryFile")) { + /* + * It's a binary file saved to disk on VMS, which + * we want to convert to fixed records format. - FM + */ +#ifdef USE_COMMAND_FILE + LYSystem(FIXED_RECORD_COMMAND); +#else + LYVMS_FixedLengthRecords(FIXED_RECORD_COMMAND); +#endif /* USE_COMMAND_FILE */ + FREE(FIXED_RECORD_COMMAND); + + if (me->remove_command) { + /* NEVER REMOVE THE FILE unless during an abort! */ + FREE(me->remove_command); + } + } else +#endif /* VMS */ + if (me->input_format == HTAtom_for("www/compressed")) { + /* + * It's a compressed file supposedly cached to + * a temporary file for uncompression. - FM + */ + if (me->anchor->FileCache != NULL) { + BOOL skip_loadfile = (BOOL) (me->viewer_command != NULL); + + /* + * Save the path with the "gz" or "Z" suffix trimmed, + * and remove any previous uncompressed copy. - FM + */ + StrAllocCopy(path, me->anchor->FileCache); + if ((len = (int) strlen(path)) > 3 && + (!strcasecomp(&path[len - 2], "gz") || + !strcasecomp(&path[len - 2], "zz"))) { +#ifdef USE_ZLIB + if (!skip_loadfile) { + use_zread = YES; + } else +#endif /* USE_ZLIB */ + { + path[len - 3] = '\0'; + (void) remove(path); + } + } else if (len > 4 && !strcasecomp(&path[len - 3], "bz2")) { +#ifdef USE_BZLIB + if (!skip_loadfile) { + use_zread = YES; + } else +#endif /* USE_BZLIB */ + { + path[len - 4] = '\0'; + (void) remove(path); + } + } else if (len > 3 && !strcasecomp(&path[len - 2], "br")) { +#ifdef USE_BROTLI + if (!skip_loadfile) { + use_zread = YES; + } else +#endif /* USE_BROTLI */ + { + path[len - 3] = '\0'; + (void) remove(path); + } + } else if (len > 2 && !strcasecomp(&path[len - 1], "Z")) { + path[len - 2] = '\0'; + (void) remove(path); + } + if (!use_zread) { + if (!dump_output_immediately) { + /* + * Tell user what's happening. - FM + */ + _HTProgress(me->end_command); + } + /* + * Uncompress it. - FM + */ + if (!isEmpty(me->end_command)) + LYSystem(me->end_command); + found = LYCanReadFile(me->anchor->FileCache); + } + if (found) { + /* + * It's still there with the "gz" or "Z" suffix, + * so the uncompression failed. - FM + */ + if (!dump_output_immediately) { + lynx_force_repaint(); + LYrefresh(); + } + HTAlert(ERROR_UNCOMPRESSING_TEMP); + (void) LYRemoveTemp(me->anchor->FileCache); + FREE(me->anchor->FileCache); + } else { + /* + * Succeeded! Create a complete address + * for the uncompressed file and invoke + * HTLoadFile() to handle it. - FM + */ +#ifdef FNAMES_8_3 + /* + * Assuming we have just uncompressed e.g. + * FILE-mpeg.gz -> FILE-mpeg, restore/shorten + * the name to be fit for passing to an external + * viewer, by renaming FILE-mpeg -> FILE.mpe - kw + */ + if (skip_loadfile) { + char *new_path = NULL; + char *the_dash = me->idash ? strrchr(path, '-') : 0; + + if (the_dash != 0) { + unsigned off = (unsigned) (the_dash - path); + + StrAllocCopy(new_path, path); + new_path[off] = '.'; + if (strlen(new_path + off) > 4) + new_path[off + 4] = '\0'; + if (LYRenameFile(path, new_path) == 0) { + FREE(path); + path = new_path; + } else { + FREE(new_path); + } + } + } +#endif /* FNAMES_8_3 */ + LYLocalFileToURL(&addr, path); + if (!use_zread) { + LYRenamedTemp(me->anchor->FileCache, path); + StrAllocCopy(me->anchor->FileCache, path); + StrAllocCopy(me->anchor->content_encoding, "binary"); + } + FREE(path); + if (!skip_loadfile) { + /* + * Lock the chartrans info we may possibly have, + * so HTCharsetFormat() will not apply the default + * for local files. - KW + */ + if (HTAnchor_getUCLYhndl(me->anchor, + UCT_STAGE_PARSER) < 0) { + /* + * If not yet set - KW + */ + HTAnchor_copyUCInfoStage(me->anchor, + UCT_STAGE_PARSER, + UCT_STAGE_MIME, + UCT_SETBY_DEFAULT + 1); + } + HTAnchor_copyUCInfoStage(me->anchor, + UCT_STAGE_PARSER, + UCT_STAGE_MIME, -1); + } + /* + * Create a complete address for + * the uncompressed file. - FM + */ + if (!dump_output_immediately) { + /* + * Tell user what's happening. - FM + * HTInfoMsg2(WWW_USING_MESSAGE, addr); + * but only in the history, not on screen -RS + */ + LYstore_message2(WWW_USING_MESSAGE, addr); + } + + if (skip_loadfile) { + /* + * It's a temporary file we're passing to a viewer or + * helper application. Loading the temp file through + * HTLoadFile() would result in yet another HTStream + * (created with HTSaveAndExecute()) which would just + * copy the temp file to another temp file (or even the + * same!). We can skip this needless duplication by + * using the viewer_command which has already been + * determined when the HTCompressed stream was created. + * - kw + */ + FREE(me->end_command); + + HTAddParam(&(me->end_command), me->viewer_command, 1, me->anchor->FileCache); + HTEndParam(&(me->end_command), me->viewer_command, 1); + + if (!dump_output_immediately) { + /* + * Tell user what's happening. - FM + */ + HTProgress(me->end_command); +#ifndef WIN_EX + stop_curses(); +#endif + } +#ifdef _WIN_CC + exec_command(me->end_command, FALSE); +#else + LYSystem(me->end_command); +#endif + if (me->remove_command) { + /* NEVER REMOVE THE FILE unless during an abort!!! */ + FREE(me->remove_command); + } + if (!dump_output_immediately) { +#ifdef WIN_EX + if (focus_window) { + HTInfoMsg(gettext("Set focus1")); + (void) SetForegroundWindow(cur_handle); + } +#else + start_curses(); +#endif + } + } else { + (void) HTLoadFile(addr, + me->anchor, + me->output_format, + me->sink); + } + if (dump_output_immediately && + me->output_format == WWW_PRESENT) { + FREE(addr); + (void) remove(me->anchor->FileCache); + FREE(me->anchor->FileCache); + FREE(me->remove_command); + FREE(me->end_command); + FREE(me->viewer_command); + FREE(me); + return; + } + } + FREE(addr); + } + if (me->remove_command) { + /* NEVER REMOVE THE FILE unless during an abort!!! */ + FREE(me->remove_command); + } + } else if (strcmp(me->end_command, "SaveToFile")) { + /* + * It's a temporary file we're passing to a viewer or helper + * application. - FM + */ + if (!dump_output_immediately) { + /* + * Tell user what's happening. - FM + */ + _HTProgress(me->end_command); +#ifndef WIN_EX + stop_curses(); +#endif + } +#ifdef _WIN_CC + exec_command(me->end_command, wait_viewer_termination); +#else + LYSystem(me->end_command); +#endif + + if (me->remove_command) { + /* NEVER REMOVE THE FILE unless during an abort!!! */ + FREE(me->remove_command); + } + if (!dump_output_immediately) { +#ifdef WIN_EX + if (focus_window) { + HTInfoMsg(gettext("Set focus2")); + (void) SetForegroundWindow(cur_handle); + } +#else + start_curses(); +#endif + } + } else { + /* + * It's a file we saved to disk for handling via a menu. - FM + */ + if (me->remove_command) { + /* NEVER REMOVE THE FILE unless during an abort!!! */ + FREE(me->remove_command); + } + if (!dump_output_immediately) { +#ifdef WIN_EX + if (focus_window) { + HTInfoMsg(gettext("Set focus3")); + (void) SetForegroundWindow(cur_handle); + } +#else + start_curses(); +#endif + } + } + FREE(me->end_command); + } + FREE(me->viewer_command); + + if (dump_output_immediately) { + if (me->anchor->FileCache) + (void) remove(me->anchor->FileCache); + FREE(me); +#ifdef USE_PERSISTENT_COOKIES + /* + * We want to save cookies picked up when in source mode. ... + */ + if (persistent_cookies) + LYStoreCookies(LYCookieSaveFile); +#endif /* USE_PERSISTENT_COOKIES */ + exit_immediately(EXIT_SUCCESS); + } + + FREE(me); + return; +} + +#ifdef VMS +# define REMOVE_COMMAND "delete/noconfirm/nolog %s;" +#else +# define REMOVE_COMMAND "%s" +#endif /* VMS */ + +/* Abort writing + * ------------- + */ +static void HTFWriter_abort(HTStream *me, HTError e GCC_UNUSED) +{ + CTRACE((tfp, "HTFWriter_abort called\n")); + LYCloseTempFP(me->fp); + FREE(me->viewer_command); + if (me->end_command) { /* Temp file */ + CTRACE((tfp, "HTFWriter: Aborting: file not executed or saved.\n")); + FREE(me->end_command); + if (me->remove_command) { +#ifdef VMS + LYSystem(me->remove_command); +#else + (void) chmod(me->remove_command, 0600); /* Ignore errors */ + if (0 != unlink(me->remove_command)) { + char buf[560]; + + sprintf(buf, "%.60s '%.400s': %.60s", + gettext("Error deleting file"), + me->remove_command, LYStrerror(errno)); + HTAlert(buf); + } +#endif + FREE(me->remove_command); + } + } + + FREE(WWW_Download_File); + + FREE(me); +} + +/* Structured Object Class + * ----------------------- + */ +static const HTStreamClass HTFWriter = /* As opposed to print etc */ +{ + "FileWriter", + HTFWriter_free, + HTFWriter_abort, + HTFWriter_put_character, + HTFWriter_put_string, + HTFWriter_write +}; + +/* Subclass-specific Methods + * ------------------------- + */ +HTStream *HTFWriter_new(FILE *fp) +{ + HTStream *me; + + if (!fp) + return NULL; + + me = typecalloc(HTStream); + if (me == NULL) + outofmem(__FILE__, "HTFWriter_new"); + + me->isa = &HTFWriter; + + me->fp = fp; + me->end_command = NULL; + me->remove_command = NULL; + me->anchor = NULL; + me->sink = NULL; + + return me; +} + +/* Make system command from template + * --------------------------------- + * + * See mailcap spec for description of template. + */ +static char *mailcap_substitute(HTParentAnchor *anchor, + HTPresentation *pres, + char *fnam) +{ + char *result = LYMakeMailcapCommand(pres->command, + anchor->content_type_params, + fnam); + +#if defined(UNIX) + /* if we don't have a "%s" token, expect to provide the file via stdin */ + if (!LYMailcapUsesPctS(pres->command)) { + char *prepend = 0; + const char *format = "( %s ) < %s"; + + HTSprintf(&prepend, "( %s", result); /* ...avoid quoting */ + HTAddParam(&prepend, format, 2, fnam); /* ...to quote if needed */ + FREE(result); + result = prepend; + } +#endif + return result; +} + +/* Take action using a system command + * ---------------------------------- + * + * originally from Ghostview handling by Marc Andreseen. + * Creates temporary file, writes to it, executes system command + * on end-document. The suffix of the temp file can be given + * in case the application is fussy, or so that a generic opener can + * be used. + */ +HTStream *HTSaveAndExecute(HTPresentation *pres, + HTParentAnchor *anchor, + HTStream *sink) +{ + char fnam[LY_MAXPATH]; + const char *suffix; + HTStream *me; + + if (traversal) { + LYCancelledFetch = TRUE; + return (NULL); + } +#if defined(EXEC_LINKS) || defined(EXEC_SCRIPTS) + if (pres->quality >= 999.0) { /* exec link */ + if (dump_output_immediately) { + LYCancelledFetch = TRUE; + return (NULL); + } + if (no_exec) { + HTAlert(EXECUTION_DISABLED); + return HTPlainPresent(pres, anchor, sink); + } + if (!local_exec) { + if (local_exec_on_local_files && + (LYJumpFileURL || + !StrNCmp(anchor->address, "file://localhost", 16))) { + /* allow it to continue */ + ; + } else { + char *buf = 0; + + HTSprintf0(&buf, EXECUTION_DISABLED_FOR_FILE, + key_for_func(LYK_OPTIONS)); + HTAlert(buf); + FREE(buf); + return HTPlainPresent(pres, anchor, sink); + } + } + } +#endif /* EXEC_LINKS || EXEC_SCRIPTS */ + + if (dump_output_immediately) { + return (HTSaveToFile(pres, anchor, sink)); + } + + me = typecalloc(HTStream); + if (me == NULL) + outofmem(__FILE__, "HTSaveAndExecute"); + + me->isa = &HTFWriter; + me->input_format = pres->rep; + me->output_format = pres->rep_out; + me->anchor = anchor; + me->sink = sink; + + if (LYCachedTemp(fnam, &(anchor->FileCache))) { + /* This used to be LYNewBinFile(fnam); changed to a different call so + * that the open fp gets registered in the list keeping track of temp + * files, equivalent to when LYOpenTemp() gets called below. This + * avoids a file descriptor leak caused by LYCloseTempFP() not being + * able to find the fp. The binary suffix is expected to not be used, + * it's only for fallback in unusual error cases. - kw + */ + me->fp = LYOpenTempRewrite(fnam, BIN_SUFFIX, BIN_W); + } else { +#if defined(WIN_EX) && !defined(__CYGWIN__) /* 1998/01/04 (Sun) */ + if (!StrNCmp(anchor->address, "file://localhost", 16)) { + + /* 1998/01/23 (Fri) 17:38:26 */ + char *cp, *view_fname; + + me->fp = NULL; + + view_fname = fnam + 3; + LYStrNCpy(view_fname, anchor->address + 17, sizeof(fnam) - 5); + HTUnEscape(view_fname); + + if (StrChr(view_fname, ':') == NULL) { + fnam[0] = windows_drive[0]; + fnam[1] = windows_drive[1]; + fnam[2] = '/'; + view_fname = fnam; + } + + /* 1998/04/21 (Tue) 11:04:16 */ + cp = view_fname; + while (*cp) { + if (IS_SJIS_HI1(UCH(*cp)) || IS_SJIS_HI2(UCH(*cp))) { + cp += 2; + continue; + } else if (*cp == '/') { + *cp = '\\'; + } + cp++; + } + if (StrChr(view_fname, ' ')) + view_fname = quote_pathname(view_fname); + + StrAllocCopy(me->viewer_command, pres->command); + + me->end_command = mailcap_substitute(anchor, pres, view_fname); + me->remove_command = NULL; + + return me; + } +#endif + /* + * Check for a suffix. + * Save the file under a suitably suffixed name. + */ + if (!strcasecomp(pres->rep->name, STR_HTML)) { + suffix = HTML_SUFFIX; + } else if (!strncasecomp(pres->rep->name, "text/", 5)) { + suffix = TEXT_SUFFIX; + } else if ((suffix = HTFileSuffix(pres->rep, + anchor->content_encoding)) == 0 + || *suffix != '.') { + if (!strncasecomp(pres->rep->name, "application/", 12)) { + suffix = BIN_SUFFIX; + } else { + suffix = HTML_SUFFIX; + } + } + me->fp = LYOpenTemp(fnam, suffix, BIN_W); + } + + if (!me->fp) { + HTAlert(CANNOT_OPEN_TEMP); + FREE(me); + return NULL; + } + + StrAllocCopy(me->viewer_command, pres->command); + /* + * Make command to process file. + */ + me->end_command = mailcap_substitute(anchor, pres, fnam); + + /* + * Make command to delete file. + */ + me->remove_command = NULL; + HTAddParam(&(me->remove_command), REMOVE_COMMAND, 1, fnam); + HTEndParam(&(me->remove_command), REMOVE_COMMAND, 1); + + StrAllocCopy(anchor->FileCache, fnam); + return me; +} + +/* Format Converter using system command + * ------------------------------------- + */ + +/* @@@@@@@@@@@@@@@@@@@@@@ */ + +/* Save to a local file LJM!!! + * -------------------- + * + * usually a binary file that can't be displayed + * + * originally from Ghostview handling by Marc Andreseen. + * Asks the user if he wants to continue, creates a temporary + * file, and writes to it. In HTSaveToFile_Free + * the user will see a list of choices for download + */ +HTStream *HTSaveToFile(HTPresentation *pres, + HTParentAnchor *anchor, + HTStream *sink) +{ + HTStream *ret_obj; + char fnam[LY_MAXPATH]; + const char *suffix; + char *cp; + int c = 0; + +#ifdef VMS + BOOL IsBinary = TRUE; +#endif + + ret_obj = typecalloc(HTStream); + + if (ret_obj == NULL) + outofmem(__FILE__, "HTSaveToFile"); + + ret_obj->isa = &HTFWriter; + ret_obj->remove_command = NULL; + ret_obj->end_command = NULL; + ret_obj->input_format = pres->rep; + ret_obj->output_format = pres->rep_out; + ret_obj->anchor = anchor; + ret_obj->sink = sink; + + if (dump_output_immediately) { + ret_obj->fp = stdout; /* stdout */ + if (HTOutputFormat == WWW_DOWNLOAD) + goto Prepend_BASE; + return ret_obj; + } + + LYCancelDownload = FALSE; + if (HTOutputFormat != WWW_DOWNLOAD) { + if (traversal || + (no_download && !override_no_download && no_disk_save)) { + if (!traversal) { + HTAlert(CANNOT_DISPLAY_FILE); + } + LYCancelDownload = TRUE; + if (traversal) + LYCancelledFetch = TRUE; + FREE(ret_obj); + return (NULL); + } + + if (((cp = StrChr(pres->rep->name, ';')) != NULL) && + strstr((cp + 1), "charset") != NULL) { + _user_message(MSG_DOWNLOAD_OR_CANCEL, pres->rep->name); + } else if (*(pres->rep->name) != '\0') { + _user_message(MSG_DOWNLOAD_OR_CANCEL, pres->rep->name); + } else { + _statusline(CANNOT_DISPLAY_FILE_D_OR_C); + } + + while (c != 'D' && c != 'C' && !LYCharIsINTERRUPT(c)) { + c = LYgetch_single(); +#ifdef VMS + /* + * 'C'ancel on Control-C or Control-Y and + * a 'N'o to the "really exit" query. - FM + */ + if (HadVMSInterrupt) { + HadVMSInterrupt = FALSE; + c = 'C'; + } +#endif /* VMS */ + } + + /* + * Cancel on 'C', 'c' or Control-G or Control-C. + */ + if (c == 'C' || LYCharIsINTERRUPT(c)) { + _statusline(CANCELLING_FILE); + LYCancelDownload = TRUE; + FREE(ret_obj); + return (NULL); + } + } + + /* + * Set up a 'D'ownload. + */ + if (LYCachedTemp(fnam, &(anchor->FileCache))) { + /* This used to be LYNewBinFile(fnam); changed to a different call so + * that the open fp gets registered in the list keeping track of temp + * files, equivalent to when LYOpenTemp() gets called below. This + * avoids a file descriptor leak caused by LYCloseTempFP() not being + * able to find the fp. The binary suffix is expected to not be used, + * it's only for fallback in unusual error cases. - kw + */ + ret_obj->fp = LYOpenTempRewrite(fnam, BIN_SUFFIX, BIN_W); + } else { + /* + * Check for a suffix. + * Save the file under a suitably suffixed name. + */ + if (!strcasecomp(pres->rep->name, STR_HTML)) { + suffix = HTML_SUFFIX; + } else if (!strncasecomp(pres->rep->name, "text/", 5)) { + suffix = TEXT_SUFFIX; + } else if (!strncasecomp(pres->rep->name, "application/", 12)) { + suffix = BIN_SUFFIX; + } else if ((suffix = HTFileSuffix(pres->rep, + anchor->content_encoding)) == 0 + || *suffix != '.') { + suffix = HTML_SUFFIX; + } + ret_obj->fp = LYOpenTemp(fnam, suffix, BIN_W); + } + + if (!ret_obj->fp) { + HTAlert(CANNOT_OPEN_OUTPUT); + FREE(ret_obj); + return NULL; + } + + if (0 == strncasecomp(pres->rep->name, "text/", 5) || + 0 == strcasecomp(pres->rep->name, "application/postscript") || + 0 == strcasecomp(pres->rep->name, "application/x-RUNOFF-MANUAL")) + /* + * It's a text file requested via 'd'ownload. Keep adding others to + * the above list, 'til we add a configurable procedure. - FM + */ +#ifdef VMS + IsBinary = FALSE; +#endif + + /* + * Any "application/foo" or other non-"text/foo" types that are actually + * text but not checked, above, will be treated as binary, so show the type + * to help sort that out later. Unix folks don't need to know this, but + * we'll show it to them, too. - FM + */ + HTInfoMsg2(CONTENT_TYPE_MSG, pres->rep->name); + + StrAllocCopy(WWW_Download_File, fnam); + + /* + * Make command to delete file. + */ + ret_obj->remove_command = NULL; + HTAddParam(&(ret_obj->remove_command), REMOVE_COMMAND, 1, fnam); + HTEndParam(&(ret_obj->remove_command), REMOVE_COMMAND, 1); + +#ifdef VMS + if (IsBinary && UseFixedRecords) { + StrAllocCopy(ret_obj->end_command, "SaveVMSBinaryFile"); + FIXED_RECORD_COMMAND = 0; + HTAddParam(&FIXED_RECORD_COMMAND, FIXED_RECORD_COMMAND_MASK, 1, fnam); + HTEndParam(&FIXED_RECORD_COMMAND, FIXED_RECORD_COMMAND_MASK, 1); + + } else { +#endif /* VMS */ + StrAllocCopy(ret_obj->end_command, "SaveToFile"); +#ifdef VMS + } +#endif /* VMS */ + + _statusline(RETRIEVING_FILE); + + StrAllocCopy(anchor->FileCache, fnam); + Prepend_BASE: + if (LYPrependBaseToSource && + !strncasecomp(pres->rep->name, STR_HTML, 9) && + !anchor->content_encoding) { + /* + * Add the document's base as a BASE tag at the top of the file, so + * that any partial or relative URLs within it will be resolved + * relative to that if no BASE tag is present and replaces it. Note + * that the markup will be technically invalid if a DOCTYPE + * declaration, or HTML or HEAD tags, are present, and thus the file + * may need editing for perfection. - FM + * + * Add timestamp (last reload). + */ + char *temp = NULL; + + if (non_empty(anchor->content_base)) { + StrAllocCopy(temp, anchor->content_base); + } else if (non_empty(anchor->content_location)) { + StrAllocCopy(temp, anchor->content_location); + } + if (temp) { + LYRemoveBlanks(temp); + if (!is_url(temp)) { + FREE(temp); + } + } + + fprintf(ret_obj->fp, + "\n", anchor->address); + if (non_empty(anchor->date)) { + fprintf(ret_obj->fp, + "\n", anchor->date); + if (non_empty(anchor->last_modified) + && strcmp(anchor->last_modified, anchor->date) + && strcmp(anchor->last_modified, + "Thu, 01 Jan 1970 00:00:01 GMT")) { + fprintf(ret_obj->fp, + "\n", anchor->last_modified); + } + } + fprintf(ret_obj->fp, + "\n\n", (temp ? temp : anchor->address)); + FREE(temp); + } + if (LYPrependCharsetToSource && + !strncasecomp(pres->rep->name, STR_HTML, 9) && + !anchor->content_encoding) { + /* + * Add the document's charset as a META CHARSET tag at the top of the + * file, so HTTP charset header will not be forgotten when a document + * saved as local file. We add this line only(!) if HTTP charset + * present. - LP Note that the markup will be technically invalid if a + * DOCTYPE declaration, or HTML or HEAD tags, are present, and thus the + * file may need editing for perfection. - FM + */ + char *temp = NULL; + + if (non_empty(anchor->charset)) { + StrAllocCopy(temp, anchor->charset); + LYRemoveBlanks(temp); + fprintf(ret_obj->fp, + "\n\n", + temp); + } + FREE(temp); + } + return ret_obj; +} + +/* Set up stream for uncompressing - FM + * ------------------------------- + */ +HTStream *HTCompressed(HTPresentation *pres, + HTParentAnchor *anchor, + HTStream *sink) +{ + HTStream *me; + HTFormat format; + char *type = NULL; + HTPresentation *Pres = NULL; + HTPresentation *Pnow = NULL; + int n, i; + BOOL can_present = FALSE; + char fnam[LY_MAXPATH]; + char temp[LY_MAXPATH]; /* actually stores just a suffix */ + const char *suffix; + char *uncompress_mask = NULL; + const char *compress_suffix = ""; + const char *middle; + + /* + * Deal with any inappropriate invocations of this function, or a download + * request, in which case we won't bother to uncompress the file. - FM + */ + if (!(anchor->content_encoding && anchor->content_type)) { + /* + * We have no idea what we're dealing with, so treat it as a binary + * stream. - FM + */ + format = HTAtom_for(STR_BINARY); + me = HTStreamStack(format, pres->rep_out, sink, anchor); + return me; + } + n = HTList_count(HTPresentations); + for (i = 0; i < n; i++) { + Pnow = (HTPresentation *) HTList_objectAt(HTPresentations, i); + if (!strcasecomp(Pnow->rep->name, anchor->content_type) && + Pnow->rep_out == WWW_PRESENT) { + const char *program; + + /* + * Pick the best presentation. User-defined mappings are at the + * end of the list, and unless the quality is lower, we prefer + * those. + */ + if (Pres == 0) + Pres = Pnow; + else if (Pres->quality > Pnow->quality) + continue; + else + Pres = Pnow; + /* + * We have a presentation mapping for it. - FM + */ + can_present = TRUE; + switch (HTEncodingToCompressType(anchor->content_encoding)) { + case cftGzip: + if ((program = HTGetProgramPath(ppGZIP)) != NULL) { + /* + * It's compressed with the modern gzip. - FM + */ + StrAllocCopy(uncompress_mask, program); + StrAllocCat(uncompress_mask, " -d --no-name %s"); + compress_suffix = "gz"; + } + break; + case cftDeflate: + if ((program = HTGetProgramPath(ppINFLATE)) != NULL) { + /* + * It's compressed with a zlib wrapper. + */ + StrAllocCopy(uncompress_mask, program); + StrAllocCat(uncompress_mask, " %s"); + compress_suffix = "zz"; + } + break; + case cftBzip2: + if ((program = HTGetProgramPath(ppBZIP2)) != NULL) { + StrAllocCopy(uncompress_mask, program); + StrAllocCat(uncompress_mask, " -d %s"); + compress_suffix = "bz2"; + } + break; + case cftBrotli: + if ((program = HTGetProgramPath(ppBROTLI)) != NULL) { + StrAllocCopy(uncompress_mask, program); + StrAllocCat(uncompress_mask, " -j -d %s"); + compress_suffix = "br"; + } + break; + case cftCompress: + if ((program = HTGetProgramPath(ppUNCOMPRESS)) != NULL) { + /* + * It's compressed the old fashioned Unix way. - FM + */ + StrAllocCopy(uncompress_mask, program); + StrAllocCat(uncompress_mask, " %s"); + compress_suffix = "Z"; + } + break; + case cftNone: + break; + } + } + } + if (can_present == FALSE || /* no presentation mapping */ + uncompress_mask == NULL || /* not gzip or compress */ + StrChr(anchor->content_type, ';') || /* wrong charset */ + HTOutputFormat == WWW_DOWNLOAD || /* download */ + !strcasecomp(pres->rep_out->name, STR_DOWNLOAD) || /* download */ + (traversal && /* only handle html or plain text for traversals */ + strcasecomp(anchor->content_type, STR_HTML) && + strcasecomp(anchor->content_type, STR_PLAINTEXT))) { + /* + * Cast the Content-Encoding to a Content-Type and pass it back to be + * handled as that type. - FM + */ + if (StrChr(anchor->content_encoding, '/') == NULL) { + /* + * Use "x-" prefix, none of the types we are likely to construct + * here are official. That is we generate "application/x-gzip" and + * so on. - kw + */ + if (!strncasecomp(anchor->content_encoding, "x-", 2)) + StrAllocCopy(type, "application/"); + else + StrAllocCopy(type, "application/x-"); + StrAllocCat(type, anchor->content_encoding); + } else { + StrAllocCopy(type, anchor->content_encoding); + } + format = HTAtom_for(type); + FREE(type); + FREE(uncompress_mask); + me = HTStreamStack(format, pres->rep_out, sink, anchor); + return me; + } + + /* + * Set up the stream structure for uncompressing and then handling based on + * the uncompressed Content-Type.- FM + */ + me = typecalloc(HTStream); + if (me == NULL) + outofmem(__FILE__, "HTCompressed"); + + me->isa = &HTFWriter; + me->input_format = pres->rep; + me->output_format = pres->rep_out; + me->anchor = anchor; + me->sink = sink; +#ifdef FNAMES_8_3 + me->idash = FALSE; +#endif + + /* + * Remove any old versions of the file. - FM + */ + if (anchor->FileCache) { + (void) LYRemoveTemp(anchor->FileCache); + FREE(anchor->FileCache); + } + + /* + * Get a new temporary filename and substitute a suitable suffix. - FM + */ + middle = NULL; + if (!strcasecomp(anchor->content_type, STR_HTML)) { + middle = HTML_SUFFIX; + middle++; /* point to 'h' of .htm(l) - kw */ + } else if (!strncasecomp(anchor->content_type, "text/", 5)) { + middle = &TEXT_SUFFIX[1]; + } else if (!strncasecomp(anchor->content_type, "application/", 12)) { + /* FIXME: why is this BEFORE HTFileSuffix? */ + middle = &BIN_SUFFIX[1]; + } else if ((suffix = + HTFileSuffix(HTAtom_for(anchor->content_type), NULL)) && + *suffix == '.') { +#if defined(VMS) || defined(FNAMES_8_3) + if (StrChr(suffix + 1, '.') == NULL) +#endif + middle = suffix + 1; + } + + temp[0] = 0; /* construct the suffix */ + if (middle) { +#ifdef FNAMES_8_3 + me->idash = TRUE; /* remember position of '-' - kw */ + strcat(temp, "-"); /* NAME-htm, NAME-txt, etc. - hack for DOS */ +#else + strcat(temp, "."); /* NAME.html, NAME-txt etc. */ +#endif /* FNAMES_8_3 */ + strcat(temp, middle); +#ifdef VMS + strcat(temp, "-"); /* NAME.html-gz, NAME.txt-gz, NAME.txt-Z etc. */ +#else + strcat(temp, "."); /* NAME-htm.gz (DOS), NAME.html.gz (UNIX)etc. */ +#endif /* VMS */ + } + strcat(temp, compress_suffix); + + /* + * Open the file for receiving the compressed input stream. - FM + */ + me->fp = LYOpenTemp(fnam, temp, BIN_W); + if (!me->fp) { + HTAlert(CANNOT_OPEN_TEMP); + FREE(uncompress_mask); + FREE(me); + return NULL; + } + + /* + * me->viewer_command will be NULL if the converter Pres found above is not + * for an external viewer but an internal HTStream converter. We also + * don't set it under conditions where HTSaveAndExecute would disallow + * execution of the command. - KW + */ + if (!dump_output_immediately && !traversal +#if defined(EXEC_LINKS) || defined(EXEC_SCRIPTS) + && (Pres->quality < 999.0 || + (!no_exec && /* allowed exec link or script ? */ + (local_exec || + (local_exec_on_local_files && + (LYJumpFileURL || + !StrNCmp(anchor->address, "file://localhost", 16)))))) +#endif /* EXEC_LINKS || EXEC_SCRIPTS */ + ) { + StrAllocCopy(me->viewer_command, Pres->command); + } + + /* + * Make command to process file. - FM + */ +#ifdef USE_BROTLI + if (compress_suffix[0] == 'b' /* e.g., ".br" */ + && compress_suffix[1] == 'r' + && !me->viewer_command) { + /* + * We won't call brotli externally, so we don't need to supply a + * command for it. + */ + StrAllocCopy(me->end_command, ""); + } else +#endif +#ifdef USE_BZLIB + if (compress_suffix[0] == 'b' /* must be bzip2 */ + && compress_suffix[1] == 'z' + && !me->viewer_command) { + /* + * We won't call bzip2 externally, so we don't need to supply a command + * for it. + */ + StrAllocCopy(me->end_command, ""); + } else +#endif +#ifdef USE_ZLIB + /* FIXME: allow deflate here, e.g., 'z' */ + if (compress_suffix[0] == 'g' /* must be gzip */ + && !me->viewer_command) { + /* + * We won't call gzip or compress externally, so we don't need to + * supply a command for it. + */ + StrAllocCopy(me->end_command, ""); + } else +#endif /* USE_ZLIB */ + { + me->end_command = NULL; + HTAddParam(&(me->end_command), uncompress_mask, 1, fnam); + HTEndParam(&(me->end_command), uncompress_mask, 1); + } + FREE(uncompress_mask); + + /* + * Make command to delete file. - FM + */ + me->remove_command = NULL; + HTAddParam(&(me->remove_command), REMOVE_COMMAND, 1, fnam); + HTEndParam(&(me->remove_command), REMOVE_COMMAND, 1); + + /* + * Save the filename and return the structure. - FM + */ + StrAllocCopy(anchor->FileCache, fnam); + return me; +} + +/* Dump output to stdout - LJM & FM + * --------------------- + * + */ +HTStream *HTDumpToStdout(HTPresentation *pres GCC_UNUSED, + HTParentAnchor *anchor, + HTStream *sink GCC_UNUSED) +{ + HTStream *ret_obj; + + ret_obj = typecalloc(HTStream); + + if (ret_obj == NULL) + outofmem(__FILE__, "HTDumpToStdout"); + + ret_obj->isa = &HTFWriter; + ret_obj->remove_command = NULL; + ret_obj->end_command = NULL; + ret_obj->anchor = anchor; + + ret_obj->fp = stdout; /* stdout */ + return ret_obj; +} + +#if defined(VMS) && !defined(USE_COMMAND_FILE) +#include +#include /* RMS status codes */ +#include /* I/O function codes */ +#include /* file information block defs */ +#include /* attribute request codes */ +#ifdef NOTDEFINED /*** Not all versions of VMS compilers have these. ***/ +#include /* file characteristics */ +#include /* file attribute defs */ +#else /*** So we'll define what we need from them ourselves. ***/ +#define FCH$V_CONTIGB 0x005 /* pos of cont best try bit */ +#define FCH$M_CONTIGB (1 << FCH$V_CONTIGB) /* contig best try bit mask */ +/* VMS I/O User's Reference Manual: Part I (V5.x doc set) */ +struct fatdef { + unsigned char fat$b_rtype, fat$b_rattrib; + unsigned short fat$w_rsize; + unsigned long fat$l_hiblk, fat$l_efblk; + unsigned short fat$w_ffbyte; + unsigned char fat$b_bktsize, fat$b_vfcsize; + unsigned short fat$w_maxrec, fat$w_defext, fat$w_gbc; + unsigned:16,:32,:16; /* 6 bytes reserved, 2 bytes not used */ + unsigned short fat$w_versions; +}; +#endif /* NOTDEFINED */ + +/* arbitrary descriptor without type and class info */ +typedef struct dsc { + unsigned short len, mbz; + void *adr; +} Desc; + +extern unsigned long sys$open(), sys$qiow(), sys$dassgn(); + +#define syswork(sts) ((sts) & 1) +#define sysfail(sts) (!syswork(sts)) + +/* + * 25-Jul-1995 - Pat Rankin (rankin@eql.caltech.edu) + * + * Force a file to be marked as having fixed-length, 512 byte records + * without implied carriage control, and with best_try_contiguous set. + */ +static unsigned long LYVMS_FixedLengthRecords(char *filename) +{ + struct FAB fab; /* RMS file access block */ + struct fibdef fib; /* XQP file information block */ + struct fatdef recattr; /* XQP file "record" attributes */ + struct atrdef attr_rqst_list[3]; /* XQP attribute request itemlist */ + + Desc fib_dsc; + unsigned short channel, iosb[4]; + unsigned long fchars, sts, tmp; + + /* initialize file access block */ + fab = cc$rms_fab; + fab.fab$l_fna = filename; + fab.fab$b_fns = (unsigned char) strlen(filename); + fab.fab$l_fop = FAB$M_UFO; /* user file open; no further RMS processing */ + fab.fab$b_fac = FAB$M_PUT; /* need write access */ + fab.fab$b_shr = FAB$M_NIL; /* exclusive access */ + + sts = sys$open(&fab); /* channel in stv; $dassgn to close */ + if (sts == RMS$_FLK) { + /* For MultiNet, at least, if the file was just written by a remote + NFS client, the local NFS server might still have it open, and the + failed access attempt will provoke it to be closed, so try again. */ + sts = sys$open(&fab); + } + if (sysfail(sts)) + return sts; + + /* RMS supplies a user-mode channel (see FAB$L_FOP FAB$V_UFO doc) */ + channel = (unsigned short) fab.fab$l_stv; + + /* set up ACP interface structures */ + /* file information block, passed by descriptor; it's okay to start with + an empty FIB after RMS has accessed the file for us */ + fib_dsc.len = sizeof fib; + fib_dsc.mbz = 0; + fib_dsc.adr = &fib; + memset((void *) &fib, 0, sizeof fib); + /* attribute request list */ + attr_rqst_list[0].atr$w_size = sizeof recattr; + attr_rqst_list[0].atr$w_type = ATR$C_RECATTR; + *(void **) &attr_rqst_list[0].atr$l_addr = (void *) &recattr; + attr_rqst_list[1].atr$w_size = sizeof fchars; + attr_rqst_list[1].atr$w_type = ATR$C_UCHAR; + *(void **) &attr_rqst_list[1].atr$l_addr = (void *) &fchars; + attr_rqst_list[2].atr$w_size = attr_rqst_list[2].atr$w_type = 0; + attr_rqst_list[2].atr$l_addr = 0; + /* file "record" attributes */ + memset((void *) &recattr, 0, sizeof recattr); + fchars = 0; /* file characteristics */ + + /* get current attributes */ + sts = sys$qiow(0, channel, IO$_ACCESS, iosb, (void (*)()) 0, 0, + &fib_dsc, 0, 0, 0, attr_rqst_list, 0); + if (syswork(sts)) + sts = iosb[0]; + + /* set desired attributes */ + if (syswork(sts)) { + recattr.fat$b_rtype = FAB$C_SEQ | FAB$C_FIX; /* org=seq, rfm=fix */ + recattr.fat$w_rsize = recattr.fat$w_maxrec = 512; /* lrl=mrs=512 */ + recattr.fat$b_rattrib = 0; /* rat=none */ + fchars |= FCH$M_CONTIGB; /* contiguous-best-try */ + sts = sys$qiow(0, channel, IO$_DEACCESS, iosb, (void (*)()) 0, 0, + &fib_dsc, 0, 0, 0, attr_rqst_list, 0); + if (syswork(sts)) + sts = iosb[0]; + } + + /* all done */ + tmp = sys$dassgn(channel); + if (syswork(sts)) + sts = tmp; + return sts; +} +#endif /* VMS && !USE_COMMAND_FILE */ diff --git a/src/HTFont.h b/src/HTFont.h new file mode 100644 index 0000000..90c2b9e --- /dev/null +++ b/src/HTFont.h @@ -0,0 +1,50 @@ +/* The portable font concept (!?*) +*/ + +/* Line mode browser version: +*/ +#ifndef HTFONT_H +#define HTFONT_H + +typedef long int HTMLFont; /* For now */ + +#define HT_FONT 0 +#define HT_CAPITALS 1 +#define HT_BOLD 2 +#define HT_UNDERLINE 4 +#define HT_INVERSE 8 +#define HT_DOUBLE 0x10 + +#define HT_BLACK 0 +#define HT_WHITE 1 + +/* + * Lynx internal character representations. + */ +#define HT_NON_BREAK_SPACE ((char)1) +#define HT_EN_SPACE ((char)2) +#define LY_UNDERLINE_START_CHAR '\003' +#define LY_UNDERLINE_END_CHAR '\004' + +/* Turn about is fair play ASCII platforms use EBCDIC tab; + EBCDIC platforms use ASCII tab for LY_BOLD_START_CHAR. +*/ +#ifdef EBCDIC +#define LY_BOLD_START_CHAR '\011' +#else +#define LY_BOLD_START_CHAR '\005' +#endif + +#define LY_BOLD_END_CHAR '\006' +#define LY_SOFT_HYPHEN ((char)7) +#define LY_SOFT_NEWLINE ((char)8) + +#ifdef EBCDIC +#define IsSpecialAttrChar(a) (((a) > '\002') && ((a) <= '\011') && ((a)!='\t')) +#else +#define IsSpecialAttrChar(a) (((a) > '\002') && ((a) <= '\010')) +#endif + +#define IsNormalChar(a) ((a) != '\0' && !IsSpecialAttrChar(a)) + +#endif /* HTFONT_H */ diff --git a/src/HTForms.h b/src/HTForms.h new file mode 100644 index 0000000..17f7491 --- /dev/null +++ b/src/HTForms.h @@ -0,0 +1,174 @@ +/* + * $LynxId: HTForms.h,v 1.34 2018/05/04 22:50:54 tom Exp $ + */ +#ifndef HTFORMS_H +#define HTFORMS_H + +#ifndef LYSTRUCTS_H +#include +#endif /* LYSTRUCTS_H */ + +#ifdef __cplusplus +extern "C" { +#endif +/* change_form_link() calls change_form_link_ex() with all its args and FALSE + * as last arg + */ extern int change_form_link(int cur, + DocInfo *newdoc, + BOOLEAN *refresh_screen, + int use_last_tfpos, + int immediate_submit); + + extern int change_form_link_ex(int cur, + DocInfo *newdoc, + BOOLEAN *refresh_screen, + int use_last_tfpos, + int immediate_submit, + int draw_only); + +/* InputFieldData is used to pass the info between HTML.c and Gridtext.c in + * HText_beginInput() + */ + typedef struct _InputFieldData { + const char *accept; + const char *align; + int checked; + const char *iclass; + int disabled; + int readonly; + const char *error; + const char *height; + const char *id; + const char *lang; + const char *max; + const char *maxlength; + const char *md; + const char *min; + const char *name; + int size; + const char *src; + const char *type; + char *value; + const char *width; + int name_cs; /* charset handle for name */ + int value_cs; /* charset handle for value */ + const char *accept_cs; + } InputFieldData; + +/* The OptionType structure is for a linked list of option entries + */ + typedef struct _OptionType { + char *name; /* the name of the entry */ + char *cp_submit_value; /* the value to submit */ + int value_cs; /* charset value is in */ + struct _OptionType *next; /* the next entry */ + } OptionType; + +/* + * The FormInfo structure is used to contain the form field data within each + * anchor. A pointer to this structure is in the TextAnchor struct. + */ + typedef struct _FormInfo { + char *name; /* the name of the link */ + int number; /* which form is the link within */ + int type; /* string, int, etc. */ + char *value; /* user entered string data */ + char *orig_value; /* the original value */ + int size; /* width on the screen */ + unsigned maxlength; /* max width of data */ + int group; /* a group associated with the link + * this is used for select's + */ + int num_value; /* value of the numerical fields */ + int hrange; /* high numerical range */ + int lrange; /* low numerical range */ + OptionType *select_list; /* array of option choices */ + char *submit_action; /* form's action */ + int submit_method; /* form's method */ + char *submit_enctype; /* form's entype */ + char *submit_title; /* form's title */ + BOOL no_cache; /* Always resubmit? */ + char *cp_submit_value; /* option value to submit */ + char *orig_submit_value; /* original submit value */ + int size_l; /* The length of the option list */ + int disabled; /* If YES, can't change values */ + int readonly; /* If YES, can't change values */ + int name_cs; + int value_cs; + char *accept_cs; + } FormInfo; + +#define FormIsReadonly(form) ((form) && ((form)->disabled || (form)->readonly)) + +/* + * As structure for info associated with a form. There is some redundancy + * here, this shouldn't waste too much memory since the total number of forms + * (as opposed to form fields) per doc is expected to be rather small. More + * things which are per form rather than per field could be moved here. - kw + */ + typedef struct _PerFormInfo { + int number; /* form number, see GridText.c */ + int disabled; /* If YES, can't change values */ + FormInfo data; + struct _PerFormInfo *next; /* pointer to next form in doc */ + int nfields; /* number of fields */ + FormInfo *first_field; + FormInfo *last_field; /* pointer to last field in form */ + char *accept_cs; + char *thisacceptcs; /* used during submit */ + } PerFormInfo; + +#define HYPERTEXT_ANCHOR 1 +#define INPUT_ANCHOR 2 /* forms mode input fields */ +#define INTERNAL_LINK_ANCHOR 5 /* 1+4, can be used as bitflag... - kw */ + + typedef enum { + F_UNKNOWN = 0, + F_TEXT_TYPE, + F_PASSWORD_TYPE, + F_CHECKBOX_TYPE, + F_RADIO_TYPE, + F_SUBMIT_TYPE, + F_RESET_TYPE, + F_OPTION_LIST_TYPE, + F_HIDDEN_TYPE, + F_TEXTAREA_TYPE, + F_RANGE_TYPE, + F_FILE_TYPE, + F_TEXT_SUBMIT_TYPE, + F_IMAGE_SUBMIT_TYPE, + F_KEYGEN_TYPE, + F_BUTTON_TYPE + } FieldTypes; + +#define F_SUBMITLIKE(type) ((type) == F_SUBMIT_TYPE || \ + (type) == F_IMAGE_SUBMIT_TYPE || \ + (type) == F_TEXT_SUBMIT_TYPE) + +#define F_TEXTLIKE(type) ((type) == F_TEXT_TYPE || \ + (type) == F_TEXT_SUBMIT_TYPE || \ + (type) == F_PASSWORD_TYPE || \ + (type) == F_FILE_TYPE || \ + (type) == F_TEXTAREA_TYPE) + +#define WWW_FORM_LINK_TYPE 1 +#define WWW_LINK_TYPE 2 +#define WWW_INTERN_LINK_TYPE 6 /* can be used as a bitflag... - kw */ +#define LINK_LINE_FOUND 8 /* used in follow_link_number, others - kw */ +#define LINK_DO_ARROWUP 16 /* returned by HTGetLinkOrFieldStart - kw */ + +/* #define different lynx modes */ +#define NORMAL_LYNX_MODE 1 +#define FORMS_LYNX_MODE 2 + +#define FIRST_ORDER 1 +#define MIDDLE_ORDER 2 +#define LAST_ORDER 3 + +/* in LYForms.c */ + extern void show_formlink_statusline(const FormInfo * form, + int for_what); +#ifdef __cplusplus +} +#endif +#endif /* HTFORMS_H */ diff --git a/src/HTInit.c b/src/HTInit.c new file mode 100644 index 0000000..466bc72 --- /dev/null +++ b/src/HTInit.c @@ -0,0 +1,1503 @@ +/* + * $LynxId: HTInit.c,v 1.98 2022/06/12 21:17:37 tom Exp $ + * + * Configuration-specific Initialization HTInit.c + * ---------------------------------------- + */ + +/* Define a basic set of suffixes and presentations + * ------------------------------------------------ + */ + +#include + +/* Implements: +*/ +#include + +#include +#include +#include +#include +#include +#include +#include + +#include /* LJM */ +#include +#include +#include + +#include +#include + +#define CTrace(p) CTRACE2(TRACE_CFG, p) + +static int HTLoadTypesConfigFile(char *fn, AcceptMedia media); +static int HTLoadExtensionsConfigFile(char *fn); + +#define SET_SUFFIX1(suffix, description, type) \ + HTSetSuffix(suffix, description, type, 1.0) + +#define SET_SUFFIX5(suffix, mimetype, type, description) \ + HTSetSuffix5(suffix, mimetype, type, description, 1.0) + +#define SET_PRESENT(mimetype, command, quality, delay) \ + HTSetPresentation(mimetype, command, 0, quality, delay, 0.0, 0L, media) + +#define SET_EXTERNL(rep_in, rep_out, command, quality) \ + HTSetConversion(rep_in, rep_out, command, quality, 3.0, 0.0, 0L, mediaEXT) + +#define SET_INTERNL(rep_in, rep_out, command, quality) \ + HTSetConversion(rep_in, rep_out, command, quality, 0.0, 0.0, 0L, mediaINT) + +void HTFormatInit(void) +{ + AcceptMedia media = mediaEXT; + + CTrace((tfp, "HTFormatInit\n")); +#ifdef NeXT + SET_PRESENT("application/postscript", "open %s", 1.0, 2.0); + SET_PRESENT("image/x-tiff", "open %s", 2.0, 2.0); + SET_PRESENT("image/tiff", "open %s", 1.0, 2.0); + SET_PRESENT("audio/basic", "open %s", 1.0, 2.0); + SET_PRESENT("*", "open %s", 1.0, 0.0); +#else + if (LYgetXDisplay() != 0) { /* Must have X11 */ + SET_PRESENT("application/postscript", "ghostview %s&", 1.0, 3.0); + if (non_empty(XLoadImageCommand)) { + /* *INDENT-OFF* */ + SET_PRESENT("image/gif", XLoadImageCommand, 1.0, 3.0); + SET_PRESENT("image/x-xbm", XLoadImageCommand, 1.0, 3.0); + SET_PRESENT("image/x-xbitmap", XLoadImageCommand, 1.0, 3.0); + SET_PRESENT("image/x-png", XLoadImageCommand, 2.0, 3.0); + SET_PRESENT("image/png", XLoadImageCommand, 1.0, 3.0); + SET_PRESENT("image/x-rgb", XLoadImageCommand, 1.0, 3.0); + SET_PRESENT("image/x-tiff", XLoadImageCommand, 2.0, 3.0); + SET_PRESENT("image/tiff", XLoadImageCommand, 1.0, 3.0); + SET_PRESENT("image/jpeg", XLoadImageCommand, 1.0, 3.0); + /* *INDENT-ON* */ + + } + SET_PRESENT("video/mpeg", "mpeg_play %s &", 1.0, 3.0); + + } +#endif + +#ifdef EXEC_SCRIPTS + /* set quality to 999.0 for protected exec applications */ +#ifndef VMS + SET_PRESENT("application/x-csh", "csh %s", 999.0, 3.0); + SET_PRESENT("application/x-sh", "sh %s", 999.0, 3.0); + SET_PRESENT("application/x-ksh", "ksh %s", 999.0, 3.0); +#else + SET_PRESENT("application/x-VMS_script", "@%s", 999.0, 3.0); +#endif /* not VMS */ +#endif /* EXEC_SCRIPTS */ + + /* + * Add our header handlers. + */ + SET_INTERNL("message/x-http-redirection", "*", HTMIMERedirect, 2.0); + SET_INTERNL("message/x-http-redirection", STR_PRESENT, HTMIMERedirect, 2.0); + SET_INTERNL("message/x-http-redirection", "www/debug", HTMIMERedirect, 1.0); + SET_INTERNL("www/mime", STR_PRESENT, HTMIMEConvert, 1.0); + SET_INTERNL("www/mime", STR_DOWNLOAD, HTMIMEConvert, 1.0); + SET_INTERNL("www/mime", STR_SOURCE, HTMIMEConvert, 1.0); + SET_INTERNL("www/mime", STR_DUMP, HTMIMEConvert, 1.0); + + /* + * Add our compressed file handlers. + */ + SET_INTERNL("www/compressed", STR_DOWNLOAD, HTCompressed, 1.0); + SET_INTERNL("www/compressed", STR_PRESENT, HTCompressed, 1.0); + SET_INTERNL("www/compressed", STR_SOURCE, HTCompressed, 1.0); + SET_INTERNL("www/compressed", STR_DUMP, HTCompressed, 1.0); + + /* + * The following support some content types seen here/there: + */ + SET_INTERNL("application/html", "text/x-c", HTMLToC, 0.5); + SET_INTERNL("application/html", STR_PLAINTEXT, HTMLToPlain, 0.5); + SET_INTERNL("application/html", STR_PRESENT, HTMLPresent, 2.0); + SET_INTERNL("application/html", STR_SOURCE, HTPlainPresent, 1.0); + SET_INTERNL("application/xml", STR_PRESENT, HTMLPresent, 2.0); + SET_INTERNL("application/x-wais-source", STR_SOURCE, HTPlainPresent, 1.0); + SET_INTERNL("application/x-wais-source", STR_PRESENT, HTWSRCConvert, 2.0); + SET_INTERNL("application/x-wais-source", STR_DOWNLOAD, HTWSRCConvert, 1.0); + SET_INTERNL("application/x-wais-source", STR_DUMP, HTWSRCConvert, 1.0); + + /* + * Save all unknown mime types to disk. + */ + SET_EXTERNL(STR_SOURCE, STR_PRESENT, HTSaveToFile, 1.0); + SET_EXTERNL(STR_SOURCE, STR_SOURCE, HTSaveToFile, 1.0); + SET_EXTERNL(STR_SOURCE, STR_DOWNLOAD, HTSaveToFile, 1.0); + SET_EXTERNL(STR_SOURCE, "*", HTSaveToFile, 1.0); + + /* + * Output all www/dump presentations to stdout. + */ + SET_EXTERNL(STR_SOURCE, STR_DUMP, HTDumpToStdout, 1.0); + + /* + * Other internal types, which must precede the "www/present" entries + * below (otherwise, they will be filtered out in HTFilterPresentations()). + */ + SET_INTERNL("text/css", STR_PLAINTEXT, HTMLToPlain, 0.5); + SET_INTERNL(STR_HTML, STR_PLAINTEXT, HTMLToPlain, 0.5); + SET_INTERNL(STR_HTML, "text/x-c", HTMLToC, 0.5); + SET_INTERNL(STR_HTML, STR_SOURCE, HTPlainPresent, 1.0); + SET_INTERNL(STR_PLAINTEXT, STR_SOURCE, HTPlainPresent, 1.0); + SET_INTERNL("text/sgml", STR_SOURCE, HTPlainPresent, 1.0); + SET_INTERNL("text/x-sgml", STR_SOURCE, HTPlainPresent, 1.0); + + /* + * Now add our basic conversions. These include the types which will + * be listed in a "Accept:" line sent to a server. These criteria are + * used in HTFilterPresentations() to select acceptable types: + * + * a) input is not "www/mime" or "www/compressed" + * b) output is "www/present" + * c) quality is in the range 0.0 to 1.0, i.e., excludes the 2.0's. + * + * For reference: + * RFC 1874 - text/sgml + * RFC 2046 - text/plain + * RFC 2318 - text/css + * RFC 3023 - text/xml + * obsolete - text/x-sgml + * + * as well as + * http://www.iana.org/assignments/media-types/media-types.xhtml + * + * and + * http://www.w3.org/TR/xhtml-media-types/ + * + * which describes + * application/xhtml+xml + * text/html + */ + SET_INTERNL("application/xhtml+xml", STR_PRESENT, XHTMLPresent, 1.0); + SET_INTERNL("application/xhtml+xml", STR_SOURCE, HTPlainPresent, 1.0); + SET_INTERNL("text/css", STR_PRESENT, HTPlainPresent, 1.0); + SET_INTERNL(STR_HTML, STR_PRESENT, HTMLPresent, 1.0); + SET_INTERNL(STR_PLAINTEXT, STR_PRESENT, HTPlainPresent, 1.0); + SET_INTERNL("text/sgml", STR_PRESENT, HTMLPresent, 1.0); + SET_INTERNL("text/x-sgml", STR_PRESENT, HTMLPresent, 2.0); + SET_INTERNL("text/xml", STR_PRESENT, HTMLPresent, 2.0); + + if (LYisAbsPath(global_type_map)) { + /* These should override the default types as necessary. */ + HTLoadTypesConfigFile(global_type_map, mediaSYS); + } + + /* + * Load the local maps. + */ + if (IsOurFile(LYAbsOrHomePath(&personal_type_map)) + && LYCanReadFile(personal_type_map)) { + /* These should override everything else. */ + HTLoadTypesConfigFile(personal_type_map, mediaUSR); + } + + /* + * Put text/html and text/plain at beginning of list. - kw + */ + HTReorderPresentation(WWW_PLAINTEXT, WWW_PRESENT); + HTReorderPresentation(WWW_HTML, WWW_PRESENT); + + /* + * Analyze the list, and set 'get_accept' for those whose representations + * are not redundant. + */ + HTFilterPresentations(); +} + +void HTPreparsedFormatInit(void) +{ + CTrace((tfp, "HTPreparsedFormatInit\n")); + if (LYPreparsedSource) { + SET_INTERNL(STR_HTML, STR_SOURCE, HTMLParsedPresent, 1.0); + SET_INTERNL(STR_HTML, STR_DUMP, HTMLParsedPresent, 1.0); + } +} + +/* Some of the following is taken from: */ + +/* +Copyright (c) 1991 Bell Communications Research, Inc. (Bellcore) + +Permission to use, copy, modify, and distribute this material +for any purpose and without fee is hereby granted, provided +that the above copyright notice and this permission notice +appear in all copies, and that the name of Bellcore not be +used in advertising or publicity pertaining to this +material without the specific, prior written permission +of an authorized representative of Bellcore. BELLCORE +MAKES NO REPRESENTATIONS ABOUT THE ACCURACY OR SUITABILITY +OF THIS MATERIAL FOR ANY PURPOSE. IT IS PROVIDED "AS IS", +WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. +*/ +/****************************************************** + Metamail -- A tool to help diverse mail readers + cope with diverse multimedia mail formats. + + Author: Nathaniel S. Borenstein, Bellcore + + ******************************************************* */ + +struct MailcapEntry { + char *contenttype; + char *command; + char *testcommand; + int needsterminal; + int copiousoutput; + int needtofree; + char *label; + char *printcommand; + char *nametemplate; + float quality; + long int maxbytes; +}; + +static int ExitWithError(const char *txt); +static int PassesTest(struct MailcapEntry *mc); + +static char *GetCommand(char *s, char **t) +{ + char *s2; + int quoted = 0; + + s = LYSkipBlanks(s); + /* marca -- added + 1 for error case -- oct 24, 1993. */ + s2 = typeMallocn(char, strlen(s) * 2 + 1); /* absolute max, if all % signs */ + + if (!s2) + ExitWithError(MEMORY_EXHAUSTED_ABORT); + + *t = s2; + while (non_empty(s)) { + if (quoted) { + if (*s == '%') + *s2++ = '%'; /* Quote through next level, ugh! */ + + *s2++ = *s++; + quoted = 0; + } else { + if (*s == ';') { + *s2 = '\0'; + return (++s); + } + if (*s == ESCAPE) { + quoted = 1; + ++s; + } else { + *s2++ = *s++; + } + } + } + *s2 = '\0'; + return (NULL); +} + +/* no leading or trailing space, all lower case */ +static char *Cleanse(char *s) +{ + LYTrimLeading(s); + LYTrimTrailing(s); + LYLowerCase(s); + return (s); +} + +/* remove unnecessary (unquoted) blanks in a shell command */ +static void TrimCommand(char *command) +{ + LYTrimTrailing(command); +#ifdef UNIX + { + char *s = command; + char *d = command; + int ch; + int c0 = ' '; + BOOL escape = FALSE; + BOOL dquote = FALSE; + BOOL squote = FALSE; + + while ((ch = *s++) != '\0') { + if (escape) { + escape = FALSE; + } else if (squote) { + if (ch == SQUOTE) + squote = FALSE; + } else if (dquote) { + switch (ch) { + case DQUOTE: + dquote = FALSE; + break; + case ESCAPE: + escape = TRUE; + break; + } + } else { + switch (ch) { + case DQUOTE: + dquote = TRUE; + break; + case SQUOTE: + squote = TRUE; + break; + } + } + if (!escape && !dquote && !squote) { + if (ch == '\t') + ch = ' '; + if (ch == ' ') { + if (c0 == ' ') + continue; + } + } + *d++ = (char) ch; + c0 = ch; + } + *d = '\0'; + } +#endif +} + +static int ProcessMailcapEntry(FILE *fp, struct MailcapEntry *mc, AcceptMedia media) +{ + size_t rawentryalloc = 2000, len, need; + char *rawentry, *s, *t; + char *LineBuf = NULL; + + rawentry = (char *) malloc(rawentryalloc); + if (!rawentry) + ExitWithError(MEMORY_EXHAUSTED_ABORT); + + *rawentry = '\0'; + while (LYSafeGets(&LineBuf, fp) != 0) { + LYTrimNewline(LineBuf); + if (LineBuf[0] == '#' || LineBuf[0] == '\0') + continue; + len = strlen(LineBuf); + need = len + strlen(rawentry) + 1; + if (need > rawentryalloc) { + rawentryalloc += (2000 + need); + rawentry = typeRealloc(char, rawentry, rawentryalloc); + + if (!rawentry) + ExitWithError(MEMORY_EXHAUSTED_ABORT); + } + if (len > 0 && LineBuf[len - 1] == ESCAPE) { + LineBuf[len - 1] = '\0'; + strcat(rawentry, LineBuf); + } else { + strcat(rawentry, LineBuf); + break; + } + } + FREE(LineBuf); + + t = s = LYSkipBlanks(rawentry); + if (!*s) { + /* totally blank entry -- quietly ignore */ + FREE(rawentry); + return (0); + } + s = StrChr(rawentry, ';'); + if (s == NULL) { + CTrace((tfp, + "ProcessMailcapEntry: Ignoring invalid mailcap entry: %s\n", + rawentry)); + FREE(rawentry); + return (0); + } + *s++ = '\0'; + if (!strncasecomp(t, STR_HTML, 9) || + !strncasecomp(t, STR_PLAINTEXT, 10)) { + --s; + *s = ';'; + CTrace((tfp, "ProcessMailcapEntry: Ignoring mailcap entry: %s\n", + rawentry)); + FREE(rawentry); + return (0); + } + LYRemoveBlanks(rawentry); + LYLowerCase(rawentry); + + mc->needsterminal = 0; + mc->copiousoutput = 0; + mc->needtofree = 1; + mc->testcommand = NULL; + mc->label = NULL; + mc->printcommand = NULL; + mc->contenttype = NULL; + StrAllocCopy(mc->contenttype, rawentry); + mc->quality = (float) 1.0; + mc->maxbytes = 0; + t = GetCommand(s, &mc->command); + if (!t) { + goto assign_presentation; + } + s = LYSkipBlanks(t); + while (s) { + char *arg, *eq, *mallocd_string; + + t = GetCommand(s, &mallocd_string); + arg = mallocd_string; + eq = StrChr(arg, '='); + if (eq) { + *eq++ = '\0'; + eq = LYSkipBlanks(eq); + } + if (non_empty(arg)) { + arg = Cleanse(arg); + if (!strcmp(arg, "needsterminal")) { + mc->needsterminal = 1; + } else if (!strcmp(arg, "copiousoutput")) { + mc->copiousoutput = 1; + } else if (eq && !strcmp(arg, "test")) { + mc->testcommand = NULL; + StrAllocCopy(mc->testcommand, eq); + TrimCommand(mc->testcommand); + CTrace((tfp, "ProcessMailcapEntry: Found testcommand:%s\n", + mc->testcommand)); + } else if (eq && !strcmp(arg, "description")) { + mc->label = eq; /* ignored */ + } else if (eq && !strcmp(arg, "label")) { + mc->label = eq; /* ignored: bogus old name for description */ + } else if (eq && !strcmp(arg, "print")) { + mc->printcommand = eq; /* ignored */ + } else if (eq && !strcmp(arg, "textualnewlines")) { + /* no support for now. What does this do anyways? */ + /* ExceptionalNewline(mc->contenttype, atoi(eq)); */ + } else if (eq && !strcmp(arg, "q")) { + mc->quality = (float) atof(eq); + if (mc->quality > 0.000 && mc->quality < 0.001) + mc->quality = (float) 0.001; + } else if (eq && !strcmp(arg, "mxb")) { + mc->maxbytes = atol(eq); + if (mc->maxbytes < 0) + mc->maxbytes = 0; + } else if (strcmp(arg, "notes")) { /* IGNORE notes field */ + if (*arg) + CTrace((tfp, + "ProcessMailcapEntry: Ignoring mailcap flag '%s'.\n", + arg)); + } + + } + FREE(mallocd_string); + s = t; + } + + assign_presentation: + FREE(rawentry); + + if (PassesTest(mc)) { + CTrace((tfp, "ProcessMailcapEntry Setting up conversion %s : %s\n", + mc->contenttype, mc->command)); + HTSetPresentation(mc->contenttype, + mc->command, + mc->testcommand, + mc->quality, + 3.0, 0.0, mc->maxbytes, media); + } + FREE(mc->command); + FREE(mc->testcommand); + FREE(mc->contenttype); + + return (1); +} + +#define L_CURL '{' +#define R_CURL '}' + +static const char *LYSkipQuoted(const char *s) +{ + int escaped = 0; + + ++s; /* skip first quote */ + while (*s != 0) { + if (escaped) { + escaped = 0; + } else if (*s == ESCAPE) { + escaped = 1; + } else if (*s == DQUOTE) { + ++s; + break; + } + ++s; + } + return s; +} + +/* + * Note: the tspecials[] here are those defined for Content-Type header, so + * this function is not really general-purpose. + */ +static const char *LYSkipToken(const char *s) +{ + static const char tspecials[] = "\"()<>@,;:\\/[]?.="; + + while (*s != '\0' && !WHITE(*s) && StrChr(tspecials, *s) == 0) { + ++s; + } + return s; +} + +static const char *LYSkipValue(const char *s) +{ + if (*s == DQUOTE) + s = LYSkipQuoted(s); + else + s = LYSkipToken(s); + return s; +} + +/* + * Copy the value from the source, dequoting if needed. + */ +static char *LYCopyValue(const char *s) +{ + const char *t; + char *result = 0; + int j, k; + + if (*s == DQUOTE) { + t = LYSkipQuoted(s); + StrAllocCopy(result, s + 1); + result[t - s - 2] = '\0'; + for (j = k = 0;; ++j, ++k) { + if (result[j] == ESCAPE) { + ++j; + } + if ((result[k] = result[j]) == '\0') + break; + } + } else { + t = LYSkipToken(s); + StrAllocCopy(result, s); + result[t - s] = '\0'; + } + return result; +} + +/* + * The "Content-Type:" field, contains zero or more parameters after a ';'. + * Return the value of the named parameter, or null. + */ +static char *LYGetContentType(const char *name, + const char *params) +{ + char *result = 0; + + if (params != 0) { + if (name != 0) { + size_t length = strlen(name); + const char *test = StrChr(params, ';'); /* skip type/subtype */ + const char *next; + + while (test != 0) { + BOOL found = FALSE; + + ++test; /* skip the ';' */ + test = LYSkipCBlanks(test); + next = LYSkipToken(test); + if ((next - test) == (int) length + && !StrNCmp(test, name, length)) { + found = TRUE; + } + test = LYSkipCBlanks(next); + if (*test == '=') { + ++test; + test = LYSkipCBlanks(test); + if (found) { + result = LYCopyValue(test); + break; + } else { + test = LYSkipValue(test); + } + test = LYSkipCBlanks(test); + } + if (*test != ';') { + break; /* we're lost */ + } + } + } else { /* return the content-type */ + StrAllocCopy(result, params); + *LYSkipNonBlanks(result) = '\0'; + } + } + return result; +} + +/* + * Check if the command uses a "%s" substitution. We need to know this, to + * decide when to create temporary files, etc. + */ +BOOL LYMailcapUsesPctS(const char *controlstring) +{ + BOOL result = FALSE; + const char *from; + const char *next; + int prefixed = 0; + int escaped = 0; + + for (from = controlstring; *from != '\0'; from++) { + if (escaped) { + escaped = 0; + } else if (*from == ESCAPE) { + escaped = 1; + } else if (prefixed) { + prefixed = 0; + switch (*from) { + case '%': /* not defined */ + case 'n': + case 'F': + case 't': + break; + case 's': + result = TRUE; + break; + case L_CURL: + next = StrChr(from, R_CURL); + if (next != 0) { + from = next; + break; + } + /* FALLTHRU */ + default: + break; + } + } else if (*from == '%') { + prefixed = 1; + } + } + return result; +} + +/* + * Build the command string for testing or executing a mailcap entry. + * If a substitution from the Content-Type header is requested but no + * parameters are available, return -1, otherwise 0. + * + * This does not support multipart %n or %F (does this apply to lynx?) + */ +static int BuildCommand(HTChunk *cmd, + const char *controlstring, + const char *TmpFileName, + const char *params) +{ + int result = 0; + size_t TmpFileLen = strlen(TmpFileName); + const char *from; + const char *next; + char *name, *value; + int prefixed = 0; + int escaped = 0; + + for (from = controlstring; *from != '\0'; from++) { + if (escaped) { + escaped = 0; + HTChunkPutc(cmd, UCH(*from)); + } else if (*from == ESCAPE) { + escaped = 1; + } else if (prefixed) { + prefixed = 0; + switch (*from) { + case '%': /* not defined */ + HTChunkPutc(cmd, UCH(*from)); + break; + case 'n': + /* FALLTHRU */ + case 'F': + CTrace((tfp, "BuildCommand: Bad mailcap \"test\" clause: %s\n", + controlstring)); + break; + case 't': + if ((value = LYGetContentType(NULL, params)) != 0) { + HTChunkPuts(cmd, value); + FREE(value); + } + break; + case 's': + if (TmpFileLen) { + HTChunkPuts(cmd, TmpFileName); + } + break; + case L_CURL: + next = StrChr(from, R_CURL); + if (next != 0) { + if (params != 0) { + ++from; + name = 0; + HTSprintf0(&name, "%.*s", (int) (next - from), from); + if ((value = LYGetContentType(name, params)) != 0) { + HTChunkPuts(cmd, value); + FREE(value); + } else if (name) { + if (!strcmp(name, "charset")) { + HTChunkPuts(cmd, "ISO-8859-1"); + } else { + CTrace((tfp, "BuildCommand no value for %s\n", name)); + } + } + FREE(name); + } else { + result = -1; + } + from = next; + break; + } + /* FALLTHRU */ + default: + CTrace((tfp, + "BuildCommand: Ignoring unrecognized format code in mailcap file '%%%c'.\n", + *from)); + break; + } + } else if (*from == '%') { + prefixed = 1; + } else { + HTChunkPutc(cmd, UCH(*from)); + } + } + HTChunkTerminate(cmd); + return result; +} + +/* + * Build the mailcap test-command and execute it. This is only invoked when + * we cannot tell just by looking at the command if it would succeed. + * + * Returns 0 for success, -1 for error and 1 for deferred. + */ +int LYTestMailcapCommand(const char *testcommand, + const char *params) +{ + int result; + char TmpFileName[LY_MAXPATH]; + HTChunk *expanded = 0; + + if (LYMailcapUsesPctS(testcommand)) { + if (LYOpenTemp(TmpFileName, HTML_SUFFIX, "w") == 0) + ExitWithError(CANNOT_OPEN_TEMP); + LYCloseTemp(TmpFileName); + } else { + /* We normally don't need a temp file name - kw */ + TmpFileName[0] = '\0'; + } + expanded = HTChunkCreate(1024); + if (BuildCommand(expanded, testcommand, TmpFileName, params) != 0) { + result = 1; + CTrace((tfp, "PassesTest: Deferring test command: %s\n", expanded->data)); + } else { + CTrace((tfp, "PassesTest: Executing test command: %s\n", expanded->data)); + if ((result = LYSystem(expanded->data)) != 0) { + result = -1; + CTrace((tfp, "PassesTest: Test failed!\n")); + } else { + CTrace((tfp, "PassesTest: Test passed!\n")); + } + } + + HTChunkFree(expanded); + (void) LYRemoveTemp(TmpFileName); + + return result; +} + +char *LYMakeMailcapCommand(const char *command, + const char *params, + const char *filename) +{ + HTChunk *expanded = 0; + char *result = 0; + + expanded = HTChunkCreate(1024); + BuildCommand(expanded, command, filename, params); + StrAllocCopy(result, expanded->data); + HTChunkFree(expanded); + return result; +} + +#define RTR_forget 0 +#define RTR_lookup 1 +#define RTR_add 2 + +static int RememberTestResult(int mode, char *cmd, int result) +{ + struct cmdlist_s { + char *cmd; + int result; + struct cmdlist_s *next; + }; + static struct cmdlist_s *cmdlist = NULL; + struct cmdlist_s *cur; + + switch (mode) { + case RTR_forget: + while (cmdlist) { + cur = cmdlist->next; + FREE(cmdlist->cmd); + FREE(cmdlist); + cmdlist = cur; + } + break; + case RTR_lookup: + for (cur = cmdlist; cur; cur = cur->next) + if (!strcmp(cmd, cur->cmd)) + return cur->result; + return -1; + case RTR_add: + cur = typecalloc(struct cmdlist_s); + + if (cur == NULL) + outofmem(__FILE__, "RememberTestResult"); + + cur->next = cmdlist; + StrAllocCopy(cur->cmd, cmd); + cur->result = result; + cmdlist = cur; + break; + } + return 0; +} + +/* FIXME: this sometimes used caseless comparison, e.g., strcasecomp */ +#define SameCommand(tst,ref) !strcmp(tst,ref) + +static int PassesTest(struct MailcapEntry *mc) +{ + int result; + + /* + * Make sure we have a command + */ + if (!mc->testcommand) + return (1); + + /* + * Save overhead of system() calls by faking these. - FM + */ + if (SameCommand(mc->testcommand, "test \"$DISPLAY\"") || + SameCommand(mc->testcommand, "test \"$DISPLAY\" != \"\"") || + SameCommand(mc->testcommand, "test -n \"$DISPLAY\"")) { + FREE(mc->testcommand); + CTrace((tfp, "PassesTest: Testing for XWINDOWS environment.\n")); + if (LYgetXDisplay() != NULL) { + CTrace((tfp, "PassesTest: Test passed!\n")); + return (0 == 0); + } else { + CTrace((tfp, "PassesTest: Test failed!\n")); + return (-1 == 0); + } + } + if (SameCommand(mc->testcommand, "test -z \"$DISPLAY\"")) { + FREE(mc->testcommand); + CTrace((tfp, "PassesTest: Testing for NON_XWINDOWS environment.\n")); + if (LYgetXDisplay() == NULL) { + CTrace((tfp, "PassesTest: Test passed!\n")); + return (0 == 0); + } else { + CTrace((tfp, "PassesTest: Test failed!\n")); + return (-1 == 0); + } + } + + /* + * Why do anything but return success for this one! - FM + */ + if (SameCommand(mc->testcommand, "test -n \"$LYNX_VERSION\"")) { + FREE(mc->testcommand); + CTrace((tfp, "PassesTest: Testing for LYNX environment.\n")); + CTrace((tfp, "PassesTest: Test passed!\n")); + return (0 == 0); + } else + /* + * ... or failure for this one! - FM + */ + if (SameCommand(mc->testcommand, "test -z \"$LYNX_VERSION\"")) { + FREE(mc->testcommand); + CTrace((tfp, "PassesTest: Testing for non-LYNX environment.\n")); + CTrace((tfp, "PassesTest: Test failed!\n")); + return (-1 == 0); + } + + result = RememberTestResult(RTR_lookup, mc->testcommand, 0); + if (result == -1) { + result = LYTestMailcapCommand(mc->testcommand, NULL); + RememberTestResult(RTR_add, mc->testcommand, result ? 1 : 0); + } + + /* + * Free the test command as well since + * we won't be needing it anymore. + */ + if (result != 1) + FREE(mc->testcommand); + + if (result < 0) { + CTrace((tfp, "PassesTest: Test failed!\n")); + } else if (result == 0) { + CTrace((tfp, "PassesTest: Test passed!\n")); + } + + return (result >= 0); +} + +static int ProcessMailcapFile(char *file, AcceptMedia media) +{ + struct MailcapEntry mc; + FILE *fp; + + CTrace((tfp, "ProcessMailcapFile: Loading file '%s'.\n", + file)); + if ((fp = fopen(file, TXT_R)) == NULL) { + CTrace((tfp, "ProcessMailcapFile: Could not open '%s'.\n", + file)); + return (-1 == 0); + } + + while (fp && !feof(fp)) { + ProcessMailcapEntry(fp, &mc, media); + } + LYCloseInput(fp); + RememberTestResult(RTR_forget, NULL, 0); + return (0 == 0); +} + +static int ExitWithError(const char *txt) +{ + if (txt) + fprintf(tfp, "Lynx: %s\n", txt); + exit_immediately(EXIT_FAILURE); + return (-1); +} + +/* Reverse the entries from each mailcap after it has been read, so that + * earlier entries have precedence. Set to 0 to get traditional lynx + * behavior, which means that the last match wins. - kw */ +static int reverse_mailcap = 1; + +static int HTLoadTypesConfigFile(char *fn, AcceptMedia media) +{ + int result = 0; + HTList *saved = HTPresentations; + + if (reverse_mailcap) { /* temporarily hide existing list */ + HTPresentations = NULL; + } + + result = ProcessMailcapFile(fn, media); + + if (reverse_mailcap) { + if (result && HTPresentations) { + HTList_reverse(HTPresentations); + HTList_appendList(HTPresentations, saved); + FREE(saved); + } else { + HTPresentations = saved; + } + } + return result; +} + +/* ------------------------------------------------------------------------ */ +/* ------------------------------------------------------------------------ */ +/* ------------------------------------------------------------------------ */ + +/* Define a basic set of suffixes + * ------------------------------ + * + * The LAST suffix for a type is that used for temporary files + * of that type. + * The quality is an apriori bias as to whether the file should be + * used. Not that different suffixes can be used to represent files + * which are of the same format but are originals or regenerated, + * with different values. + */ +/* + * Additional notes: the encoding parameter may be taken into account when + * looking for a match; for that purpose "7bit", "8bit", and "binary" are + * equivalent. + * + * Use of mixed case and of pseudo MIME types with embedded spaces should be + * avoided. It was once necessary for getting the fancy strings into type + * labels in FTP directory listings, but that can now be done with the + * description field (using HTSetSuffix5). AFAIK the only effect of such + * "fancy" (and mostly invalid) types that cannot be reproduced by using a + * description fields is some statusline messages in SaveToFile (HTFWriter.c). + * And showing the user an invalid MIME type as the 'Content-type:' is not such + * a hot idea anyway, IMO. Still, if you want it, it is still possible (even + * in lynx.cfg now), but use of it in the defaults below has been reduced. + * + * Case variations rely on peculiar behavior of HTAtom.c for matching. They + * lead to surprising behavior, Lynx retains the case of a string in the form + * first encountered after starting up. So while later suffix rules generally + * override or modify earlier ones, the case used for a MIME time is determined + * by the first suffix rule (or other occurrence). Matching in HTAtom_for is + * effectively case insensitive, except for the first character of the string + * which is treated as case-sensitive by the hash function there; best not to + * rely on that, rather convert MIME types to lowercase on input as is already + * done in most places (And HTAtom could become consistently case-sensitive, as + * in newer W3C libwww). + * - kw 1999-10-12 + */ +void HTFileInit(void) +{ +#ifdef BUILTIN_SUFFIX_MAPS + if (LYUseBuiltinSuffixes) { + CTrace((tfp, "HTFileInit: Loading default (HTInit) extension maps.\n")); + + /* default suffix interpretation */ + SET_SUFFIX1("*", STR_PLAINTEXT, "8bit"); + SET_SUFFIX1("*.*", STR_PLAINTEXT, "8bit"); + +#ifdef EXEC_SCRIPTS + /* + * define these extensions for exec scripts. + */ +#ifndef VMS + /* for csh exec links */ + HTSetSuffix(".csh", "application/x-csh", "8bit", 0.8); + HTSetSuffix(".sh", "application/x-sh", "8bit", 0.8); + HTSetSuffix(".ksh", "application/x-ksh", "8bit", 0.8); +#else + HTSetSuffix(".com", "application/x-VMS_script", "8bit", 0.8); +#endif /* !VMS */ +#endif /* EXEC_SCRIPTS */ + + /* + * Some of the old incarnation of the mappings is preserved and can be had + * by defining TRADITIONAL_SUFFIXES. This is for some cases where I felt + * the old rules might be preferred by someone, for some reason. It's not + * done consistently. A lot more of this stuff could probably be changed + * too or omitted, now that nearly the equivalent functionality is + * available in lynx.cfg. - kw 1999-10-12 + */ + /* *INDENT-OFF* */ + SET_SUFFIX1(".saveme", "application/x-Binary", "binary"); + SET_SUFFIX1(".dump", "application/x-Binary", "binary"); + SET_SUFFIX1(".bin", "application/x-Binary", "binary"); + + SET_SUFFIX1(".arc", "application/x-Compressed", "binary"); + + SET_SUFFIX1(".alpha-exe", "application/x-Executable", "binary"); + SET_SUFFIX1(".alpha_exe", "application/x-Executable", "binary"); + SET_SUFFIX1(".AXP-exe", "application/x-Executable", "binary"); + SET_SUFFIX1(".AXP_exe", "application/x-Executable", "binary"); + SET_SUFFIX1(".VAX-exe", "application/x-Executable", "binary"); + SET_SUFFIX1(".VAX_exe", "application/x-Executable", "binary"); + SET_SUFFIX5(".exe", STR_BINARY, "binary", "Executable"); + +#ifdef TRADITIONAL_SUFFIXES + SET_SUFFIX1(".exe.Z", "application/x-Comp. Executable", "binary"); + SET_SUFFIX1(".Z", "application/UNIX Compressed", "binary"); + SET_SUFFIX1(".tar_Z", "application/UNIX Compr. Tar", "binary"); + SET_SUFFIX1(".tar.Z", "application/UNIX Compr. Tar", "binary"); +#else + SET_SUFFIX5(".Z", "application/x-compress", "binary", "UNIX Compressed"); + SET_SUFFIX5(".Z", NULL, "compress", "UNIX Compressed"); + SET_SUFFIX5(".exe.Z", STR_BINARY, "compress", "Executable"); + SET_SUFFIX5(".tar_Z", "application/x-tar", "compress", "UNIX Compr. Tar"); + SET_SUFFIX5(".tar.Z", "application/x-tar", "compress", "UNIX Compr. Tar"); +#endif + +#ifdef TRADITIONAL_SUFFIXES + SET_SUFFIX1("-gz", "application/GNU Compressed", "binary"); + SET_SUFFIX1("_gz", "application/GNU Compressed", "binary"); + SET_SUFFIX1(".gz", "application/GNU Compressed", "binary"); + + SET_SUFFIX5(".tar.gz", "application/x-tar", "binary", "GNU Compr. Tar"); + SET_SUFFIX5(".tgz", "application/x-tar", "gzip", "GNU Compr. Tar"); +#else + SET_SUFFIX5("-gz", "application/x-gzip", "binary", "GNU Compressed"); + SET_SUFFIX5("_gz", "application/x-gzip", "binary", "GNU Compressed"); + SET_SUFFIX5(".gz", "application/x-gzip", "binary", "GNU Compressed"); + SET_SUFFIX5("-gz", NULL, "gzip", "GNU Compressed"); + SET_SUFFIX5("_gz", NULL, "gzip", "GNU Compressed"); + SET_SUFFIX5(".gz", NULL, "gzip", "GNU Compressed"); + + SET_SUFFIX5(".tar.gz", "application/x-tar", "gzip", "GNU Compr. Tar"); + SET_SUFFIX5(".tgz", "application/x-tar", "gzip", "GNU Compr. Tar"); +#endif + +#ifdef TRADITIONAL_SUFFIXES + SET_SUFFIX1(".src", "application/x-WAIS-source", "8bit"); + SET_SUFFIX1(".wsrc", "application/x-WAIS-source", "8bit"); +#else + SET_SUFFIX5(".wsrc", "application/x-wais-source", "8bit", "WAIS-source"); +#endif + + SET_SUFFIX5(".zip", "application/zip", "binary", "Zip File"); + + SET_SUFFIX1(".zz", "application/x-deflate", "binary"); + SET_SUFFIX1(".zz", "application/deflate", "binary"); + + SET_SUFFIX1(".bz2", "application/x-bzip2", "binary"); + SET_SUFFIX1(".bz2", "application/bzip2", "binary"); + + SET_SUFFIX1(".br", "application/x-brotli", "binary"); + + SET_SUFFIX1(".xz", "application/x-xz", "binary"); + + SET_SUFFIX1(".lz", "application/x-lzip", "binary"); + SET_SUFFIX1(".lzma", "application/x-lzma", "binary"); + +#ifdef TRADITIONAL_SUFFIXES + SET_SUFFIX1(".uu", "application/x-UUencoded", "8bit"); + + SET_SUFFIX1(".hqx", "application/x-Binhex", "8bit"); + + SET_SUFFIX1(".o", "application/x-Prog. Object", "binary"); + SET_SUFFIX1(".a", "application/x-Prog. Library", "binary"); +#else + SET_SUFFIX5(".uu", "application/x-uuencoded", "7bit", "UUencoded"); + + SET_SUFFIX5(".hqx", "application/mac-binhex40", "8bit", "Mac BinHex"); + + HTSetSuffix5(".o", STR_BINARY, "binary", "Prog. Object", 0.5); + HTSetSuffix5(".a", STR_BINARY, "binary", "Prog. Library", 0.5); + HTSetSuffix5(".so", STR_BINARY, "binary", "Shared Lib", 0.5); +#endif + + SET_SUFFIX5(".oda", "application/oda", "binary", "ODA"); + + SET_SUFFIX5(".pdf", "application/pdf", "binary", "PDF"); + + SET_SUFFIX5(".eps", "application/postscript", "8bit", "Postscript"); + SET_SUFFIX5(".ai", "application/postscript", "8bit", "Postscript"); + SET_SUFFIX5(".ps", "application/postscript", "8bit", "Postscript"); + + SET_SUFFIX5(".rtf", "application/rtf", "8bit", "RTF"); + + SET_SUFFIX5(".dvi", "application/x-dvi", "8bit", "DVI"); + + SET_SUFFIX5(".hdf", "application/x-hdf", "8bit", "HDF"); + + SET_SUFFIX1(".cdf", "application/x-netcdf", "8bit"); + SET_SUFFIX1(".nc", "application/x-netcdf", "8bit"); + +#ifdef TRADITIONAL_SUFFIXES + SET_SUFFIX1(".latex", "application/x-Latex", "8bit"); + SET_SUFFIX1(".tex", "application/x-Tex", "8bit"); + SET_SUFFIX1(".texinfo", "application/x-Texinfo", "8bit"); + SET_SUFFIX1(".texi", "application/x-Texinfo", "8bit"); +#else + SET_SUFFIX5(".latex", "application/x-latex", "8bit", "LaTeX"); + SET_SUFFIX5(".tex", "text/x-tex", "8bit", "TeX"); + SET_SUFFIX5(".texinfo", "application/x-texinfo", "8bit", "Texinfo"); + SET_SUFFIX5(".texi", "application/x-texinfo", "8bit", "Texinfo"); +#endif + +#ifdef TRADITIONAL_SUFFIXES + SET_SUFFIX1(".t", "application/x-Troff", "8bit"); + SET_SUFFIX1(".tr", "application/x-Troff", "8bit"); + SET_SUFFIX1(".roff", "application/x-Troff", "8bit"); + + SET_SUFFIX1(".man", "application/x-Troff-man", "8bit"); + SET_SUFFIX1(".me", "application/x-Troff-me", "8bit"); + SET_SUFFIX1(".ms", "application/x-Troff-ms", "8bit"); +#else + SET_SUFFIX5(".t", "application/x-troff", "8bit", "Troff"); + SET_SUFFIX5(".tr", "application/x-troff", "8bit", "Troff"); + SET_SUFFIX5(".roff", "application/x-troff", "8bit", "Troff"); + + SET_SUFFIX5(".man", "application/x-troff-man", "8bit", "Man Page"); + SET_SUFFIX5(".me", "application/x-troff-me", "8bit", "Troff me"); + SET_SUFFIX5(".ms", "application/x-troff-ms", "8bit", "Troff ms"); +#endif + + SET_SUFFIX1(".zoo", "application/x-Zoo File", "binary"); + +#if defined(TRADITIONAL_SUFFIXES) || defined(VMS) + SET_SUFFIX1(".bak", "application/x-VMS BAK File", "binary"); + SET_SUFFIX1(".bkp", "application/x-VMS BAK File", "binary"); + SET_SUFFIX1(".bck", "application/x-VMS BAK File", "binary"); + + SET_SUFFIX5(".bkp_gz", STR_BINARY, "gzip", "GNU BAK File"); + SET_SUFFIX5(".bkp-gz", STR_BINARY, "gzip", "GNU BAK File"); + SET_SUFFIX5(".bck_gz", STR_BINARY, "gzip", "GNU BAK File"); + SET_SUFFIX5(".bck-gz", STR_BINARY, "gzip", "GNU BAK File"); + + SET_SUFFIX5(".bkp-Z", STR_BINARY, "compress", "Comp. BAK File"); + SET_SUFFIX5(".bkp_Z", STR_BINARY, "compress", "Comp. BAK File"); + SET_SUFFIX5(".bck-Z", STR_BINARY, "compress", "Comp. BAK File"); + SET_SUFFIX5(".bck_Z", STR_BINARY, "compress", "Comp. BAK File"); +#else + HTSetSuffix5(".bak", NULL, "binary", "Backup", 0.5); + SET_SUFFIX5(".bkp", STR_BINARY, "binary", "VMS BAK File"); + SET_SUFFIX5(".bck", STR_BINARY, "binary", "VMS BAK File"); +#endif + +#if defined(TRADITIONAL_SUFFIXES) || defined(VMS) + SET_SUFFIX1(".hlb", "application/x-VMS Help Libr.", "binary"); + SET_SUFFIX1(".olb", "application/x-VMS Obj. Libr.", "binary"); + SET_SUFFIX1(".tlb", "application/x-VMS Text Libr.", "binary"); + SET_SUFFIX1(".obj", "application/x-VMS Prog. Obj.", "binary"); + SET_SUFFIX1(".decw$book", "application/x-DEC BookReader", "binary"); + SET_SUFFIX1(".mem", "application/x-RUNOFF-MANUAL", "8bit"); +#else + SET_SUFFIX5(".hlb", STR_BINARY, "binary", "VMS Help Libr."); + SET_SUFFIX5(".olb", STR_BINARY, "binary", "VMS Obj. Libr."); + SET_SUFFIX5(".tlb", STR_BINARY, "binary", "VMS Text Libr."); + SET_SUFFIX5(".obj", STR_BINARY, "binary", "Prog. Object"); + SET_SUFFIX5(".decw$book", STR_BINARY, "binary", "DEC BookReader"); + SET_SUFFIX5(".mem", "text/x-runoff-manual", "8bit", "RUNOFF-MANUAL"); +#endif + + SET_SUFFIX1(".vsd", "application/visio", "binary"); + + SET_SUFFIX5(".lha", "application/x-lha", "binary", "lha File"); + SET_SUFFIX5(".lzh", "application/x-lzh", "binary", "lzh File"); + SET_SUFFIX5(".sea", "application/x-sea", "binary", "sea File"); +#ifdef TRADITIONAL_SUFFIXES + SET_SUFFIX5(".sit", "application/x-sit", "binary", "sit File"); +#else + SET_SUFFIX5(".sit", "application/x-stuffit", "binary", "StuffIt"); +#endif + SET_SUFFIX5(".dms", "application/x-dms", "binary", "dms File"); + SET_SUFFIX5(".iff", "application/x-iff", "binary", "iff File"); + + SET_SUFFIX1(".bcpio", "application/x-bcpio", "binary"); + SET_SUFFIX1(".cpio", "application/x-cpio", "binary"); + +#ifdef TRADITIONAL_SUFFIXES + SET_SUFFIX1(".gtar", "application/x-gtar", "binary"); +#endif + + SET_SUFFIX1(".shar", "application/x-shar", "8bit"); + SET_SUFFIX1(".share", "application/x-share", "8bit"); + +#ifdef TRADITIONAL_SUFFIXES + SET_SUFFIX1(".sh", "application/x-sh", "8bit"); /* xtra */ +#endif + + SET_SUFFIX1(".sv4cpio", "application/x-sv4cpio", "binary"); + SET_SUFFIX1(".sv4crc", "application/x-sv4crc", "binary"); + + SET_SUFFIX5(".tar", "application/x-tar", "binary", "Tar File"); + SET_SUFFIX1(".ustar", "application/x-ustar", "binary"); + + SET_SUFFIX1(".snd", "audio/basic", "binary"); + SET_SUFFIX1(".au", "audio/basic", "binary"); + + SET_SUFFIX1(".aifc", "audio/x-aiff", "binary"); + SET_SUFFIX1(".aif", "audio/x-aiff", "binary"); + SET_SUFFIX1(".aiff", "audio/x-aiff", "binary"); + SET_SUFFIX1(".wav", "audio/x-wav", "binary"); + SET_SUFFIX1(".midi", "audio/midi", "binary"); + SET_SUFFIX1(".mod", "audio/mod", "binary"); + + SET_SUFFIX1(".gif", "image/gif", "binary"); + SET_SUFFIX1(".ief", "image/ief", "binary"); + SET_SUFFIX1(".jfif", "image/jpeg", "binary"); /* xtra */ + SET_SUFFIX1(".jfif-tbnl", "image/jpeg", "binary"); /* xtra */ + SET_SUFFIX1(".jpe", "image/jpeg", "binary"); + SET_SUFFIX1(".jpg", "image/jpeg", "binary"); + SET_SUFFIX1(".jpeg", "image/jpeg", "binary"); + SET_SUFFIX1(".tif", "image/tiff", "binary"); + SET_SUFFIX1(".tiff", "image/tiff", "binary"); + SET_SUFFIX1(".ham", "image/ham", "binary"); + SET_SUFFIX1(".ras", "image/x-cmu-rast", "binary"); + SET_SUFFIX1(".pnm", "image/x-portable-anymap", "binary"); + SET_SUFFIX1(".pbm", "image/x-portable-bitmap", "binary"); + SET_SUFFIX1(".pgm", "image/x-portable-graymap", "binary"); + SET_SUFFIX1(".ppm", "image/x-portable-pixmap", "binary"); + SET_SUFFIX1(".png", "image/png", "binary"); + SET_SUFFIX1(".rgb", "image/x-rgb", "binary"); + SET_SUFFIX1(".xbm", "image/x-xbitmap", "binary"); + SET_SUFFIX1(".xpm", "image/x-xpixmap", "binary"); + SET_SUFFIX1(".xwd", "image/x-xwindowdump", "binary"); + + SET_SUFFIX1(".rtx", "text/richtext", "8bit"); + SET_SUFFIX1(".tsv", "text/tab-separated-values", "8bit"); + SET_SUFFIX1(".etx", "text/x-setext", "8bit"); + + SET_SUFFIX1(".mpg", "video/mpeg", "binary"); + SET_SUFFIX1(".mpe", "video/mpeg", "binary"); + SET_SUFFIX1(".mpeg", "video/mpeg", "binary"); + SET_SUFFIX1(".mov", "video/quicktime", "binary"); + SET_SUFFIX1(".qt", "video/quicktime", "binary"); + SET_SUFFIX1(".avi", "video/x-msvideo", "binary"); + SET_SUFFIX1(".movie", "video/x-sgi-movie", "binary"); + SET_SUFFIX1(".mv", "video/x-sgi-movie", "binary"); + + SET_SUFFIX1(".mime", "message/rfc822", "8bit"); + + SET_SUFFIX1(".c", STR_PLAINTEXT, "8bit"); + SET_SUFFIX1(".cc", STR_PLAINTEXT, "8bit"); + SET_SUFFIX1(".c++", STR_PLAINTEXT, "8bit"); + SET_SUFFIX1(".css", STR_PLAINTEXT, "8bit"); + SET_SUFFIX1(".h", STR_PLAINTEXT, "8bit"); + SET_SUFFIX1(".pl", STR_PLAINTEXT, "8bit"); + SET_SUFFIX1(".text", STR_PLAINTEXT, "8bit"); + SET_SUFFIX1(".txt", STR_PLAINTEXT, "8bit"); + + SET_SUFFIX1(".php", STR_HTML, "8bit"); + SET_SUFFIX1(".php3", STR_HTML, "8bit"); + SET_SUFFIX1(".html3", STR_HTML, "8bit"); + SET_SUFFIX1(".ht3", STR_HTML, "8bit"); + SET_SUFFIX1(".phtml", STR_HTML, "8bit"); + SET_SUFFIX1(".shtml", STR_HTML, "8bit"); + SET_SUFFIX1(".sht", STR_HTML, "8bit"); + SET_SUFFIX1(".htmlx", STR_HTML, "8bit"); + SET_SUFFIX1(".htm", STR_HTML, "8bit"); + SET_SUFFIX1(".html", STR_HTML, "8bit"); + /* *INDENT-ON* */ + + } else { /* LYSuffixRules */ + /* + * Note that even .html -> text/html, .htm -> text/html are omitted if + * default maps are compiled in but then skipped because of a + * configuration file directive. Whoever changes the config file in + * this way can easily also add the SUFFIX rules there. - kw + */ + CTrace((tfp, + "HTFileInit: Skipping all default (HTInit) extension maps!\n")); + } /* LYSuffixRules */ + +#else /* BUILTIN_SUFFIX_MAPS */ + + CTrace((tfp, + "HTFileInit: Default (HTInit) extension maps not compiled in.\n")); + /* + * The following two are still used if BUILTIN_SUFFIX_MAPS was undefined. + * Without one of them, lynx would always need to have a mapping specified + * in a lynx.cfg or mime.types file to be usable for local HTML files at + * all. That includes many of the generated user interface pages. - kw + */ + SET_SUFFIX1(".htm", STR_HTML, "8bit"); + SET_SUFFIX1(".html", STR_HTML, "8bit"); +#endif /* BUILTIN_SUFFIX_MAPS */ + + if (LYisAbsPath(global_extension_map)) { + /* These should override the default extensions as necessary. */ + HTLoadExtensionsConfigFile(global_extension_map); + } + + /* + * Load the local maps. + */ + if (IsOurFile(LYAbsOrHomePath(&personal_extension_map)) + && LYCanReadFile(personal_extension_map)) { + /* These should override everything else. */ + HTLoadExtensionsConfigFile(personal_extension_map); + } +} + +/* -------------------- Extension config file reading --------------------- */ + +/* + * The following is lifted from NCSA httpd 1.0a1, by Rob McCool; + * NCSA httpd is in the public domain, as is this code. + * + * Modified Oct 97 - KW + */ + +#define MAX_STRING_LEN 256 + +static int HTGetLine(char *s, int n, FILE *f) +{ + register int i = 0, r; + + if (!f) + return (1); + + while (1) { + r = fgetc(f); + s[i] = (char) r; + + if (s[i] == CR) { + r = fgetc(f); + if (r == LF) + s[i] = (char) r; + else if (r != EOF) + ungetc(r, f); + } + + if ((r == EOF) || (s[i] == LF) || (s[i] == CR) || (i == (n - 1))) { + s[i] = '\0'; + return (feof(f) ? 1 : 0); + } + ++i; + } +} + +static void HTGetWord(char *word, char *line, int stop, int stop2) +{ + int x = 0, y; + + for (x = 0; (line[x] + && UCH(line[x]) != UCH(stop) + && UCH(line[x]) != UCH(stop2)); x++) { + word[x] = line[x]; + } + + word[x] = '\0'; + if (line[x]) + ++x; + y = 0; + + while ((line[y++] = line[x++])) { + ; + } + + return; +} + +static int HTLoadExtensionsConfigFile(char *fn) +{ + char line[MAX_STRING_LEN]; + char word[MAX_STRING_LEN]; + char *ct; + FILE *f; + int count = 0; + + CTrace((tfp, "HTLoadExtensionsConfigFile: Loading file '%s'.\n", fn)); + + if ((f = fopen(fn, TXT_R)) == NULL) { + CTrace((tfp, "HTLoadExtensionsConfigFile: Could not open '%s'.\n", fn)); + return count; + } + + while (!(HTGetLine(line, (int) sizeof(line), f))) { + HTGetWord(word, line, ' ', '\t'); + if (line[0] == '\0' || word[0] == '#') + continue; + ct = NULL; + StrAllocCopy(ct, word); + LYLowerCase(ct); + + while (line[0]) { + HTGetWord(word, line, ' ', '\t'); + if (word[0] && (word[0] != ' ')) { + char *ext = NULL; + + HTSprintf0(&ext, ".%s", word); + LYLowerCase(ext); + + CTrace((tfp, "setting suffix '%s' to '%s'.\n", ext, ct)); + + if (strstr(ct, "tex") != NULL || + strstr(ct, "postscript") != NULL || + strstr(ct, "sh") != NULL || + strstr(ct, "troff") != NULL || + strstr(ct, "rtf") != NULL) + SET_SUFFIX1(ext, ct, "8bit"); + else + SET_SUFFIX1(ext, ct, "binary"); + count++; + + FREE(ext); + } + } + FREE(ct); + } + LYCloseInput(f); + + return count; +} diff --git a/src/HTML.c b/src/HTML.c new file mode 100644 index 0000000..5c57a07 --- /dev/null +++ b/src/HTML.c @@ -0,0 +1,8198 @@ +/* + * $LynxId: HTML.c,v 1.200 2022/07/22 20:22:13 tom Exp $ + * + * Structured stream to Rich hypertext converter + * ============================================ + * + * This generates a hypertext object. It converts from the + * structured stream interface of HTML events into the style- + * oriented interface of the HText.h interface. This module is + * only used in clients and should not be linked into servers. + * + * Override this module if making a new GUI browser. + * + * Being Overridden + * + */ + +#define HTSTREAM_INTERNAL 1 + +#include + +#define Lynx_HTML_Handler +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef VMS +#include +#endif /* VMS */ + +#ifdef USE_PRETTYSRC +#include +#endif + +#ifdef USE_COLOR_STYLE +#include +#include +#include +#include +#undef SELECTED_STYLES +#define pHText_changeStyle(X,Y,Z) {} +#endif /* USE_COLOR_STYLE */ + +#ifdef USE_SOURCE_CACHE +#include +#endif + +#include +#include + +#include +#include + +#define STACKLEVEL(me) ((me->stack + MAX_NESTING - 1) - me->sp) + +#define DFT_TEXTAREA_COLS 60 +#define DFT_TEXTAREA_ROWS 4 + +#define MAX_TEXTAREA_COLS LYcolLimit +#define MAX_TEXTAREA_ROWS (3 * LYlines) + +#define LimitValue(name, value) \ + if (name > value) { \ + CTRACE((tfp, "Limited " #name " to %d, was %d\n", \ + value, name)); \ + name = value; \ + } + +struct _HTStream { + const HTStreamClass *isa; +#ifdef USE_SOURCE_CACHE + HTParentAnchor *anchor; + FILE *fp; + char *filename; + HTChunk *chunk; + HTChunk *last_chunk; /* the last chunk in a chain! */ + const HTStreamClass *actions; + HTStream *target; + int status; +#else + /* .... */ +#endif +}; + +static HTStyleSheet *styleSheet = NULL; /* Application-wide */ + +/* Module-wide style cache +*/ +static HTStyle *styles[HTML_ELEMENTS + LYNX_HTML_EXTRA_ELEMENTS]; + + /* adding 24 nested list styles */ + /* and 3 header alignment styles */ + /* and 3 div alignment styles */ +static HTStyle *default_style = NULL; + +const char *LYToolbarName = "LynxPseudoToolbar"; + +/* used to turn off a style if the HTML author forgot to +static int i_prior_style = -1; + */ + +/* + * Private function.... + */ +static int HTML_end_element(HTStructured * me, int element_number, + char **include); + +static int HTML_start_element(HTStructured * me, int element_number, + const BOOL *present, + STRING2PTR value, + int tag_charset, + char **include); + +/* + * If we have verbose_img set, display labels for images. + */ +#define VERBOSE_IMG(value,src_type,string) \ + ((verbose_img) ? (newtitle = MakeNewTitle(value,src_type)): string) + +static char *MakeNewTitle(STRING2PTR value, int src_type); +static char *MakeNewImageValue(STRING2PTR value); +static char *MakeNewMapValue(STRING2PTR value, const char *mapstr); + +/* Set an internal flag that the next call to a stack-affecting method + * is only internal and the stack manipulation should be skipped. - kw + */ +#define SET_SKIP_STACK(el_num) if (HTML_dtd.tags[el_num].contents != SGML_EMPTY) \ + { me->skip_stack++; } + +void strtolower(char *i) +{ + if (!i) + return; + while (*i) { + *i = (char) TOLOWER(*i); + i++; + } +} + +/* Flattening the style structure + * ------------------------------ + * + * On the NeXT, and on any read-only browser, it is simpler for the text to + * have a sequence of styles, rather than a nested tree of styles. In this + * case we have to flatten the structure as it arrives from SGML tags into a + * sequence of styles. + */ + +/* + * If style really needs to be set, call this. + */ +void actually_set_style(HTStructured * me) +{ + if (!me->text) { /* First time through */ + LYGetChartransInfo(me); + UCSetTransParams(&me->T, + me->UCLYhndl, me->UCI, + HTAnchor_getUCLYhndl(me->node_anchor, + UCT_STAGE_HTEXT), + HTAnchor_getUCInfoStage(me->node_anchor, + UCT_STAGE_HTEXT)); + me->text = HText_new2(me->node_anchor, me->target); + HText_beginAppend(me->text); + HText_setStyle(me->text, me->new_style); + me->in_word = NO; + LYCheckForContentBase(me); + } else { + HText_setStyle(me->text, me->new_style); + } + + me->old_style = me->new_style; + me->style_change = NO; +} + +/* + * If you THINK you need to change style, call this. + */ +static void change_paragraph_style(HTStructured * me, HTStyle *style) +{ + if (me->new_style != style) { + me->style_change = YES; + me->new_style = style; + } + me->in_word = NO; +} + +/* + * Return true if we should write a message (to LYNXMESSAGES, or the trace + * file) telling about some bad HTML that we've found. + */ +BOOL LYBadHTML(HTStructured * me) +{ + BOOL code = FALSE; + + switch ((enumBadHtml) cfg_bad_html) { + case BAD_HTML_IGNORE: + break; + case BAD_HTML_TRACE: + code = TRUE; + break; + case BAD_HTML_MESSAGE: + code = TRUE; + break; + case BAD_HTML_WARN: + /* + * If we're already tracing, do not add a warning. + */ + if (!TRACE && !me->inBadHTML) { + HTUserMsg(BAD_HTML_USE_TRACE); + me->inBadHTML = TRUE; + } + code = TRACE; + break; + } + return code; +} + +/* + * Handle the formatted message. + */ +void LYShowBadHTML(const char *message) +{ + if (dump_output_immediately && dump_to_stderr) + fprintf(stderr, "%s", message); + + switch ((enumBadHtml) cfg_bad_html) { + case BAD_HTML_IGNORE: + break; + case BAD_HTML_TRACE: + case BAD_HTML_MESSAGE: + case BAD_HTML_WARN: + CTRACE((tfp, "%s", message)); + break; + } + + switch ((enumBadHtml) cfg_bad_html) { + case BAD_HTML_IGNORE: + case BAD_HTML_TRACE: + case BAD_HTML_WARN: + break; + case BAD_HTML_MESSAGE: + LYstore_message(message); + break; + } +} + +/*_________________________________________________________________________ + * + * A C T I O N R O U T I N E S + */ + +/* Character handling + * ------------------ + */ +void HTML_put_character(HTStructured * me, int c) +{ + unsigned uc = UCH(c); + + /* + * Ignore all non-MAP content when just scanning a document for MAPs. - FM + */ + if (LYMapsOnly && me->sp[0].tag_number != HTML_OBJECT) + return; + + c = (int) uc; + + /* + * Do EOL conversion if needed. - FM + * + * Convert EOL styles: + * macintosh: cr --> lf + * ascii: cr-lf --> lf + * unix: lf --> lf + */ + if ((me->lastraw == '\r') && c == '\n') { + me->lastraw = -1; + return; + } + me->lastraw = c; + if (c == '\r') { + c = '\n'; + uc = UCH(c); + } + + /* + * Handle SGML_LITTERAL tags that have HTChunk elements. - FM + */ + switch (me->sp[0].tag_number) { + + case HTML_COMMENT: + return; /* Do Nothing */ + + case HTML_TITLE: + if (c == LY_SOFT_HYPHEN) + return; + if (c != '\n' && c != '\t' && c != '\r') { + HTChunkPutc(&me->title, uc); +#ifdef EXP_JAPANESE_SPACES + } else if (c == '\t') { + HTChunkPutc(&me->title, ' '); + /* don't replace '\n' with ' ' if Chinese or Japanese - HN */ + } else if (me->title.size > 0 && + is8bits(me->title.data[me->title.size - 1])) { + if (HTCJK == CHINESE || HTCJK == JAPANESE) { + /* TODO: support 2nd byte of SJIS (!is8bits && IS_SJIS_LO) */ + return; + } else if (IS_UTF8_TTY) { + /* find start position of UTF-8 sequence */ + int i = me->title.size - 1; + + while (i > 0 && (me->title.data[i] & 0xc0) == 0x80) /* UTF_EXTRA */ + i--; + if (isUTF8CJChar(&(me->title.data[i]))) + return; + } + HTChunkPutc(&me->title, ' '); +#endif + } else { + HTChunkPutc(&me->title, ' '); + } + return; + + case HTML_STYLE: + HTChunkPutc(&me->style_block, uc); + return; + + case HTML_SCRIPT: + HTChunkPutc(&me->script, uc); + return; + + case HTML_OBJECT: + HTChunkPutc(&me->object, uc); + return; + + case HTML_TEXTAREA: + HTChunkPutc(&me->textarea, uc); + return; + + case HTML_SELECT: + case HTML_OPTION: + HTChunkPutc(&me->option, uc); + return; + + case HTML_MATH: + HTChunkPutc(&me->math, uc); + return; + + default: + if (me->inSELECT) { + /* + * If we are within a SELECT not caught by the cases above - + * HTML_SELECT or HTML_OPTION may not be the last element pushed on + * the style stack if there were invalid markup tags within a + * SELECT element. For error recovery, treat text as part of the + * OPTION text, it is probably meant to show up as user-visible + * text. Having A as an open element while in SELECT is really + * sick, don't make anchor text part of the option text in that + * case since the option text will probably just be discarded. - + * kw + */ + if (me->sp[0].tag_number == HTML_A) + break; + HTChunkPutc(&me->option, uc); + return; + } + break; + } /* end first switch */ + + /* + * Handle all other tag content. - FM + */ + switch (me->sp[0].tag_number) { + + case HTML_PRE: /* Formatted text */ + /* + * We guarantee that the style is up-to-date in begin_litteral. But we + * still want to strip \r's. + */ + if (c != '\r' && + !(c == '\n' && me->inLABEL && !me->inP) && + !(c == '\n' && !me->inPRE)) { + me->inP = TRUE; + me->inLABEL = FALSE; + HText_appendCharacter(me->text, c); + } + me->inPRE = TRUE; + break; + + case HTML_LISTING: /* Literal text */ + case HTML_XMP: + case HTML_PLAINTEXT: + /* + * We guarantee that the style is up-to-date in begin_litteral. But we + * still want to strip \r's. + */ + if (c != '\r') { + me->inP = TRUE; + me->inLABEL = FALSE; + HText_appendCharacter(me->text, c); + } + break; + + default: + /* + * Free format text. + */ + if (me->sp->style->id == ST_Preformatted) { + if (c != '\r' && + !(c == '\n' && me->inLABEL && !me->inP) && + !(c == '\n' && !me->inPRE)) { + me->inP = TRUE; + me->inLABEL = FALSE; + HText_appendCharacter(me->text, c); + } + me->inPRE = TRUE; + + } else if (me->sp->style->id == ST_Listing || + me->sp->style->id == ST_Example) { + if (c != '\r') { + me->inP = TRUE; + me->inLABEL = FALSE; + HText_appendCharacter(me->text, c); + } + + } else { + if (me->style_change) { + if ((c == '\n') || (c == ' ')) + return; /* Ignore it */ + UPDATE_STYLE; + } + if (c == '\n') { + if (me->in_word) { +#ifdef EXP_JAPANESE_SPACES + if (HText_checkLastChar_needSpaceOnJoinLines(me->text)) { +#else + if (HText_getLastChar(me->text) != ' ') { +#endif + me->inP = TRUE; + me->inLABEL = FALSE; + HText_appendCharacter(me->text, ' '); + } + me->in_word = NO; + } + + } else if (c == ' ' || c == '\t') { + if (HText_getLastChar(me->text) != ' ') { + me->inP = TRUE; + me->inLABEL = FALSE; + HText_appendCharacter(me->text, ' '); + } + + } else if (c == '\r') { + /* ignore */ + + } else { + me->inP = TRUE; + me->inLABEL = FALSE; + HText_appendCharacter(me->text, c); + me->in_word = YES; + } + } + } /* end second switch */ + + if (c == '\n' || c == '\t') { + HText_setLastChar(me->text, ' '); /* set it to a generic separator */ + } else { + HText_setLastChar(me->text, c); + } +} + +/* String handling + * --------------- + * + * This is written separately from put_character because the loop can + * in some cases be promoted to a higher function call level for speed. + */ +void HTML_put_string(HTStructured * me, const char *s) +{ + HTChunk *target = NULL; + +#ifdef USE_PRETTYSRC + char *translated_string = NULL; +#endif + + if (s == NULL || (LYMapsOnly && me->sp[0].tag_number != HTML_OBJECT)) + return; +#ifdef USE_PRETTYSRC + if (psrc_convert_string) { + StrAllocCopy(translated_string, s); + TRANSLATE_AND_UNESCAPE_ENTITIES(&translated_string, TRUE, FALSE); + s = (const char *) translated_string; + } +#endif + + switch (me->sp[0].tag_number) { + + case HTML_COMMENT: + break; /* Do Nothing */ + + case HTML_TITLE: + target = &me->title; + break; + + case HTML_STYLE: + target = &me->style_block; + break; + + case HTML_SCRIPT: + target = &me->script; + break; + + case HTML_PRE: /* Formatted text */ + case HTML_LISTING: /* Literal text */ + case HTML_XMP: + case HTML_PLAINTEXT: + /* + * We guarantee that the style is up-to-date in begin_litteral + */ + HText_appendText(me->text, s); + break; + + case HTML_OBJECT: + target = &me->object; + break; + + case HTML_TEXTAREA: + target = &me->textarea; + break; + + case HTML_SELECT: + case HTML_OPTION: + target = &me->option; + break; + + case HTML_MATH: + target = &me->math; + break; + + default: /* Free format text? */ + if (!me->sp->style->freeFormat) { + /* + * If we are within a preformatted text style not caught by the + * cases above (HTML_PRE or similar may not be the last element + * pushed on the style stack). - kw + */ +#ifdef USE_PRETTYSRC + if (psrc_view) { + /* + * We do this so that a raw '\r' in the string will not be + * interpreted as an internal request to break a line - passing + * '\r' to HText_appendText is treated by it as a request to + * insert a blank line - VH + */ + for (; *s; ++s) + HTML_put_character(me, *s); + } else +#endif + HText_appendText(me->text, s); + break; + } else { + const char *p = s; + char c; + + if (me->style_change) { + for (; *p && ((*p == '\n') || (*p == '\r') || + (*p == ' ') || (*p == '\t')); p++) ; /* Ignore leaders */ + if (!*p) + break; + UPDATE_STYLE; + } + for (; *p; p++) { + if (*p == 13 && p[1] != 10) { + /* + * Treat any '\r' which is not followed by '\n' as '\n', to + * account for macintosh lineend in ALT attributes etc. - + * kw + */ + c = '\n'; + } else { + c = *p; + } + if (me->style_change) { + if ((c == '\n') || (c == ' ') || (c == '\t')) + continue; /* Ignore it */ + UPDATE_STYLE; + } + if (c == '\n') { + if (me->in_word) { +#ifdef EXP_JAPANESE_SPACES + if (HText_checkLastChar_needSpaceOnJoinLines(me->text)) +#else + if (HText_getLastChar(me->text) != ' ') +#endif + HText_appendCharacter(me->text, ' '); + me->in_word = NO; + } + + } else if (c == ' ' || c == '\t') { + if (HText_getLastChar(me->text) != ' ') + HText_appendCharacter(me->text, ' '); + + } else if (c == '\r') { + /* ignore */ + } else { + HText_appendCharacter(me->text, c); + me->in_word = YES; + } + + /* set the Last Character */ + if (c == '\n' || c == '\t') { + /* set it to a generic separator */ + HText_setLastChar(me->text, ' '); + } else if (c == '\r' && + HText_getLastChar(me->text) == ' ') { + /* + * \r's are ignored. In order to keep collapsing spaces + * correctly, we must default back to the previous + * separator, if there was one. So we set LastChar to a + * generic separator. + */ + HText_setLastChar(me->text, ' '); + } else { + HText_setLastChar(me->text, c); + } + + } /* for */ + } + } /* end switch */ + + if (target != NULL) { + if (target->data == s) { + CTRACE((tfp, "BUG: appending chunk to itself: `%.*s'\n", + target->size, target->data)); + } else { + HTChunkPuts(target, s); + } + } +#ifdef USE_PRETTYSRC + if (psrc_convert_string) { + psrc_convert_string = FALSE; + FREE(translated_string); + } +#endif +} + +/* Buffer write + * ------------ + */ +void HTML_write(HTStructured * me, const char *s, int l) +{ + const char *p; + const char *e = s + l; + + if (LYMapsOnly && me->sp[0].tag_number != HTML_OBJECT) + return; + + for (p = s; p < e; p++) + HTML_put_character(me, *p); +} + +/* + * "Internal links" are hyperlinks whose source and destination are + * within the same document, and for which the destination is given + * as a URL Reference with an empty URL, but possibly with a non-empty + * #fragment. (This terminology re URL-Reference vs. URL follows the + * Fielding URL syntax and semantics drafts). + * Differences: + * (1) The document's base (in whatever way it is given) is not used for + * resolving internal link references. + * (2) Activating an internal link should not result in a new retrieval + * of a copy of the document. + * (3) Internal links are the only way to refer with a hyperlink to a document + * (or a location in it) which is only known as the result of a POST + * request (doesn't have a URL from which the document can be retrieved + * with GET), and can only be used from within that document. + * + * *If track_internal_links is true, we keep track of whether a + * link destination was given as an internal link. This information is + * recorded in the type of the link between anchor objects, and is available + * to the HText object and the mainloop from there. URL References to + * internal destinations are still resolved into an absolute form before + * being passed on, but using the current stream's retrieval address instead + * of the base URL. + * Examples: (replace [...] to have a valid absolute URL) + * In document retrieved from [...]/mypath/mydoc.htm w/ base [...]/otherpath/ + * a. HREF="[...]/mypath/mydoc.htm" -> [...]/mypath/mydoc.htm + * b. HREF="[...]/mypath/mydoc.htm#frag" -> [...]/mypath/mydoc.htm#frag + * c. HREF="mydoc.htm" -> [...]/otherpath/mydoc.htm + * d. HREF="mydoc.htm#frag" -> [...]/otherpath/mydoc.htm#frag + * e. HREF="" -> [...]/mypath/mydoc.htm (marked internal) + * f. HREF="#frag" -> [...]/mypath/mydoc.htm#frag (marked internal) + * + * *If track_internal_links is false, URL-less URL-References are + * resolved differently from URL-References with a non-empty URL (using the + * current stream's retrieval address instead of the base), but we make no + * further distinction. Resolution is then as in the examples above, execept + * that there is no "(marked internal)". + * + * *Note that this doesn't apply to form ACTIONs (always resolved using base, + * never marked internal). Also other references encountered or generated + * are not marked internal, whether they have a URL or not, if in a given + * context an internal link makes no sense (e.g., IMG SRC=). + */ + +/* A flag is used to keep track of whether an "URL reference" encountered + had a real "URL" or not. In the latter case, it will be marked as + "internal". The flag is set before we start messing around with the + string (resolution of relative URLs etc.). This variable only used + locally here, don't confuse with LYinternal_flag which is for + overriding non-caching similar to LYoverride_no_cache. - kw */ +#define CHECK_FOR_INTERN(flag,s) \ + flag = (BOOLEAN) (((s) && (*(s)=='#' || *(s)=='\0')) ? TRUE : FALSE) + +/* Last argument to pass to HTAnchor_findChildAndLink() calls, + just an abbreviation. - kw */ +#define INTERN_CHK(flag) (HTLinkType *)((flag) ? HTInternalLink : NULL) +#define INTERN_LT INTERN_CHK(intern_flag) + +#ifdef USE_COLOR_STYLE +static char *Style_className = 0; +static char *Style_className_end = 0; +static size_t Style_className_len = 0; +static int hcode; + +#ifdef LY_FIND_LEAKS +static void free_Style_className(void) +{ + FREE(Style_className); +} +#endif + +static void addClassName(const char *prefix, + const char *actual, + size_t length) +{ + size_t offset = strlen(prefix); + size_t have = (unsigned) (Style_className_end - Style_className); + size_t need = (offset + length + 1); + + if ((have + need) >= Style_className_len) { + Style_className_len += 1024 + 2 * (have + need); + if (Style_className == 0) { + Style_className = typeMallocn(char, Style_className_len); + } else { + Style_className = typeRealloc(char, Style_className, Style_className_len); + } + if (Style_className == NULL) + outofmem(__FILE__, "addClassName"); + Style_className_end = Style_className + have; + } + if (offset) + strcpy(Style_className_end, prefix); + if (length) + memcpy(Style_className_end + offset, actual, length); + Style_className_end[offset + length] = '\0'; + strtolower(Style_className_end); + + Style_className_end += (offset + length); +} +#else +#define addClassName(prefix, actual, length) /* nothing */ +#endif + +static void LYStartArea(HTStructured * obj, const char *href, + const char *alt, + const char *title, + int tag_charset) +{ + BOOL new_present[HTML_AREA_ATTRIBUTES]; + const char *new_value[HTML_AREA_ATTRIBUTES]; + int i; + + for (i = 0; i < HTML_AREA_ATTRIBUTES; i++) + new_present[i] = NO; + + if (alt) { + new_present[HTML_AREA_ALT] = YES; + new_value[HTML_AREA_ALT] = (const char *) alt; + } + if (non_empty(title)) { + new_present[HTML_AREA_TITLE] = YES; + new_value[HTML_AREA_TITLE] = (const char *) title; + } + if (href) { + new_present[HTML_AREA_HREF] = YES; + new_value[HTML_AREA_HREF] = (const char *) href; + } + + (*obj->isa->start_element) (obj, HTML_AREA, new_present, new_value, + tag_charset, 0); +} + +static void LYHandleFIG(HTStructured * me, const BOOL *present, + STRING2PTR value, + int isobject, + int imagemap, + const char *id, + const char *src, + int convert, + int start, + BOOL *intern_flag GCC_UNUSED) +{ + if (start == TRUE) { + me->inFIG = TRUE; + if (me->inA) { + SET_SKIP_STACK(HTML_A); + HTML_end_element(me, HTML_A, NULL); + } + if (!isobject) { + LYEnsureDoubleSpace(me); + LYResetParagraphAlignment(me); + me->inFIGwithP = TRUE; + } else { + me->inFIGwithP = FALSE; + HTML_put_character(me, ' '); /* space char may be ignored */ + } + if (non_empty(id)) { + if (present && convert) { + CHECK_ID(HTML_FIG_ID); + } else + LYHandleID(me, id); + } + me->in_word = NO; + me->inP = FALSE; + + if (clickable_images && non_empty(src)) { + char *href = NULL; + + StrAllocCopy(href, src); + CHECK_FOR_INTERN(*intern_flag, href); + LYLegitimizeHREF(me, &href, TRUE, TRUE); + if (*href) { + me->CurrentA = HTAnchor_findChildAndLink(me->node_anchor, /* Parent */ + NULL, /* Tag */ + href, /* Address */ + INTERN_CHK(*intern_flag)); /* Type */ + HText_beginAnchor(me->text, me->inUnderline, me->CurrentA); + if (me->inBoldH == FALSE) + HText_appendCharacter(me->text, LY_BOLD_START_CHAR); + HTML_put_string(me, (isobject + ? (imagemap + ? "(IMAGE)" + : "(OBJECT)") + : "[FIGURE]")); + if (me->inBoldH == FALSE) + HText_appendCharacter(me->text, LY_BOLD_END_CHAR); + HText_endAnchor(me->text, 0); + HTML_put_character(me, '-'); + HTML_put_character(me, ' '); /* space char may be ignored */ + me->in_word = NO; + } + FREE(href); + } + } else { /* handle end tag */ + if (me->inFIGwithP) { + LYEnsureDoubleSpace(me); + } else { + HTML_put_character(me, ' '); /* space char may be ignored */ + } + LYResetParagraphAlignment(me); + me->inFIGwithP = FALSE; + me->inFIG = FALSE; + change_paragraph_style(me, me->sp->style); /* Often won't really change */ + if (me->List_Nesting_Level >= 0) { + UPDATE_STYLE; + HText_NegateLineOne(me->text); + } + } +} + +static void clear_objectdata(HTStructured * me) +{ + if (me) { + HTChunkClear(&me->object); + me->object_started = FALSE; + me->object_declare = FALSE; + me->object_shapes = FALSE; + me->object_ismap = FALSE; + FREE(me->object_usemap); + FREE(me->object_id); + FREE(me->object_title); + FREE(me->object_data); + FREE(me->object_type); + FREE(me->object_classid); + FREE(me->object_codebase); + FREE(me->object_codetype); + FREE(me->object_name); + } +} + +#define HTParseALL(pp,pconst) \ + { char* free_me = *pp; \ + *pp = HTParse(*pp, pconst, PARSE_ALL); \ + FREE(free_me); \ + } + +/* Start Element + * ------------- + */ +static int HTML_start_element(HTStructured * me, int element_number, + const BOOL *present, + STRING2PTR value, + int tag_charset, + char **include) +{ + char *alt_string = NULL; + char *id_string = NULL; + char *newtitle = NULL; + char **pdoctitle = NULL; + char *href = NULL; + char *map_href = NULL; + char *title = NULL; + char *I_value = NULL; + char *I_name = NULL; + char *temp = NULL; + const char *Base = NULL; + int dest_char_set = -1; + HTParentAnchor *dest = NULL; /* An anchor's destination */ + BOOL dest_ismap = FALSE; /* Is dest an image map script? */ + HTChildAnchor *ID_A = NULL; /* HTML_foo_ID anchor */ + int url_type = 0, i = 0; + char *cp = NULL; + HTMLElement ElementNumber = (HTMLElement) element_number; + BOOL intern_flag = FALSE; + short stbl_align = HT_ALIGN_NONE; + int status = HT_OK; + +#ifdef USE_COLOR_STYLE + const char *class_name; + const char *prefix_string; + BOOL class_used = FALSE; +#endif + + if (LYMapsOnly) { + if (!(ElementNumber == HTML_MAP || ElementNumber == HTML_AREA || + ElementNumber == HTML_BASE || ElementNumber == HTML_OBJECT || + ElementNumber == HTML_A)) { + return HT_OK; + } + } else if (!me->text) { + UPDATE_STYLE; + } { + /* me->tag_charset is charset for attribute values. */ + int j = ((tag_charset < 0) ? me->UCLYhndl : tag_charset); + + if ((me->tag_charset != j) || (j < 0 /* for trace entry */ )) { + CTRACE((tfp, "me->tag_charset: %d -> %d", me->tag_charset, j)); + CTRACE((tfp, " (me->UCLYhndl: %d, tag_charset: %d)\n", + me->UCLYhndl, tag_charset)); + me->tag_charset = j; + } + } + +/* this should be done differently */ +#if defined(USE_COLOR_STYLE) + + addClassName(";", + HTML_dtd.tags[element_number].name, + (size_t) HTML_dtd.tags[element_number].name_len); + + class_name = (force_classname ? forced_classname : class_string); + force_classname = FALSE; + + if (force_current_tag_style == FALSE) { + current_tag_style = (non_empty(class_name) + ? -1 + : cached_tag_styles[element_number]); + } else { + force_current_tag_style = FALSE; + } + + CTRACE2(TRACE_STYLE, (tfp, "CSS.elt:<%s>\n", HTML_dtd.tags[element_number].name)); + + prefix_string = ""; + if (current_tag_style == -1) { /* Append class_name */ + hcode = color_style_1(HTML_dtd.tags[element_number].name); + if (non_empty(class_name)) { + int ohcode = hcode; + + prefix_string = HTML_dtd.tags[element_number].name; + hcode = color_style_3(prefix_string, ".", class_name); + if (!hashStyles[hcode].used) { /* None such -> classless version */ + hcode = ohcode; + prefix_string = ""; + CTRACE2(TRACE_STYLE, + (tfp, + "STYLE.start_element: <%s> (class <%s> not configured), hcode=%d.\n", + HTML_dtd.tags[element_number].name, class_name, hcode)); + } else { + addClassName(".", class_name, strlen(class_name)); + + CTRACE2(TRACE_STYLE, + (tfp, "STYLE.start_element: <%s>.<%s>, hcode=%d.\n", + prefix_string, class_name, hcode)); + class_used = TRUE; + } + } + + class_string[0] = '\0'; + + } else { /* (current_tag_style!=-1) */ + if (non_empty(class_name)) { + addClassName(".", class_name, strlen(class_name)); + class_string[0] = '\0'; + } + hcode = current_tag_style; + if (hcode >= 0 && hashStyles[hcode].used) { + prefix_string = hashStyles[hcode].name; + } + CTRACE2(TRACE_STYLE, + (tfp, "STYLE.start_element: <%s>, hcode=%d.\n", + HTML_dtd.tags[element_number].name, hcode)); + current_tag_style = -1; + } + + if (!class_used && ElementNumber == HTML_INPUT) { /* For some other too? */ + const char *type = ""; + int ohcode = hcode; + + if (present && present[HTML_INPUT_TYPE] && value[HTML_INPUT_TYPE]) + type = value[HTML_INPUT_TYPE]; + + hcode = color_style_3(prefix_string, ".type.", type); + if (!hashStyles[hcode].used) { /* None such -> classless version */ + hcode = ohcode; + CTRACE2(TRACE_STYLE, + (tfp, "STYLE.start_element: type <%s> not configured.\n", + type)); + } else { + addClassName(".type.", type, strlen(type)); + + CTRACE2(TRACE_STYLE, + (tfp, "STYLE.start_element: <%s>.type.<%s>, hcode=%d.\n", + HTML_dtd.tags[element_number].name, type, hcode)); + } + } + + HText_characterStyle(me->text, hcode, STACK_ON); +#endif /* USE_COLOR_STYLE */ + + /* + * Handle the start tag. - FM + */ + switch (ElementNumber) { + + case HTML_HTML: + break; + + case HTML_HEAD: + break; + + case HTML_BASE: + if (present && present[HTML_BASE_HREF] && !local_host_only && + non_empty(value[HTML_BASE_HREF])) { + char *base = NULL; + const char *related = NULL; + + StrAllocCopy(base, value[HTML_BASE_HREF]); + CTRACE((tfp, "*HTML_BASE: initial href=`%s'\n", NonNull(base))); + + if (!(url_type = LYLegitimizeHREF(me, &base, TRUE, TRUE))) { + CTRACE((tfp, "HTML: BASE '%s' is not an absolute URL.\n", + NonNull(base))); + } + + if (url_type == LYNXIMGMAP_URL_TYPE) { + /* + * These have a non-standard form, basically strip the prefix + * or the code below would insert a nonsense host into the + * pseudo URL. These should never occur where they would be + * used for resolution of relative URLs anyway. We can also + * strip the #map part. - kw + */ + temp = base; + base = HTParse(base + 11, "", PARSE_ALL_WITHOUT_ANCHOR); + FREE(temp); + } + + /* + * Get parent's address for defaulted fields. + */ + related = me->node_anchor->address; + + /* + * Create the access field. + */ + temp = HTParse(base, related, PARSE_ACCESS + PARSE_PUNCTUATION); + StrAllocCopy(me->base_href, temp); + FREE(temp); + + /* + * Create the host[:port] field. + */ + temp = HTParse(base, "", PARSE_HOST + PARSE_PUNCTUATION); + if (!StrNCmp(temp, "//", 2)) { + StrAllocCat(me->base_href, temp); + if (!strcmp(me->base_href, "file://")) { + StrAllocCat(me->base_href, "localhost"); + } + } else { + if (isFILE_URL(me->base_href)) { + StrAllocCat(me->base_href, "//localhost"); + } else if (strcmp(me->base_href, STR_NEWS_URL)) { + FREE(temp); + StrAllocCat(me->base_href, (temp = HTParse(related, "", + PARSE_HOST + PARSE_PUNCTUATION))); + } + } + FREE(temp); + + /* + * Create the path field. + */ + temp = HTParse(base, "", PARSE_PATH + PARSE_PUNCTUATION); + if (*temp != '\0') { + char *p = StrChr(temp, '?'); + + if (p) + *p = '\0'; + p = strrchr(temp, '/'); + if (p) + *(p + 1) = '\0'; /* strip after the last slash */ + + StrAllocCat(me->base_href, temp); + } else if (!strcmp(me->base_href, STR_NEWS_URL)) { + StrAllocCat(me->base_href, "*"); + } else if (isNEWS_URL(me->base_href) || + isNNTP_URL(me->base_href) || + isSNEWS_URL(me->base_href)) { + StrAllocCat(me->base_href, "/*"); + } else { + StrAllocCat(me->base_href, "/"); + } + FREE(temp); + FREE(base); + + me->inBASE = TRUE; + me->node_anchor->inBASE = TRUE; + StrAllocCopy(me->node_anchor->content_base, me->base_href); + /* me->base_href is a valid URL */ + + CTRACE((tfp, "*HTML_BASE: final href=`%s'\n", me->base_href)); + } + break; + + case HTML_META: + if (present) + LYHandleMETA(me, present, value, include); + break; + + case HTML_TITLE: + HTChunkClear(&me->title); + break; + + case HTML_LINK: + intern_flag = FALSE; + if (present && present[HTML_LINK_HREF]) { + CHECK_FOR_INTERN(intern_flag, value[HTML_LINK_HREF]); + /* + * Prepare to do housekeeping on the reference. - FM + */ + if (isEmpty(value[HTML_LINK_HREF])) { + Base = (me->inBASE) + ? me->base_href + : me->node_anchor->address; + StrAllocCopy(href, Base); + } else { + StrAllocCopy(href, value[HTML_LINK_HREF]); + (void) LYLegitimizeHREF(me, &href, TRUE, TRUE); + + Base = (me->inBASE && *href != '\0' && *href != '#') + ? me->base_href + : me->node_anchor->address; + HTParseALL(&href, Base); + } + + /* + * Handle links with a REV attribute. - FM + * Handle REV="made" or REV="owner". - LM & FM + * Handle REL="author" -TD + */ + if (present && + ((present[HTML_LINK_REV] && + value[HTML_LINK_REV] && + (!strcasecomp("made", value[HTML_LINK_REV]) || + !strcasecomp("owner", value[HTML_LINK_REV]))) || + (present[HTML_LINK_REL] && + value[HTML_LINK_REL] && + (!strcasecomp("author", value[HTML_LINK_REL]))))) { + /* + * Load the owner element. - FM + */ + HTAnchor_setOwner(me->node_anchor, href); + CTRACE((tfp, "HTML: DOC OWNER '%s' found\n", href)); + FREE(href); + + /* + * Load the RevTitle element if a TITLE attribute and value + * are present. - FM + */ + if (present && present[HTML_LINK_TITLE] && + value[HTML_LINK_TITLE] && + *value[HTML_LINK_TITLE] != '\0') { + StrAllocCopy(title, value[HTML_LINK_TITLE]); + TRANSLATE_AND_UNESCAPE_ENTITIES(&title, TRUE, FALSE); + LYTrimHead(title); + LYTrimTail(title); + if (*title != '\0') + HTAnchor_setRevTitle(me->node_anchor, title); + FREE(title); + } + break; + } + + /* + * Handle REL links. - FM + */ + + if (present && + present[HTML_LINK_REL] && value[HTML_LINK_REL]) { + /* + * Ignore style sheets, for now. - FM + * + * lss and css have different syntax - lynx shouldn't try to + * parse them now (it tries to parse them as lss, so it exits + * with error message on the 1st non-empty line) - VH + */ +#ifndef USE_COLOR_STYLE + if (!strcasecomp(value[HTML_LINK_REL], "StyleSheet") || + !strcasecomp(value[HTML_LINK_REL], "Style")) { + CTRACE2(TRACE_STYLE, + (tfp, "HTML: StyleSheet link found.\n")); + CTRACE2(TRACE_STYLE, + (tfp, " StyleSheets not yet implemented.\n")); + FREE(href); + break; + } +#endif /* ! USE_COLOR_STYLE */ + + /* + * Ignore anything not registered in the 28-Mar-95 IETF HTML + * 3.0 draft and W3C HTML 3.2 draft, or not appropriate for + * Lynx banner links in the expired Maloney and Quin relrev + * draft. We'll make this more efficient when the situation + * stabilizes, and for now, we'll treat "Banner" as another + * toolbar element. - FM + */ + if (!strcasecomp(value[HTML_LINK_REL], "Home") || + !strcasecomp(value[HTML_LINK_REL], "ToC") || + !strcasecomp(value[HTML_LINK_REL], "Contents") || + !strcasecomp(value[HTML_LINK_REL], "Index") || + !strcasecomp(value[HTML_LINK_REL], "Glossary") || + !strcasecomp(value[HTML_LINK_REL], "Copyright") || + !strcasecomp(value[HTML_LINK_REL], "Help") || + !strcasecomp(value[HTML_LINK_REL], "Search") || + !strcasecomp(value[HTML_LINK_REL], "Bookmark") || + !strcasecomp(value[HTML_LINK_REL], "Banner") || + !strcasecomp(value[HTML_LINK_REL], "Top") || + !strcasecomp(value[HTML_LINK_REL], "Origin") || + !strcasecomp(value[HTML_LINK_REL], "Navigator") || + !strcasecomp(value[HTML_LINK_REL], "Disclaimer") || + !strcasecomp(value[HTML_LINK_REL], "Author") || + !strcasecomp(value[HTML_LINK_REL], "Editor") || + !strcasecomp(value[HTML_LINK_REL], "Publisher") || + !strcasecomp(value[HTML_LINK_REL], "Trademark") || + !strcasecomp(value[HTML_LINK_REL], "Hotlist") || + !strcasecomp(value[HTML_LINK_REL], "Begin") || + !strcasecomp(value[HTML_LINK_REL], "First") || + !strcasecomp(value[HTML_LINK_REL], "End") || + !strcasecomp(value[HTML_LINK_REL], "Last") || + !strcasecomp(value[HTML_LINK_REL], "Documentation") || + !strcasecomp(value[HTML_LINK_REL], "Biblioentry") || + !strcasecomp(value[HTML_LINK_REL], "Bibliography") || + !strcasecomp(value[HTML_LINK_REL], "Start") || + !strcasecomp(value[HTML_LINK_REL], "Appendix")) { + StrAllocCopy(title, value[HTML_LINK_REL]); + pdoctitle = &title; /* for setting HTAnchor's title */ + } else if (!strcasecomp(value[HTML_LINK_REL], "Up") || + !strcasecomp(value[HTML_LINK_REL], "Next") || + !strcasecomp(value[HTML_LINK_REL], "Previous") || + !strcasecomp(value[HTML_LINK_REL], "Prev") || + !strcasecomp(value[HTML_LINK_REL], "Child") || + !strcasecomp(value[HTML_LINK_REL], "Sibling") || + !strcasecomp(value[HTML_LINK_REL], "Parent") || + !strcasecomp(value[HTML_LINK_REL], "Meta") || + !strcasecomp(value[HTML_LINK_REL], "URC") || + !strcasecomp(value[HTML_LINK_REL], "Pointer") || + !strcasecomp(value[HTML_LINK_REL], "Translation") || + !strcasecomp(value[HTML_LINK_REL], "Definition") || + !strcasecomp(value[HTML_LINK_REL], "Alternate") || + !strcasecomp(value[HTML_LINK_REL], "Section") || + !strcasecomp(value[HTML_LINK_REL], "Subsection") || + !strcasecomp(value[HTML_LINK_REL], "Chapter")) { + StrAllocCopy(title, value[HTML_LINK_REL]); + /* not setting target HTAnchor's title, for these + links of highly relative character. Instead, + try to remember the REL attribute as a property + of the link (but not the destination), in the + (otherwise underused) link type in a special format; + the LIST page generation code may later use it. - kw */ + if (!intern_flag) { + StrAllocCopy(temp, "RelTitle: "); + StrAllocCat(temp, value[HTML_LINK_REL]); + } +#ifndef DISABLE_BIBP + } else if (!strcasecomp(value[HTML_LINK_REL], "citehost")) { + /* Citehost determination for bibp links. - RDC */ + HTAnchor_setCitehost(me->node_anchor, href); + CTRACE((tfp, "HTML: citehost '%s' found\n", href)); + FREE(href); + break; +#endif + } else { + CTRACE((tfp, "HTML: LINK with REL=\"%s\" ignored.\n", + value[HTML_LINK_REL])); + FREE(href); + break; + } + } + } else if (present && + present[HTML_LINK_REL] && value[HTML_LINK_REL]) { + /* + * If no HREF was specified, handle special REL links with + * self-designated HREFs. - FM + */ + if (!strcasecomp(value[HTML_LINK_REL], "Home")) { + StrAllocCopy(href, LynxHome); + } else if (!strcasecomp(value[HTML_LINK_REL], "Help")) { + StrAllocCopy(href, helpfile); + } else if (!strcasecomp(value[HTML_LINK_REL], "Index")) { + StrAllocCopy(href, indexfile); + } else { + CTRACE((tfp, + "HTML: LINK with REL=\"%s\" and no HREF ignored.\n", + value[HTML_LINK_REL])); + break; + } + StrAllocCopy(title, value[HTML_LINK_REL]); + pdoctitle = &title; + } + if (href) { + /* + * Create a title (link name) from the TITLE value, if present, or + * default to the REL value that was loaded into title. - FM + */ + if (present && present[HTML_LINK_TITLE] && + non_empty(value[HTML_LINK_TITLE])) { + StrAllocCopy(title, value[HTML_LINK_TITLE]); + TRANSLATE_AND_UNESCAPE_ENTITIES(&title, TRUE, FALSE); + LYTrimHead(title); + LYTrimTail(title); + pdoctitle = &title; + FREE(temp); /* forget about recording RelTitle - kw */ + } + if (isEmpty(title)) { + FREE(href); + FREE(title); + break; + } + + if (me->inA) { + /* + * Ugh! The LINK tag, which is a HEAD element, is in an + * Anchor, which is BODY element. All we can do is close the + * Anchor and cross our fingers. - FM + */ + SET_SKIP_STACK(HTML_A); + HTML_end_element(me, HTML_A, include); + } + + /* + * Create anchors for the links that simulate a toolbar. - FM + */ + me->CurrentA = HTAnchor_findChildAndLink(me->node_anchor, /* Parent */ + NULL, /* Tag */ + href, /* Address */ + (temp + ? (HTLinkType *) + HTAtom_for(temp) + : INTERN_LT)); /* Type */ + FREE(temp); + if ((dest = HTAnchor_parent(HTAnchor_followLink(me->CurrentA) + )) != NULL) { + if (pdoctitle && !HTAnchor_title(dest)) + HTAnchor_setTitle(dest, *pdoctitle); + + /* Don't allow CHARSET attribute to change *this* document's + charset assumption. - kw */ + if (dest == me->node_anchor) + dest = NULL; + if (present[HTML_LINK_CHARSET] && + non_empty(value[HTML_LINK_CHARSET])) { + dest_char_set = UCGetLYhndl_byMIME(value[HTML_LINK_CHARSET]); + if (dest_char_set < 0) + dest_char_set = UCLYhndl_for_unrec; + } + if (dest && dest_char_set >= 0) + HTAnchor_setUCInfoStage(dest, dest_char_set, + UCT_STAGE_PARSER, + UCT_SETBY_LINK); + } + UPDATE_STYLE; + if (!HText_hasToolbar(me->text) && + (ID_A = HTAnchor_findChildAndLink(me->node_anchor, /* Parent */ + LYToolbarName, /* Tag */ + NULL, /* Address */ + (HTLinkType *) 0))) { /* Type */ + HText_appendCharacter(me->text, '#'); + HText_setLastChar(me->text, ' '); /* absorb white space */ + HText_beginAnchor(me->text, me->inUnderline, ID_A); + HText_endAnchor(me->text, 0); + HText_setToolbar(me->text); + } else { + /* + * Add collapsible space to separate link from previous + * generated links. - kw + */ + HTML_put_character(me, ' '); + } + HText_beginAnchor(me->text, me->inUnderline, me->CurrentA); + if (me->inBoldH == FALSE) + HText_appendCharacter(me->text, LY_BOLD_START_CHAR); +#ifdef USE_COLOR_STYLE + if (present && present[HTML_LINK_CLASS] && + non_empty(value[HTML_LINK_CLASS])) { + char *tmp = 0; + int hcode2; + + HTSprintf0(&tmp, "link.%s.%s", value[HTML_LINK_CLASS], title); + hcode2 = color_style_1(tmp); + CTRACE2(TRACE_STYLE, + (tfp, "STYLE.link: using style <%s>\n", tmp)); + + HText_characterStyle(me->text, hcode2, STACK_ON); + HTML_put_string(me, title); + HTML_put_string(me, " ("); + HTML_put_string(me, value[HTML_LINK_CLASS]); + HTML_put_string(me, ")"); + HText_characterStyle(me->text, hcode2, STACK_OFF); + FREE(tmp); + } else +#endif + HTML_put_string(me, title); + if (me->inBoldH == FALSE) + HText_appendCharacter(me->text, LY_BOLD_END_CHAR); + HText_endAnchor(me->text, 0); + } + FREE(href); + FREE(title); + break; + + case HTML_ISINDEX: + if (((present)) && + ((present[HTML_ISINDEX_HREF] && value[HTML_ISINDEX_HREF]) || + (present[HTML_ISINDEX_ACTION] && value[HTML_ISINDEX_ACTION]))) { + /* + * Lynx was supporting ACTION, which never made it into the HTML + * 2.0 specs. HTML 3.0 uses HREF, so we'll use that too, but allow + * use of ACTION as an alternate until people have fully switched + * over. - FM + */ + if (present[HTML_ISINDEX_HREF] && value[HTML_ISINDEX_HREF]) + StrAllocCopy(href, value[HTML_ISINDEX_HREF]); + else + StrAllocCopy(href, value[HTML_ISINDEX_ACTION]); + LYLegitimizeHREF(me, &href, TRUE, TRUE); + + Base = (me->inBASE && *href != '\0' && *href != '#') + ? me->base_href + : me->node_anchor->address; + HTParseALL(&href, Base); + HTAnchor_setIndex(me->node_anchor, href); + FREE(href); + + } else { + Base = (me->inBASE) ? + me->base_href : me->node_anchor->address; + HTAnchor_setIndex(me->node_anchor, Base); + } + /* + * Support HTML 3.0 PROMPT attribute. - FM + */ + if (present && + present[HTML_ISINDEX_PROMPT] && + non_empty(value[HTML_ISINDEX_PROMPT])) { + StrAllocCopy(temp, value[HTML_ISINDEX_PROMPT]); + TRANSLATE_AND_UNESCAPE_ENTITIES(&temp, TRUE, FALSE); + LYTrimHead(temp); + LYTrimTail(temp); + if (*temp != '\0') { + StrAllocCat(temp, " "); + HTAnchor_setPrompt(me->node_anchor, temp); + } else { + HTAnchor_setPrompt(me->node_anchor, ENTER_DATABASE_QUERY); + } + FREE(temp); + } else { + HTAnchor_setPrompt(me->node_anchor, ENTER_DATABASE_QUERY); + } + break; + + case HTML_NEXTID: + break; + + case HTML_STYLE: + /* + * We're getting it as Literal text, which, for now, we'll just ignore. + * - FM + */ + HTChunkClear(&me->style_block); + break; + + case HTML_SCRIPT: + /* + * We're getting it as Literal text, which, for now, we'll just ignore. + * - FM + */ + HTChunkClear(&me->script); + break; + + case HTML_BODY: + CHECK_ID(HTML_BODY_ID); + if (HText_hasToolbar(me->text)) + HText_appendParagraph(me->text); + break; + + case HTML_SECTION: + case HTML_ARTICLE: + case HTML_MAIN: + case HTML_ASIDE: + case HTML_HEADER: + case HTML_FOOTER: + case HTML_NAV: + CHECK_ID(HTML_GEN5_ID); + if (HText_hasToolbar(me->text)) + HText_appendParagraph(me->text); + break; + + case HTML_FIGURE: + CHECK_ID(HTML_GEN5_ID); + break; + + case HTML_FRAMESET: + break; + + case HTML_FRAME: + if (present && present[HTML_FRAME_NAME] && + non_empty(value[HTML_FRAME_NAME])) { + StrAllocCopy(id_string, value[HTML_FRAME_NAME]); + TRANSLATE_AND_UNESCAPE_ENTITIES(&id_string, TRUE, FALSE); + LYTrimHead(id_string); + LYTrimTail(id_string); + } + if (present && present[HTML_FRAME_SRC] && + non_empty(value[HTML_FRAME_SRC])) { + StrAllocCopy(href, value[HTML_FRAME_SRC]); + LYLegitimizeHREF(me, &href, TRUE, TRUE); + + if (me->inA) { + SET_SKIP_STACK(HTML_A); + HTML_end_element(me, HTML_A, include); + } + me->CurrentA = HTAnchor_findChildAndLink(me->node_anchor, /* Parent */ + NULL, /* Tag */ + href, /* Address */ + (HTLinkType *) 0); /* Type */ + CAN_JUSTIFY_PUSH(FALSE); + LYEnsureSingleSpace(me); + if (me->inUnderline == FALSE) + HText_appendCharacter(me->text, LY_UNDERLINE_START_CHAR); + HTML_put_string(me, "FRAME:"); + if (me->inUnderline == FALSE) + HText_appendCharacter(me->text, LY_UNDERLINE_END_CHAR); + HTML_put_character(me, ' '); + + me->in_word = NO; + CHECK_ID(HTML_FRAME_ID); + HText_beginAnchor(me->text, me->inUnderline, me->CurrentA); + if (me->inBoldH == FALSE) + HText_appendCharacter(me->text, LY_BOLD_START_CHAR); + HTML_put_string(me, (id_string ? id_string : href)); + FREE(href); + if (me->inBoldH == FALSE) + HText_appendCharacter(me->text, LY_BOLD_END_CHAR); + HText_endAnchor(me->text, 0); + LYEnsureSingleSpace(me); + CAN_JUSTIFY_POP; + } else { + CHECK_ID(HTML_FRAME_ID); + } + FREE(id_string); + break; + + case HTML_NOFRAMES: + LYEnsureDoubleSpace(me); + LYResetParagraphAlignment(me); + break; + + case HTML_IFRAME: + if (present && present[HTML_IFRAME_NAME] && + non_empty(value[HTML_IFRAME_NAME])) { + StrAllocCopy(id_string, value[HTML_IFRAME_NAME]); + TRANSLATE_AND_UNESCAPE_ENTITIES(&id_string, TRUE, FALSE); + LYTrimHead(id_string); + LYTrimTail(id_string); + } + if (present && present[HTML_IFRAME_SRC] && + non_empty(value[HTML_IFRAME_SRC])) { + StrAllocCopy(href, value[HTML_IFRAME_SRC]); + LYLegitimizeHREF(me, &href, TRUE, TRUE); + + if (me->inA) + HTML_end_element(me, HTML_A, include); + + me->CurrentA = HTAnchor_findChildAndLink(me->node_anchor, /* Parent */ + NULL, /* Tag */ + href, /* Address */ + (HTLinkType *) 0); /* Type */ + LYEnsureDoubleSpace(me); + CAN_JUSTIFY_PUSH_F + LYResetParagraphAlignment(me); + if (me->inUnderline == FALSE) + HText_appendCharacter(me->text, LY_UNDERLINE_START_CHAR); + HTML_put_string(me, "IFRAME:"); + if (me->inUnderline == FALSE) + HText_appendCharacter(me->text, LY_UNDERLINE_END_CHAR); + HTML_put_character(me, ' '); + + me->in_word = NO; + CHECK_ID(HTML_IFRAME_ID); + HText_beginAnchor(me->text, me->inUnderline, me->CurrentA); + if (me->inBoldH == FALSE) + HText_appendCharacter(me->text, LY_BOLD_START_CHAR); + HTML_put_string(me, (id_string ? id_string : href)); + FREE(href); + if (me->inBoldH == FALSE) + HText_appendCharacter(me->text, LY_BOLD_END_CHAR); + HText_endAnchor(me->text, 0); + LYEnsureSingleSpace(me); + CAN_JUSTIFY_POP; + } else { + CHECK_ID(HTML_IFRAME_ID); + } + FREE(id_string); + break; + + case HTML_BANNER: + case HTML_MARQUEE: + change_paragraph_style(me, styles[HTML_BANNER]); + UPDATE_STYLE; + if (me->sp->tag_number == (int) ElementNumber) + LYEnsureDoubleSpace(me); + /* + * Treat this as a toolbar if we don't have one yet, and we are in the + * first half of the first page. - FM + */ + if ((!HText_hasToolbar(me->text) && + HText_getLines(me->text) < (display_lines / 2)) && + (ID_A = HTAnchor_findChildAndLink(me->node_anchor, /* Parent */ + LYToolbarName, /* Tag */ + NULL, /* Address */ + (HTLinkType *) 0))) { /* Type */ + HText_beginAnchor(me->text, me->inUnderline, ID_A); + HText_endAnchor(me->text, 0); + HText_setToolbar(me->text); + } + CHECK_ID(HTML_GEN_ID); + break; + + case HTML_CENTER: + case HTML_DIV: + if (me->Division_Level < (MAX_NESTING - 1)) { + me->Division_Level++; + } else { + CTRACE((tfp, + "HTML: ****** Maximum nesting of %d divisions exceeded!\n", + MAX_NESTING)); + } + if (me->inP) + LYEnsureSingleSpace(me); /* always at least break line - kw */ + if (ElementNumber == HTML_CENTER) { + me->DivisionAlignments[me->Division_Level] = HT_CENTER; + change_paragraph_style(me, styles[HTML_DCENTER]); + UPDATE_STYLE; + me->current_default_alignment = styles[HTML_DCENTER]->alignment; + } else if (me->List_Nesting_Level >= 0 && + !(present && present[HTML_DIV_ALIGN] && + value[HTML_DIV_ALIGN] && + (!strcasecomp(value[HTML_DIV_ALIGN], "center") || + !strcasecomp(value[HTML_DIV_ALIGN], "right")))) { + if (present && present[HTML_DIV_ALIGN]) + me->current_default_alignment = HT_LEFT; + else if (me->Division_Level == 0) + me->current_default_alignment = HT_LEFT; + else if (me->sp[0].tag_number == HTML_UL || + me->sp[0].tag_number == HTML_OL || + me->sp[0].tag_number == HTML_MENU || + me->sp[0].tag_number == HTML_DIR || + me->sp[0].tag_number == HTML_LI || + me->sp[0].tag_number == HTML_LH || + me->sp[0].tag_number == HTML_DD) + me->current_default_alignment = HT_LEFT; + LYHandlePlike(me, present, value, include, HTML_DIV_ALIGN, TRUE); + me->DivisionAlignments[me->Division_Level] = (short) + me->current_default_alignment; + } else if (present && present[HTML_DIV_ALIGN] && + non_empty(value[HTML_DIV_ALIGN])) { + if (!strcasecomp(value[HTML_DIV_ALIGN], "center")) { + me->DivisionAlignments[me->Division_Level] = HT_CENTER; + change_paragraph_style(me, styles[HTML_DCENTER]); + UPDATE_STYLE; + me->current_default_alignment = styles[HTML_DCENTER]->alignment; + } else if (!strcasecomp(value[HTML_DIV_ALIGN], "right")) { + me->DivisionAlignments[me->Division_Level] = HT_RIGHT; + change_paragraph_style(me, styles[HTML_DRIGHT]); + UPDATE_STYLE; + me->current_default_alignment = styles[HTML_DRIGHT]->alignment; + } else { + me->DivisionAlignments[me->Division_Level] = HT_LEFT; + change_paragraph_style(me, styles[HTML_DLEFT]); + UPDATE_STYLE; + me->current_default_alignment = styles[HTML_DLEFT]->alignment; + } + } else { + me->DivisionAlignments[me->Division_Level] = HT_LEFT; + change_paragraph_style(me, styles[HTML_DLEFT]); + UPDATE_STYLE; + me->current_default_alignment = styles[HTML_DLEFT]->alignment; + } + CHECK_ID(HTML_DIV_ID); + break; + + case HTML_H1: + case HTML_H2: + case HTML_H3: + case HTML_H4: + case HTML_H5: + case HTML_H6: + /* + * Close the previous style if not done by HTML doc. Added to get rid + * of core dumps in BAD HTML on the net. + * GAB 07-07-94 + * But then again, these are actually allowed to nest. I guess I have + * to depend on the HTML writers correct style. + * GAB 07-12-94 + if (i_prior_style != -1) { + HTML_end_element(me, i_prior_style); + } + i_prior_style = ElementNumber; + */ + + /* + * Check whether we have an H# in a list, and if so, treat it as an LH. + * - FM + */ + if ((me->List_Nesting_Level >= 0) && + (me->sp[0].tag_number == HTML_UL || + me->sp[0].tag_number == HTML_OL || + me->sp[0].tag_number == HTML_MENU || + me->sp[0].tag_number == HTML_DIR || + me->sp[0].tag_number == HTML_LI)) { + if (HTML_dtd.tags[HTML_LH].contents == SGML_EMPTY) { + ElementNumber = HTML_LH; + } else { + me->new_style = me->sp[0].style; + ElementNumber = (HTMLElement) me->sp[0].tag_number; + UPDATE_STYLE; + } + /* + * Some authors use H# headers as a substitute for FONT, so check + * if this one immediately followed an LI. If so, both me->inP and + * me->in_word will be FALSE (though the line might not be empty + * due to a bullet and/or nbsp) and we can assume it is just for a + * FONT change. We thus will not create another line break nor add + * to the current left indentation. - FM + */ + if (!(me->inP == FALSE && me->in_word == NO)) { + HText_appendParagraph(me->text); + HTML_put_character(me, HT_NON_BREAK_SPACE); + HText_setLastChar(me->text, ' '); + me->in_word = NO; + me->inP = FALSE; + } + CHECK_ID(HTML_H_ID); + break; + } + + if (present && present[HTML_H_ALIGN] && + non_empty(value[HTML_H_ALIGN])) { + if (!strcasecomp(value[HTML_H_ALIGN], "center")) + change_paragraph_style(me, styles[HTML_HCENTER]); + else if (!strcasecomp(value[HTML_H_ALIGN], "right")) + change_paragraph_style(me, styles[HTML_HRIGHT]); + else if (!strcasecomp(value[HTML_H_ALIGN], "left") || + !strcasecomp(value[HTML_H_ALIGN], "justify")) + change_paragraph_style(me, styles[HTML_HLEFT]); + else + change_paragraph_style(me, styles[ElementNumber]); + } else if (me->Division_Level >= 0) { + if (me->DivisionAlignments[me->Division_Level] == HT_CENTER) { + change_paragraph_style(me, styles[HTML_HCENTER]); + } else if (me->DivisionAlignments[me->Division_Level] == HT_LEFT) { + change_paragraph_style(me, styles[HTML_HLEFT]); + } else if (me->DivisionAlignments[me->Division_Level] == HT_RIGHT) { + change_paragraph_style(me, styles[HTML_HRIGHT]); + } + } else { + change_paragraph_style(me, styles[ElementNumber]); + } + UPDATE_STYLE; + CHECK_ID(HTML_H_ID); + + if ((bold_headers == TRUE || + (ElementNumber == HTML_H1 && bold_H1 == TRUE)) && + (styles[ElementNumber]->font & HT_BOLD)) { + if (me->inBoldA == FALSE && me->inBoldH == FALSE) { + HText_appendCharacter(me->text, LY_BOLD_START_CHAR); + } + me->inBoldH = TRUE; + } + break; + + case HTML_P: + LYHandlePlike(me, present, value, include, HTML_P_ALIGN, TRUE); + CHECK_ID(HTML_P_ID); + break; + + case HTML_BR: + UPDATE_STYLE; + CHECK_ID(HTML_GEN_ID); + /* Add a \r (new line) if these conditions are true: + * * We are not collapsing BR's (and either we are not trimming + * blank lines, or the preceding line is non-empty), or + * * The current line has text on it. + * Otherwise, don't do anything. -DH 19980814, TD 19980827/20170704 + */ + if ((LYCollapseBRs == FALSE && + (!LYtrimBlankLines || + !HText_PreviousLineEmpty(me->text, FALSE))) || + !HText_LastLineEmpty(me->text, FALSE)) { + HText_setLastChar(me->text, ' '); /* absorb white space */ + HText_appendCharacter(me->text, '\r'); + } + me->in_word = NO; + me->inP = FALSE; + break; + + case HTML_WBR: + UPDATE_STYLE; + CHECK_ID(HTML_GEN_ID); + HText_setBreakPoint(me->text); + break; + + case HTML_HY: + case HTML_SHY: + UPDATE_STYLE; + CHECK_ID(HTML_GEN_ID); + HText_appendCharacter(me->text, LY_SOFT_HYPHEN); + break; + + case HTML_HR: + { + int width; + + /* + * Start a new line only if we had printable characters following + * the previous newline, or remove the previous line if both it and + * the last line are blank. - FM + */ + UPDATE_STYLE; + if (!HText_LastLineEmpty(me->text, FALSE)) { + HText_setLastChar(me->text, ' '); /* absorb white space */ + HText_appendCharacter(me->text, '\r'); + } else if (HText_PreviousLineEmpty(me->text, FALSE)) { + HText_RemovePreviousLine(me->text); + } + me->in_word = NO; + me->inP = FALSE; + + /* + * Add an ID link if needed. - FM + */ + CHECK_ID(HTML_HR_ID); + + /* + * Center lines within the current margins, if a right or left + * ALIGNment is not specified. If WIDTH="#%" is given and not + * garbage, use that to calculate the width, otherwise use the + * default width. - FM + */ + if (present && present[HTML_HR_ALIGN] && value[HTML_HR_ALIGN]) { + if (!strcasecomp(value[HTML_HR_ALIGN], "right")) { + me->sp->style->alignment = HT_RIGHT; + } else if (!strcasecomp(value[HTML_HR_ALIGN], "left")) { + me->sp->style->alignment = HT_LEFT; + } else { + me->sp->style->alignment = HT_CENTER; + } + } else { + me->sp->style->alignment = HT_CENTER; + } + width = LYcolLimit - + me->new_style->leftIndent - me->new_style->rightIndent; + if (present && present[HTML_HR_WIDTH] && value[HTML_HR_WIDTH] && + isdigit(UCH(*value[HTML_HR_WIDTH])) && + value[HTML_HR_WIDTH][strlen(value[HTML_HR_WIDTH]) - 1] == '%') { + char *percent = NULL; + int Percent, Width; + + StrAllocCopy(percent, value[HTML_HR_WIDTH]); + percent[strlen(percent) - 1] = '\0'; + Percent = atoi(percent); + if (Percent > 100 || Percent < 1) + width -= 5; + else { + Width = (width * Percent) / 100; + if (Width < 1) + width = 1; + else + width = Width; + } + FREE(percent); + } else { + width -= 5; + } + for (i = 0; i < width; i++) + HTML_put_character(me, '_'); + HText_appendCharacter(me->text, '\r'); + me->in_word = NO; + me->inP = FALSE; + + /* + * Reset the alignment appropriately for the division and/or block. + * - FM + */ + if (me->List_Nesting_Level < 0 && + me->Division_Level >= 0) { + me->sp->style->alignment = + me->DivisionAlignments[me->Division_Level]; + } else if (me->sp->style->id == ST_HeadingCenter || + me->sp->style->id == ST_Heading1) { + me->sp->style->alignment = HT_CENTER; + } else if (me->sp->style->id == ST_HeadingRight) { + me->sp->style->alignment = HT_RIGHT; + } else { + me->sp->style->alignment = HT_LEFT; + } + + /* + * Add a blank line and set the second line indentation for lists + * and addresses, or a paragraph separator for other blocks. - FM + */ + if (me->List_Nesting_Level >= 0 || + me->sp[0].tag_number == HTML_ADDRESS) { + HText_setLastChar(me->text, ' '); /* absorb white space */ + HText_appendCharacter(me->text, '\r'); + } else { + HText_appendParagraph(me->text); + } + } + break; + + case HTML_TAB: + if (!present) { /* Bad tag. Must have at least one attribute. - FM */ + CTRACE((tfp, "HTML: TAB tag has no attributes. Ignored.\n")); + break; + } + /* + * If page author is using TAB within a TABLE, it's probably formatted + * specifically to work well for Lynx without simple table tracking + * code. Cancel tracking, it would only make things worse. - kw + */ + HText_cancelStbl(me->text); + UPDATE_STYLE; + + CANT_JUSTIFY_THIS_LINE; + if (present[HTML_TAB_ALIGN] && value[HTML_TAB_ALIGN] && + (strcasecomp(value[HTML_TAB_ALIGN], "left") || + !(present[HTML_TAB_TO] || present[HTML_TAB_INDENT]))) { + /* + * Just ensure a collapsible space, until we have the ALIGN and DP + * attributes implemented. - FM + */ + HTML_put_character(me, ' '); + CTRACE((tfp, + "HTML: ALIGN not 'left'. Using space instead of TAB.\n")); + + } else if (!LYoverride_default_alignment(me) && + me->current_default_alignment != HT_LEFT) { + /* + * Just ensure a collapsible space, until we can replace + * HText_getCurrentColumn() in GridText.c with code which doesn't + * require that the alignment be HT_LEFT. - FM + */ + HTML_put_character(me, ' '); + CTRACE((tfp, "HTML: Not HT_LEFT. Using space instead of TAB.\n")); + + } else if ((present[HTML_TAB_TO] && + non_empty(value[HTML_TAB_TO])) || + (present[HTML_TAB_INDENT] && + value[HTML_TAB_INDENT] && + isdigit(UCH(*value[HTML_TAB_INDENT])))) { + int column, target = -1; + int enval = 2; + + column = HText_getCurrentColumn(me->text); + if (present[HTML_TAB_TO] && + non_empty(value[HTML_TAB_TO])) { + /* + * TO has priority over INDENT if both are present. - FM + */ + StrAllocCopy(temp, value[HTML_TAB_TO]); + TRANSLATE_AND_UNESCAPE_TO_STD(&temp); + if (*temp) { + target = HText_getTabIDColumn(me->text, temp); + } + } else if (isEmpty(temp) && present[HTML_TAB_INDENT] && + value[HTML_TAB_INDENT] && + isdigit(UCH(*value[HTML_TAB_INDENT]))) { + /* + * The INDENT value is in "en" (enval per column) units. + * Divide it by enval, rounding odd values up. - FM + */ + target = + (int) (((1.0 * atoi(value[HTML_TAB_INDENT])) / enval) + (0.5)); + } + FREE(temp); + /* + * If we are being directed to a column too far to the left or + * right, just add a collapsible space, otherwise, add the + * appropriate number of spaces. - FM + */ + + if (target < column || + target > HText_getMaximumColumn(me->text)) { + HTML_put_character(me, ' '); + CTRACE((tfp, + "HTML: Column out of bounds. Using space instead of TAB.\n")); + } else { + for (i = column; i < target; i++) + HText_appendCharacter(me->text, ' '); + HText_setLastChar(me->text, ' '); /* absorb white space */ + } + } + me->in_word = NO; + + /* + * If we have an ID attribute, save it together with the value of the + * column we've reached. - FM + */ + if (present[HTML_TAB_ID] && + non_empty(value[HTML_TAB_ID])) { + StrAllocCopy(temp, value[HTML_TAB_ID]); + TRANSLATE_AND_UNESCAPE_TO_STD(&temp); + if (*temp) + HText_setTabID(me->text, temp); + FREE(temp); + } + break; + + case HTML_BASEFONT: + break; + + case HTML_FONT: + + /* + * FONT *may* have been declared SGML_EMPTY in HTMLDTD.c, and + * SGML_character() in SGML.c *may* check for a FONT end tag to call + * HTML_end_element() directly (with a check in that to bypass + * decrementing of the HTML parser's stack). Or this may have been + * really a end tag, for which some incarnations of SGML.c + * would fake a start tag instead. - fm & kw + * + * But if we have an open FONT, DON'T close that one now, since FONT + * tags can be legally nested AFAIK, and Lynx currently doesn't do + * anything with them anyway... - kw + */ +#ifdef NOTUSED_FOTEMODS + if (me->inFONT == TRUE) + HTML_end_element(me, HTML_FONT, &include); +#endif /* NOTUSED_FOTEMODS */ + + /* + * Set flag to know we are in a FONT container, and add code to do + * something about it, someday. - FM + */ + me->inFONT = TRUE; + break; + + case HTML_B: /* Physical character highlighting */ + case HTML_BLINK: + case HTML_I: + case HTML_U: + + case HTML_CITE: /* Logical character highlighting */ + case HTML_EM: + case HTML_STRONG: + UPDATE_STYLE; + me->Underline_Level++; + CHECK_ID(HTML_GEN_ID); + /* + * Ignore this if inside of a bold anchor or header. Can't display + * both underline and bold at same time. + */ + if (me->inBoldA == TRUE || me->inBoldH == TRUE) { + CTRACE((tfp, "Underline Level is %d\n", me->Underline_Level)); + break; + } + if (me->inUnderline == FALSE) { + HText_appendCharacter(me->text, LY_UNDERLINE_START_CHAR); + me->inUnderline = TRUE; + CTRACE((tfp, "Beginning underline\n")); + } else { + CTRACE((tfp, "Underline Level is %d\n", me->Underline_Level)); + } + break; + + case HTML_ABBR: /* Miscellaneous character containers */ + case HTML_ACRONYM: + case HTML_AU: + case HTML_AUTHOR: + case HTML_BIG: + case HTML_CODE: + case HTML_DFN: + case HTML_KBD: + case HTML_SAMP: + case HTML_SMALL: + case HTML_TT: + case HTML_VAR: + CHECK_ID(HTML_GEN_ID); + break; /* ignore */ + + case HTML_SUP: + HText_appendCharacter(me->text, '^'); + CHECK_ID(HTML_GEN_ID); + break; + + case HTML_SUB: + HText_appendCharacter(me->text, '['); + CHECK_ID(HTML_GEN_ID); + break; + + case HTML_DEL_2: + case HTML_DEL: + case HTML_S: + case HTML_STRIKE: + CHECK_ID(HTML_GEN_ID); + if (me->inUnderline == FALSE) + HText_appendCharacter(me->text, LY_UNDERLINE_START_CHAR); + HTML_put_string(me, "[DEL:"); + if (me->inUnderline == FALSE) + HText_appendCharacter(me->text, LY_UNDERLINE_END_CHAR); + HTML_put_character(me, ' '); + me->in_word = NO; + break; + + case HTML_INS_2: + case HTML_INS: + CHECK_ID(HTML_GEN_ID); + if (me->inUnderline == FALSE) + HText_appendCharacter(me->text, LY_UNDERLINE_START_CHAR); + HTML_put_string(me, "[INS:"); + if (me->inUnderline == FALSE) + HText_appendCharacter(me->text, LY_UNDERLINE_END_CHAR); + HTML_put_character(me, ' '); + me->in_word = NO; + break; + + case HTML_Q: + CHECK_ID(HTML_GEN_ID); + /* + * Should check LANG and/or DIR attributes, and the + * me->node_anchor->charset and/or yet to be added structure elements, + * to determine whether we should use chevrons, but for now we'll + * always use double- or single-quotes. - FM + */ + if (!(me->Quote_Level & 1)) + HTML_put_character(me, '"'); + else + HTML_put_character(me, '`'); + me->Quote_Level++; + break; + + case HTML_PRE: /* Formatted text */ + /* + * Set our inPRE flag to FALSE so that a newline immediately following + * the PRE start tag will be ignored. HTML_put_character() will set it + * to TRUE when the first character within the PRE block is received. + * - FM + */ + me->inPRE = FALSE; + /* FALLTHRU */ + case HTML_LISTING: /* Literal text */ + /* FALLTHRU */ + case HTML_XMP: + /* FALLTHRU */ + case HTML_PLAINTEXT: + change_paragraph_style(me, styles[ElementNumber]); + UPDATE_STYLE; + CHECK_ID(HTML_GEN_ID); + if (me->comment_end) + HText_appendText(me->text, me->comment_end); + break; + + case HTML_BLOCKQUOTE: + case HTML_BQ: + change_paragraph_style(me, styles[ElementNumber]); + UPDATE_STYLE; + if (me->sp->tag_number == (int) ElementNumber) + LYEnsureDoubleSpace(me); + CHECK_ID(HTML_BQ_ID); + break; + + case HTML_NOTE: + change_paragraph_style(me, styles[ElementNumber]); + UPDATE_STYLE; + if (me->sp->tag_number == (int) ElementNumber) + LYEnsureDoubleSpace(me); + CHECK_ID(HTML_NOTE_ID); + { + char *note = NULL; + + /* + * Indicate the type of NOTE. + */ + if (present && present[HTML_NOTE_CLASS] && + value[HTML_NOTE_CLASS] && + (!strcasecomp(value[HTML_NOTE_CLASS], "CAUTION") || + !strcasecomp(value[HTML_NOTE_CLASS], "WARNING"))) { + StrAllocCopy(note, value[HTML_NOTE_CLASS]); + LYUpperCase(note); + StrAllocCat(note, ":"); + } else if (present && present[HTML_NOTE_ROLE] && + value[HTML_NOTE_ROLE] && + (!strcasecomp(value[HTML_NOTE_ROLE], "CAUTION") || + !strcasecomp(value[HTML_NOTE_ROLE], "WARNING"))) { + StrAllocCopy(note, value[HTML_NOTE_ROLE]); + LYUpperCase(note); + StrAllocCat(note, ":"); + } else { + StrAllocCopy(note, "NOTE:"); + } + if (me->inUnderline == FALSE) + HText_appendCharacter(me->text, LY_UNDERLINE_START_CHAR); + HTML_put_string(me, note); + if (me->inUnderline == FALSE) + HText_appendCharacter(me->text, LY_UNDERLINE_END_CHAR); + HTML_put_character(me, ' '); + CAN_JUSTIFY_START; + FREE(note); + } + CAN_JUSTIFY_START; + me->inLABEL = TRUE; + me->in_word = NO; + me->inP = FALSE; + break; + + case HTML_ADDRESS: + if (me->List_Nesting_Level < 0) { + change_paragraph_style(me, styles[ElementNumber]); + UPDATE_STYLE; + if (me->sp->tag_number == (int) ElementNumber) + LYEnsureDoubleSpace(me); + } else { + LYHandlePlike(me, present, value, include, -1, TRUE); + } + CHECK_ID(HTML_ADDRESS_ID); + break; + + case HTML_DL: + me->List_Nesting_Level++; /* increment the List nesting level */ + if (me->List_Nesting_Level <= 0) { + change_paragraph_style(me, present && present[HTML_DL_COMPACT] + ? styles[HTML_DLC] : styles[HTML_DL]); + + } else if (me->List_Nesting_Level >= 6) { + change_paragraph_style(me, present && present[HTML_DL_COMPACT] + ? styles[HTML_DLC6] : styles[HTML_DL6]); + + } else { + change_paragraph_style(me, present && present[HTML_DL_COMPACT] + ? styles[(HTML_DLC1 - 1) + me->List_Nesting_Level] + : styles[(HTML_DL1 - 1) + me->List_Nesting_Level]); + } + UPDATE_STYLE; /* update to the new style */ + CHECK_ID(HTML_DL_ID); + + break; + + case HTML_DLC: + me->List_Nesting_Level++; /* increment the List nesting level */ + if (me->List_Nesting_Level <= 0) { + change_paragraph_style(me, styles[HTML_DLC]); + + } else if (me->List_Nesting_Level >= 6) { + change_paragraph_style(me, styles[HTML_DLC6]); + + } else { + change_paragraph_style(me, + styles[(HTML_DLC1 - 1) + me->List_Nesting_Level]); + } + UPDATE_STYLE; /* update to the new style */ + CHECK_ID(HTML_DL_ID); + break; + + case HTML_DT: + CHECK_ID(HTML_GEN_ID); + if (!me->style_change) { + BOOL in_line_1 = HText_inLineOne(me->text); + HTCoord saved_spaceBefore = me->sp->style->spaceBefore; + HTCoord saved_spaceAfter = me->sp->style->spaceAfter; + + /* + * If there are several DT elements and this is not the first, and + * the preceding DT element's first (and normally only) line has + * not yet been ended, suppress intervening blank line by + * temporarily modifying the paragraph style in place. Ugly but + * there's ample precedence. - kw + */ + if (in_line_1) { + me->sp->style->spaceBefore = 0; /* temporary change */ + me->sp->style->spaceAfter = 0; /* temporary change */ + } + HText_appendParagraph(me->text); + me->sp->style->spaceBefore = saved_spaceBefore; /* undo */ + me->sp->style->spaceAfter = saved_spaceAfter; /* undo */ + me->in_word = NO; + me->sp->style->alignment = HT_LEFT; + } + me->inP = FALSE; + break; + + case HTML_DD: + CHECK_ID(HTML_GEN_ID); + HText_setLastChar(me->text, ' '); /* absorb white space */ + if (!me->style_change) { + if (!HText_LastLineEmpty(me->text, FALSE)) { + HText_appendCharacter(me->text, '\r'); + } else { + HText_NegateLineOne(me->text); + } + } else { + UPDATE_STYLE; + HText_appendCharacter(me->text, '\t'); + } + me->sp->style->alignment = HT_LEFT; + me->in_word = NO; + me->inP = FALSE; + break; + + case HTML_OL: + /* + * Set the default TYPE. + */ + me->OL_Type[(me->List_Nesting_Level < 11 ? + me->List_Nesting_Level + 1 : 11)] = '1'; + + /* + * Check whether we have a starting sequence number, or want to + * continue the numbering from a previous OL in this nest. - FM + */ + if (present && (present[HTML_OL_SEQNUM] || present[HTML_OL_START])) { + int seqnum; + + /* + * Give preference to the valid HTML 3.0 SEQNUM attribute name over + * the Netscape START attribute name (too bad the Netscape + * developers didn't read the HTML 3.0 specs before re-inventing + * the "wheel" as "we'll"). - FM + */ + if (present[HTML_OL_SEQNUM] && + non_empty(value[HTML_OL_SEQNUM])) { + seqnum = atoi(value[HTML_OL_SEQNUM]); + } else if (present[HTML_OL_START] && + non_empty(value[HTML_OL_START])) { + seqnum = atoi(value[HTML_OL_START]); + } else { + seqnum = 1; + } + + /* + * Don't allow negative numbers less than or equal to our flags, or + * numbers less than 1 if an Alphabetic or Roman TYPE. - FM + */ + if (present[HTML_OL_TYPE] && value[HTML_OL_TYPE]) { + if (*value[HTML_OL_TYPE] == 'A') { + me->OL_Type[(me->List_Nesting_Level < 11 ? + me->List_Nesting_Level + 1 : 11)] = 'A'; + if (seqnum < 1) + seqnum = 1; + } else if (*value[HTML_OL_TYPE] == 'a') { + me->OL_Type[(me->List_Nesting_Level < 11 ? + me->List_Nesting_Level + 1 : 11)] = 'a'; + if (seqnum < 1) + seqnum = 1; + } else if (*value[HTML_OL_TYPE] == 'I') { + me->OL_Type[(me->List_Nesting_Level < 11 ? + me->List_Nesting_Level + 1 : 11)] = 'I'; + if (seqnum < 1) + seqnum = 1; + } else if (*value[HTML_OL_TYPE] == 'i') { + me->OL_Type[(me->List_Nesting_Level < 11 ? + me->List_Nesting_Level + 1 : 11)] = 'i'; + if (seqnum < 1) + seqnum = 1; + } else { + if (seqnum <= OL_VOID) + seqnum = OL_VOID + 1; + } + } else if (seqnum <= OL_VOID) { + seqnum = OL_VOID + 1; + } + + me->OL_Counter[(me->List_Nesting_Level < 11 ? + me->List_Nesting_Level + 1 : 11)] = seqnum; + + } else if (present && present[HTML_OL_CONTINUE]) { + me->OL_Counter[me->List_Nesting_Level < 11 ? + me->List_Nesting_Level + 1 : 11] = OL_CONTINUE; + + } else { + me->OL_Counter[(me->List_Nesting_Level < 11 ? + me->List_Nesting_Level + 1 : 11)] = 1; + if (present && present[HTML_OL_TYPE] && value[HTML_OL_TYPE]) { + if (*value[HTML_OL_TYPE] == 'A') { + me->OL_Type[(me->List_Nesting_Level < 11 ? + me->List_Nesting_Level + 1 : 11)] = 'A'; + } else if (*value[HTML_OL_TYPE] == 'a') { + me->OL_Type[(me->List_Nesting_Level < 11 ? + me->List_Nesting_Level + 1 : 11)] = 'a'; + } else if (*value[HTML_OL_TYPE] == 'I') { + me->OL_Type[(me->List_Nesting_Level < 11 ? + me->List_Nesting_Level + 1 : 11)] = 'I'; + } else if (*value[HTML_OL_TYPE] == 'i') { + me->OL_Type[(me->List_Nesting_Level < 11 ? + me->List_Nesting_Level + 1 : 11)] = 'i'; + } + } + } + me->List_Nesting_Level++; + + if (me->List_Nesting_Level <= 0) { + change_paragraph_style(me, styles[ElementNumber]); + + } else if (me->List_Nesting_Level >= 6) { + change_paragraph_style(me, styles[HTML_OL6]); + + } else { + change_paragraph_style(me, + styles[HTML_OL1 + me->List_Nesting_Level - 1]); + } + UPDATE_STYLE; /* update to the new style */ + CHECK_ID(HTML_OL_ID); + break; + + case HTML_UL: + me->List_Nesting_Level++; + + if (me->List_Nesting_Level <= 0) { + if (!(present && present[HTML_UL_PLAIN]) && + !(present && present[HTML_UL_TYPE] && + value[HTML_UL_TYPE] && + 0 == strcasecomp(value[HTML_UL_TYPE], "PLAIN"))) { + change_paragraph_style(me, styles[ElementNumber]); + } else { + change_paragraph_style(me, styles[HTML_DIR]); + ElementNumber = HTML_DIR; + } + + } else if (me->List_Nesting_Level >= 6) { + if (!(present && present[HTML_UL_PLAIN]) && + !(present && present[HTML_UL_TYPE] && + value[HTML_UL_TYPE] && + 0 == strcasecomp(value[HTML_UL_TYPE], "PLAIN"))) { + change_paragraph_style(me, styles[HTML_OL6]); + } else { + change_paragraph_style(me, styles[HTML_MENU6]); + ElementNumber = HTML_DIR; + } + + } else { + if (!(present && present[HTML_UL_PLAIN]) && + !(present && present[HTML_UL_TYPE] && + value[HTML_UL_TYPE] && + 0 == strcasecomp(value[HTML_UL_TYPE], "PLAIN"))) { + change_paragraph_style(me, + styles[HTML_OL1 + me->List_Nesting_Level + - 1]); + } else { + change_paragraph_style(me, + styles[HTML_MENU1 + me->List_Nesting_Level + - 1]); + ElementNumber = HTML_DIR; + } + } + UPDATE_STYLE; /* update to the new style */ + CHECK_ID(HTML_UL_ID); + break; + + case HTML_MENU: + case HTML_DIR: + me->List_Nesting_Level++; + + if (me->List_Nesting_Level <= 0) { + change_paragraph_style(me, styles[ElementNumber]); + + } else if (me->List_Nesting_Level >= 6) { + change_paragraph_style(me, styles[HTML_MENU6]); + + } else { + change_paragraph_style(me, + styles[HTML_MENU1 + me->List_Nesting_Level + - 1]); + } + UPDATE_STYLE; /* update to the new style */ + CHECK_ID(HTML_UL_ID); + break; + + case HTML_LH: + UPDATE_STYLE; /* update to the new style */ + HText_appendParagraph(me->text); + CHECK_ID(HTML_GEN_ID); + HTML_put_character(me, HT_NON_BREAK_SPACE); + HText_setLastChar(me->text, ' '); + me->in_word = NO; + me->inP = FALSE; + break; + + case HTML_LI: + UPDATE_STYLE; /* update to the new style */ + HText_appendParagraph(me->text); + me->sp->style->alignment = HT_LEFT; + CHECK_ID(HTML_LI_ID); + { + int surrounding_tag_number = me->sp[0].tag_number; + + /* + * No, a LI should never occur directly within another LI, but this + * may result from incomplete error recovery. So check one more + * surrounding level in this case. - kw + */ + if (surrounding_tag_number == HTML_LI && + me->sp < (me->stack + MAX_NESTING - 1)) + surrounding_tag_number = me->sp[1].tag_number; + if (surrounding_tag_number == HTML_OL) { + char number_string[20]; + int counter, seqnum; + char seqtype; + + counter = me->List_Nesting_Level < 11 ? + me->List_Nesting_Level : 11; + if (present && present[HTML_LI_TYPE] && value[HTML_LI_TYPE]) { + if (*value[HTML_LI_TYPE] == '1') { + me->OL_Type[counter] = '1'; + } else if (*value[HTML_LI_TYPE] == 'A') { + me->OL_Type[counter] = 'A'; + } else if (*value[HTML_LI_TYPE] == 'a') { + me->OL_Type[counter] = 'a'; + } else if (*value[HTML_LI_TYPE] == 'I') { + me->OL_Type[counter] = 'I'; + } else if (*value[HTML_LI_TYPE] == 'i') { + me->OL_Type[counter] = 'i'; + } + } + if (present && present[HTML_LI_VALUE] && + ((value[HTML_LI_VALUE] != NULL) && + (*value[HTML_LI_VALUE] != '\0')) && + ((isdigit(UCH(*value[HTML_LI_VALUE]))) || + (*value[HTML_LI_VALUE] == '-' && + isdigit(UCH(*(value[HTML_LI_VALUE] + 1)))))) { + seqnum = atoi(value[HTML_LI_VALUE]); + if (seqnum <= OL_VOID) + seqnum = OL_VOID + 1; + seqtype = me->OL_Type[counter]; + if (seqtype != '1' && seqnum < 1) + seqnum = 1; + me->OL_Counter[counter] = seqnum + 1; + } else if (me->OL_Counter[counter] >= OL_VOID) { + seqnum = me->OL_Counter[counter]++; + seqtype = me->OL_Type[counter]; + if (seqtype != '1' && seqnum < 1) { + seqnum = 1; + me->OL_Counter[counter] = seqnum + 1; + } + } else { + seqnum = me->Last_OL_Count + 1; + seqtype = me->Last_OL_Type; + for (i = (counter - 1); i >= 0; i--) { + if (me->OL_Counter[i] > OL_VOID) { + seqnum = me->OL_Counter[i]++; + seqtype = me->OL_Type[i]; + i = 0; + } + } + } + if (seqtype == 'A') { + strcpy(number_string, LYUppercaseA_OL_String(seqnum)); + } else if (seqtype == 'a') { + strcpy(number_string, LYLowercaseA_OL_String(seqnum)); + } else if (seqtype == 'I') { + strcpy(number_string, LYUppercaseI_OL_String(seqnum)); + } else if (seqtype == 'i') { + strcpy(number_string, LYLowercaseI_OL_String(seqnum)); + } else { + sprintf(number_string, "%2d.", seqnum); + } + me->Last_OL_Count = seqnum; + me->Last_OL_Type = seqtype; + /* + * Hack, because there is no append string! + */ + for (i = 0; number_string[i] != '\0'; i++) + if (number_string[i] == ' ') + HTML_put_character(me, HT_NON_BREAK_SPACE); + else + HTML_put_character(me, number_string[i]); + + /* + * Use HTML_put_character so that any other spaces coming + * through will be collapsed. We'll use nbsp, so it won't + * break at the spacing character if there are no spaces in the + * subsequent text up to the right margin, but will declare it + * as a normal space to ensure collapsing if a normal space + * does immediately follow it. - FM + */ + HTML_put_character(me, HT_NON_BREAK_SPACE); + HText_setLastChar(me->text, ' '); + } else if (surrounding_tag_number == HTML_UL) { + /* + * Hack, because there is no append string! + */ + HTML_put_character(me, HT_NON_BREAK_SPACE); + HTML_put_character(me, HT_NON_BREAK_SPACE); + switch (me->List_Nesting_Level % 7) { + case 0: + HTML_put_character(me, '*'); + break; + case 1: + HTML_put_character(me, '+'); + break; + case 2: + HTML_put_character(me, 'o'); + break; + case 3: + HTML_put_character(me, '#'); + break; + case 4: + HTML_put_character(me, '@'); + break; + case 5: + HTML_put_character(me, '-'); + break; + case 6: + HTML_put_character(me, '='); + break; + + } + /* + * Keep using HTML_put_character so that any other spaces + * coming through will be collapsed. We use nbsp, so we won't + * wrap at the spacing character if there are no spaces in the + * subsequent text up to the right margin, but will declare it + * as a normal space to ensure collapsing if a normal space + * does immediately follow it. - FM + */ + HTML_put_character(me, HT_NON_BREAK_SPACE); + HText_setLastChar(me->text, ' '); + } else { + /* + * Hack, because there is no append string! + */ + HTML_put_character(me, HT_NON_BREAK_SPACE); + HTML_put_character(me, HT_NON_BREAK_SPACE); + HText_setLastChar(me->text, ' '); + } + } + CAN_JUSTIFY_START; + me->in_word = NO; + me->inP = FALSE; + break; + + case HTML_SPAN: + CHECK_ID(HTML_GEN_ID); + /* + * Should check LANG and/or DIR attributes, and the + * me->node_anchor->charset and/or yet to be added structure elements, + * and do something here. - FM + */ + break; + + case HTML_BDO: + CHECK_ID(HTML_GEN_ID); + /* + * Should check DIR (and LANG) attributes, and the + * me->node_anchor->charset and/or yet to be added structure elements, + * and do something here. - FM + */ + break; + + case HTML_SPOT: + CHECK_ID(HTML_GEN_ID); + break; + + case HTML_FN: + change_paragraph_style(me, styles[ElementNumber]); + UPDATE_STYLE; + if (me->sp->tag_number == (int) ElementNumber) + LYEnsureDoubleSpace(me); + CHECK_ID(HTML_GEN_ID); + if (me->inUnderline == FALSE) + HText_appendCharacter(me->text, LY_UNDERLINE_START_CHAR); + HTML_put_string(me, "FOOTNOTE:"); + if (me->inUnderline == FALSE) + HText_appendCharacter(me->text, LY_UNDERLINE_END_CHAR); + HTML_put_character(me, ' '); + CAN_JUSTIFY_START + me->inLABEL = TRUE; + me->in_word = NO; + me->inP = FALSE; + break; + + case HTML_A: + /* + * If we are looking for client-side image maps, then handle an A + * within a MAP that has a COORDS attribute as an AREA tag. + * Unfortunately we lose the anchor text this way for the LYNXIMGMAP, + * we would have to do much more parsing to collect it. After + * potentially handling the A as AREA, always return immediately if + * only looking for image maps, without pushing anything on the style + * stack. - kw + */ + if (me->map_address && present && present[HTML_A_COORDS]) + LYStartArea(me, + present[HTML_A_HREF] ? value[HTML_A_HREF] : NULL, + NULL, + present[HTML_A_TITLE] ? value[HTML_A_TITLE] : NULL, + tag_charset); + if (LYMapsOnly) { + return HT_OK; + } + /* + * A may have been declared SGML_EMPTY in HTMLDTD.c, and + * SGML_character() in SGML.c may check for an A end tag to call + * HTML_end_element() directly (with a check in that to bypass + * decrementing of the HTML parser's stack), so if we have an open A, + * close that one now. - FM & kw + */ + if (me->inA) { + SET_SKIP_STACK(HTML_A); + HTML_end_element(me, HTML_A, include); + } + /* + * Set to know we are in an anchor. + */ + me->inA = TRUE; + + /* + * Load id_string if we have an ID or NAME. - FM + */ + if (present && present[HTML_A_ID] && + non_empty(value[HTML_A_ID])) { + StrAllocCopy(id_string, value[HTML_A_ID]); + } else if (present && present[HTML_A_NAME] && + non_empty(value[HTML_A_NAME])) { + StrAllocCopy(id_string, value[HTML_A_NAME]); + } + if (id_string) + TRANSLATE_AND_UNESCAPE_TO_STD(&id_string); + + /* + * Handle the reference. - FM + */ + if (present && present[HTML_A_HREF]) { + /* + * Set to know we are making the content bold. + */ + me->inBoldA = TRUE; + + if (isEmpty(value[HTML_A_HREF])) + StrAllocCopy(href, "#"); + else + StrAllocCopy(href, value[HTML_A_HREF]); + CHECK_FOR_INTERN(intern_flag, href); /* '#' */ + + if (intern_flag) { /*** FAST WAY: ***/ + TRANSLATE_AND_UNESCAPE_TO_STD(&href); + + } else { + url_type = LYLegitimizeHREF(me, &href, TRUE, TRUE); + + /* + * Deal with our ftp gateway kludge. - FM + */ + if (!url_type && !StrNCmp(href, "/foo/..", 7) && + (isFTP_URL(me->node_anchor->address) || + isFILE_URL(me->node_anchor->address))) { + for (i = 0; (href[i] = href[i + 7]) != 0; i++) ; + } + } + + if (present[HTML_A_ISMAP]) /*??? */ + intern_flag = FALSE; + } else { + if (bold_name_anchors == TRUE) { + me->inBoldA = TRUE; + } + } + + if (present && present[HTML_A_TYPE] && value[HTML_A_TYPE]) { + StrAllocCopy(temp, value[HTML_A_TYPE]); + if (!intern_flag && + !strcasecomp(value[HTML_A_TYPE], HTAtom_name(HTInternalLink)) && + !LYIsUIPage3(me->node_anchor->address, UIP_LIST_PAGE, 0) && + !LYIsUIPage3(me->node_anchor->address, UIP_ADDRLIST_PAGE, 0) && + !isLYNXIMGMAP(me->node_anchor->address)) { + /* Some kind of spoof? + * Found TYPE="internal link" but not in a valid context + * where we have written it. - kw + */ + CTRACE((tfp, "HTML: Found invalid HREF=\"%s\" TYPE=\"%s\"!\n", + href, temp)); + FREE(temp); + } + } + + me->CurrentA = HTAnchor_findChildAndLink(me->node_anchor, /* Parent */ + id_string, /* Tag */ + href, /* Address */ + (temp + ? (HTLinkType *) + HTAtom_for(temp) + : INTERN_LT)); /* Type */ + FREE(temp); + FREE(id_string); + + if (me->CurrentA && present) { + if (present[HTML_A_TITLE] && + non_empty(value[HTML_A_TITLE])) { + StrAllocCopy(title, value[HTML_A_TITLE]); + TRANSLATE_AND_UNESCAPE_ENTITIES(&title, TRUE, FALSE); + LYTrimHead(title); + LYTrimTail(title); + if (*title == '\0') { + FREE(title); + } + } + if (present[HTML_A_ISMAP]) + dest_ismap = TRUE; + if (present[HTML_A_CHARSET] && + non_empty(value[HTML_A_CHARSET])) { + /* + * Set up to load the anchor's chartrans structures + * appropriately for the current display character set if it + * can handle what's claimed. - FM + */ + StrAllocCopy(temp, value[HTML_A_CHARSET]); + TRANSLATE_AND_UNESCAPE_TO_STD(&temp); + dest_char_set = UCGetLYhndl_byMIME(temp); + if (dest_char_set < 0) { + dest_char_set = UCLYhndl_for_unrec; + } + } + if (title != NULL || dest_ismap == TRUE || dest_char_set >= 0) { + dest = HTAnchor_parent(HTAnchor_followLink(me->CurrentA) + ); + } + if (dest && title != NULL && HTAnchor_title(dest) == NULL) + HTAnchor_setTitle(dest, title); + if (dest && dest_ismap) + dest->isISMAPScript = TRUE; + /* Don't allow CHARSET attribute to change *this* document's + charset assumption. - kw */ + if (dest && dest != me->node_anchor && dest_char_set >= 0) { + /* + * Load the anchor's chartrans structures. This should be done + * more intelligently when setting up the structured object, + * but it gets the job done for now. - FM + */ + HTAnchor_setUCInfoStage(dest, dest_char_set, + UCT_STAGE_MIME, + UCT_SETBY_DEFAULT); + HTAnchor_setUCInfoStage(dest, dest_char_set, + UCT_STAGE_PARSER, + UCT_SETBY_LINK); + } + FREE(temp); + dest = NULL; + FREE(title); + } + me->CurrentANum = HText_beginAnchor(me->text, + me->inUnderline, me->CurrentA); + if (me->inBoldA == TRUE && me->inBoldH == FALSE) + HText_appendCharacter(me->text, LY_BOLD_START_CHAR); +#if defined(NOTUSED_FOTEMODS) + /* + * Close an HREF-less NAMED-ed now if we aren't making their content + * bold, and let the check in HTML_end_element() deal with any dangling + * end tag this creates. - FM + */ + if (href == NULL && me->inBoldA == FALSE) { + SET_SKIP_STACK(HTML_A); + HTML_end_element(me, HTML_A, include); + } +#else + /*Close an HREF-less NAMED-ed now if force_empty_hrefless_a was + requested - VH */ + if (href == NULL && force_empty_hrefless_a) { + SET_SKIP_STACK(HTML_A); + HTML_end_element(me, HTML_A, include); + } +#endif + FREE(href); + break; + + case HTML_IMG: /* Images */ + /* + * If we're in an anchor, get the destination, and if it's a clickable + * image for the current anchor, set our flags for faking a 0,0 + * coordinate pair, which typically returns the image's default. - FM + */ + if (me->inA && me->CurrentA) { + if ((dest = HTAnchor_parent(HTAnchor_followLink(me->CurrentA) + )) != NULL) { + if (dest->isISMAPScript == TRUE) { + dest_ismap = TRUE; + CTRACE((tfp, "HTML: '%s' is an ISMAP script\n", + dest->address)); + } else if (present && present[HTML_IMG_ISMAP]) { + dest_ismap = TRUE; + dest->isISMAPScript = TRUE; + CTRACE((tfp, "HTML: Designating '%s' as an ISMAP script\n", + dest->address)); + } + } + } + + intern_flag = FALSE; /* unless set below - kw */ + /* + * If there's a USEMAP, resolve it. - FM + */ + if (present && present[HTML_IMG_USEMAP] && + non_empty(value[HTML_IMG_USEMAP])) { + StrAllocCopy(map_href, value[HTML_IMG_USEMAP]); + CHECK_FOR_INTERN(intern_flag, map_href); + (void) LYLegitimizeHREF(me, &map_href, TRUE, TRUE); + /* + * If map_href ended up zero-length or otherwise doesn't have a + * hash, it can't be valid, so ignore it. - FM + */ + if (findPoundSelector(map_href) == NULL) { + FREE(map_href); + } + } + + /* + * Handle a MAP reference if we have one at this point. - FM + */ + if (map_href) { + /* + * If the MAP reference doesn't yet begin with a scheme, check + * whether a base tag is in effect. - FM + */ + /* + * If the USEMAP value is a lone fragment and LYSeekFragMAPinCur is + * set, we'll use the current document's URL for resolving. + * Otherwise use the BASE. - kw + */ + Base = ((me->inBASE && + !(*map_href == '#' && LYSeekFragMAPinCur == TRUE)) + ? me->base_href + : me->node_anchor->address); + HTParseALL(&map_href, Base); + + /* + * Prepend our client-side MAP access field. - FM + */ + StrAllocCopy(temp, STR_LYNXIMGMAP); + StrAllocCat(temp, map_href); + StrAllocCopy(map_href, temp); + FREE(temp); + } + + /* + * Check whether we want to suppress the server-side ISMAP link if a + * client-side MAP is present. - FM + */ + if (LYNoISMAPifUSEMAP && map_href && dest_ismap) { + dest_ismap = FALSE; + dest = NULL; + } + + /* + * Check for a TITLE attribute. - FM + */ + if (present && present[HTML_IMG_TITLE] && + non_empty(value[HTML_IMG_TITLE])) { + StrAllocCopy(title, value[HTML_IMG_TITLE]); + TRANSLATE_AND_UNESCAPE_ENTITIES(&title, TRUE, FALSE); + LYTrimHead(title); + LYTrimTail(title); + if (*title == '\0') { + FREE(title); + } + } + + /* + * If there's an ALT string, use it, unless the ALT string is + * zero-length or just spaces and we are making all SRCs links or have + * a USEMAP link. - FM + */ + if (((present) && + (present[HTML_IMG_ALT] && value[HTML_IMG_ALT])) && + (!clickable_images || + ((clickable_images || map_href) && + *value[HTML_IMG_ALT] != '\0'))) { + StrAllocCopy(alt_string, value[HTML_IMG_ALT]); + TRANSLATE_AND_UNESCAPE_ENTITIES(&alt_string, + me->UsePlainSpace, me->HiddenValue); + /* + * If it's all spaces and we are making SRC or USEMAP links, treat + * it as zero-length. - FM + */ + if (clickable_images || map_href) { + LYTrimHead(alt_string); + LYTrimTail(alt_string); + if (*alt_string == '\0') { + if (map_href) { + StrAllocCopy(alt_string, (title ? title : + (temp = MakeNewMapValue(value, + "USEMAP")))); + FREE(temp); + } else if (dest_ismap) { + StrAllocCopy(alt_string, (title ? title : + (temp = MakeNewMapValue(value, + "ISMAP")))); + FREE(temp); + + } else if (me->inA == TRUE && dest) { + StrAllocCopy(alt_string, (title ? + title : + VERBOSE_IMG(value, HTML_IMG_SRC, + "[LINK]"))); + + } else { + StrAllocCopy(alt_string, + (title ? title : + ((present && + present[HTML_IMG_ISOBJECT]) ? + "(OBJECT)" : + VERBOSE_IMG(value, HTML_IMG_SRC, + "[INLINE]")))); + } + } + } + + } else if (map_href) { + StrAllocCopy(alt_string, (title ? title : + (temp = MakeNewMapValue(value, "USEMAP")))); + FREE(temp); + + } else if ((dest_ismap == TRUE) || + (me->inA && present && present[HTML_IMG_ISMAP])) { + StrAllocCopy(alt_string, (title ? title : + (temp = MakeNewMapValue(value, "ISMAP")))); + FREE(temp); + + } else if (me->inA == TRUE && dest) { + StrAllocCopy(alt_string, (title ? + title : + VERBOSE_IMG(value, HTML_IMG_SRC, + "[LINK]"))); + + } else { + if (pseudo_inline_alts || clickable_images) + StrAllocCopy(alt_string, (title ? title : + ((present && + present[HTML_IMG_ISOBJECT]) ? + "(OBJECT)" : + VERBOSE_IMG(value, HTML_IMG_SRC, + "[INLINE]")))); + else + StrAllocCopy(alt_string, NonNull(title)); + } + if (*alt_string == '\0' && map_href) { + StrAllocCopy(alt_string, (temp = MakeNewMapValue(value, "USEMAP"))); + FREE(temp); + } + + CTRACE((tfp, "HTML IMG: USEMAP=%d ISMAP=%d ANCHOR=%d PARA=%d\n", + map_href ? 1 : 0, + (dest_ismap == TRUE) ? 1 : 0, + me->inA, me->inP)); + + /* + * Check for an ID attribute. - FM + */ + if (present && present[HTML_IMG_ID] && + non_empty(value[HTML_IMG_ID])) { + StrAllocCopy(id_string, value[HTML_IMG_ID]); + TRANSLATE_AND_UNESCAPE_TO_STD(&id_string); + if (*id_string == '\0') { + FREE(id_string); + } + } + + /* + * Create links to the SRC for all images, if desired. - FM + */ + if (clickable_images && + present && present[HTML_IMG_SRC] && + non_empty(value[HTML_IMG_SRC])) { + StrAllocCopy(href, value[HTML_IMG_SRC]); + LYLegitimizeHREF(me, &href, TRUE, TRUE); + + /* + * If it's an ISMAP and/or USEMAP, or graphic for an anchor, end + * that anchor and start one for the SRC. - FM + */ + if (me->inA) { + /* + * If we have a USEMAP, end this anchor and start a new one for + * the client-side MAP. - FM + */ + if (map_href) { + if (dest_ismap) { + HTML_put_character(me, ' '); + me->in_word = NO; + HTML_put_string(me, + (temp = MakeNewMapValue(value, "ISMAP"))); + FREE(temp); + } else if (dest) { + HTML_put_character(me, ' '); + me->in_word = NO; + HTML_put_string(me, "[LINK]"); + } + if (me->inBoldA == TRUE && me->inBoldH == FALSE) { + HText_appendCharacter(me->text, LY_BOLD_END_CHAR); + } + me->inBoldA = FALSE; + HText_endAnchor(me->text, me->CurrentANum); + me->CurrentANum = 0; + if (dest_ismap || dest) + HTML_put_character(me, '-'); + if (id_string) { + if ((ID_A = HTAnchor_findChildAndLink(me->node_anchor, /* Parent */ + id_string, /* Tag */ + NULL, /* Address */ + 0)) != NULL) { /* Type */ + HText_beginAnchor(me->text, me->inUnderline, ID_A); + HText_endAnchor(me->text, 0); + } + } + me->CurrentA = HTAnchor_findChildAndLink(me->node_anchor, /* Parent */ + NULL, /* Tag */ + map_href, /* Address */ + INTERN_LT); /* Type */ + if (me->CurrentA && title) { + if ((dest = HTAnchor_parent(HTAnchor_followLink(me->CurrentA) + )) != NULL) { + if (!HTAnchor_title(dest)) + HTAnchor_setTitle(dest, title); + } + } + me->CurrentANum = HText_beginAnchor(me->text, + me->inUnderline, + me->CurrentA); + if (me->inBoldA == FALSE && me->inBoldH == FALSE) { + HText_appendCharacter(me->text, LY_BOLD_START_CHAR); + } + me->inBoldA = TRUE; + } else { + HTML_put_character(me, ' '); /* space char may be ignored */ + me->in_word = NO; + } + HTML_put_string(me, alt_string); + if (me->inBoldA == TRUE && me->inBoldH == FALSE) { + HText_appendCharacter(me->text, LY_BOLD_END_CHAR); + } + me->inBoldA = FALSE; + HText_endAnchor(me->text, me->CurrentANum); + me->CurrentANum = 0; + HTML_put_character(me, '-'); + FREE(newtitle); + StrAllocCopy(alt_string, + ((present && + present[HTML_IMG_ISOBJECT]) ? + ((map_href || dest_ismap) ? + "(IMAGE)" : "(OBJECT)") : + VERBOSE_IMG(value, HTML_IMG_SRC, "[IMAGE]"))); + if (id_string && !map_href) { + if ((ID_A = HTAnchor_findChildAndLink(me->node_anchor, /* Parent */ + id_string, /* Tag */ + NULL, /* Address */ + 0)) != NULL) { /* Type */ + HText_beginAnchor(me->text, me->inUnderline, ID_A); + HText_endAnchor(me->text, 0); + } + } + } else if (map_href) { + HTML_put_character(me, ' '); /* space char may be ignored */ + me->in_word = NO; + if (id_string) { + if ((ID_A = HTAnchor_findChildAndLink(me->node_anchor, /* Parent */ + id_string, /* Tag */ + NULL, /* Address */ + 0)) != NULL) { /* Type */ + HText_beginAnchor(me->text, me->inUnderline, ID_A); + HText_endAnchor(me->text, 0); + } + } + me->CurrentA = HTAnchor_findChildAndLink(me->node_anchor, /* Parent */ + NULL, /* Tag */ + map_href, /* Address */ + INTERN_LT); /* Type */ + if (me->CurrentA && title) { + if ((dest = HTAnchor_parent(HTAnchor_followLink(me->CurrentA) + )) != NULL) { + if (!HTAnchor_title(dest)) + HTAnchor_setTitle(dest, title); + } + } + me->CurrentANum = HText_beginAnchor(me->text, + me->inUnderline, + me->CurrentA); + if (me->inBoldA == FALSE && me->inBoldH == FALSE) + HText_appendCharacter(me->text, LY_BOLD_START_CHAR); + me->inBoldA = TRUE; + HTML_put_string(me, alt_string); + if (me->inBoldA == TRUE && me->inBoldH == FALSE) { + HText_appendCharacter(me->text, LY_BOLD_END_CHAR); + } + me->inBoldA = FALSE; + HText_endAnchor(me->text, me->CurrentANum); + me->CurrentANum = 0; + HTML_put_character(me, '-'); + FREE(newtitle); + StrAllocCopy(alt_string, + ((present && + present[HTML_IMG_ISOBJECT]) ? + "(IMAGE)" : + VERBOSE_IMG(value, HTML_IMG_SRC, "[IMAGE]"))); + } else { + HTML_put_character(me, ' '); /* space char may be ignored */ + me->in_word = NO; + if (id_string) { + if ((ID_A = HTAnchor_findChildAndLink(me->node_anchor, /* Parent */ + id_string, /* Tag */ + NULL, /* Address */ + 0)) != NULL) { /* Type */ + HText_beginAnchor(me->text, me->inUnderline, ID_A); + HText_endAnchor(me->text, 0); + } + } + } + + /* + * Create the link to the SRC. - FM + */ + me->CurrentA = HTAnchor_findChildAndLink(me->node_anchor, /* Parent */ + NULL, /* Tag */ + href, /* Address */ + (HTLinkType *) 0); /* Type */ + FREE(href); + me->CurrentANum = HText_beginAnchor(me->text, + me->inUnderline, + me->CurrentA); + if (me->inBoldH == FALSE) + HText_appendCharacter(me->text, LY_BOLD_START_CHAR); + HTML_put_string(me, alt_string); + if (!me->inA) { + if (me->inBoldH == FALSE) + HText_appendCharacter(me->text, LY_BOLD_END_CHAR); + HText_endAnchor(me->text, me->CurrentANum); + me->CurrentANum = 0; + HTML_put_character(me, ' '); /* space char may be ignored */ + me->in_word = NO; + } else { + HTML_put_character(me, ' '); /* space char may be ignored */ + me->in_word = NO; + me->inBoldA = TRUE; + } + } else if (map_href) { + if (me->inA) { + /* + * We're in an anchor and have a USEMAP, so end the anchor and + * start a new one for the client-side MAP. - FM + */ + if (dest_ismap) { + HTML_put_character(me, ' '); /* space char may be ignored */ + me->in_word = NO; + HTML_put_string(me, (temp = MakeNewMapValue(value, "ISMAP"))); + FREE(temp); + } else if (dest) { + HTML_put_character(me, ' '); /* space char may be ignored */ + me->in_word = NO; + HTML_put_string(me, "[LINK]"); + } + if (me->inBoldA == TRUE && me->inBoldH == FALSE) { + HText_appendCharacter(me->text, LY_BOLD_END_CHAR); + } + me->inBoldA = FALSE; + HText_endAnchor(me->text, me->CurrentANum); + me->CurrentANum = 0; + if (dest_ismap || dest) { + HTML_put_character(me, '-'); + } + } else { + HTML_put_character(me, ' '); + me->in_word = NO; + } + me->CurrentA = HTAnchor_findChildAndLink(me->node_anchor, /* Parent */ + NULL, /* Tag */ + map_href, /* Address */ + INTERN_LT); /* Type */ + if (me->CurrentA && title) { + if ((dest = HTAnchor_parent(HTAnchor_followLink(me->CurrentA) + )) != NULL) { + if (!HTAnchor_title(dest)) + HTAnchor_setTitle(dest, title); + } + } + me->CurrentANum = HText_beginAnchor(me->text, + me->inUnderline, + me->CurrentA); + if (me->inBoldA == FALSE && me->inBoldH == FALSE) { + HText_appendCharacter(me->text, LY_BOLD_START_CHAR); + } + me->inBoldA = TRUE; + HTML_put_string(me, alt_string); + if (!me->inA) { + if (me->inBoldA == TRUE && me->inBoldH == FALSE) { + HText_appendCharacter(me->text, LY_BOLD_END_CHAR); + } + me->inBoldA = FALSE; + HText_endAnchor(me->text, me->CurrentANum); + me->CurrentANum = 0; + } + } else { + /* + * Just put in the ALT or pseudo-ALT string for the current anchor + * or inline, with an ID link if indicated. - FM + */ + HTML_put_character(me, ' '); /* space char may be ignored */ + me->in_word = NO; + if (id_string) { + if ((ID_A = HTAnchor_findChildAndLink(me->node_anchor, /* Parent */ + id_string, /* Tag */ + NULL, /* Address */ + (HTLinkType *) 0)) != NULL) { /* Type */ + HText_beginAnchor(me->text, me->inUnderline, ID_A); + HText_endAnchor(me->text, 0); + } + } + HTML_put_string(me, alt_string); + HTML_put_character(me, ' '); /* space char may be ignored */ + me->in_word = NO; + } + FREE(map_href); + FREE(alt_string); + FREE(id_string); + FREE(title); + FREE(newtitle); + dest = NULL; + break; + + case HTML_MAP: + /* + * Load id_string if we have a NAME or ID. - FM + */ + if (present && present[HTML_MAP_NAME] && + non_empty(value[HTML_MAP_NAME])) { + StrAllocCopy(id_string, value[HTML_MAP_NAME]); + } else if (present && present[HTML_MAP_ID] && + non_empty(value[HTML_MAP_ID])) { + StrAllocCopy(id_string, value[HTML_MAP_ID]); + } + if (id_string) { + TRANSLATE_AND_UNESCAPE_TO_STD(&id_string); + if (*id_string == '\0') { + FREE(id_string); + } + } + + /* + * Generate a target anchor in this place in the containing document. + * MAP can now contain block markup, if it doesn't contain any AREAs + * (or A anchors with COORDS converted to AREAs) the current location + * can be used as a fallback for following a USEMAP link. - kw + */ + if (!LYMapsOnly) + LYHandleID(me, id_string); + + /* + * Load map_address. - FM + */ + if (id_string) { + /* + * The MAP must be in the current stream, even if it had a BASE + * tag, so we'll use its address here, but still use the BASE, if + * present, when resolving the AREA elements in it's content, + * unless the AREA's HREF is a lone fragment and + * LYSeekFragAREAinCur is set. - FM && KW + */ + StrAllocCopy(me->map_address, me->node_anchor->address); + if ((cp = StrChr(me->map_address, '#')) != NULL) + *cp = '\0'; + StrAllocCat(me->map_address, "#"); + StrAllocCat(me->map_address, id_string); + FREE(id_string); + if (present && present[HTML_MAP_TITLE] && + non_empty(value[HTML_MAP_TITLE])) { + StrAllocCopy(title, value[HTML_MAP_TITLE]); + TRANSLATE_AND_UNESCAPE_ENTITIES(&title, TRUE, FALSE); + LYTrimHead(title); + LYTrimTail(title); + if (*title == '\0') { + FREE(title); + } + } + LYAddImageMap(me->map_address, title, me->node_anchor); + FREE(title); + } + break; + + case HTML_AREA: + if (me->map_address && + present && present[HTML_AREA_HREF] && + non_empty(value[HTML_AREA_HREF])) { + /* + * Resolve the HREF. - FM + */ + StrAllocCopy(href, value[HTML_AREA_HREF]); + CHECK_FOR_INTERN(intern_flag, href); + (void) LYLegitimizeHREF(me, &href, TRUE, TRUE); + + /* + * Check whether a BASE tag is in effect, and use it for resolving, + * even though we used this stream's address for locating the MAP + * itself, unless the HREF is a lone fragment and + * LYSeekFragAREAinCur is set. - FM + */ + Base = (((me->inBASE && *href != '\0') && + !(*href == '#' && LYSeekFragAREAinCur == TRUE)) + ? me->base_href + : me->node_anchor->address); + HTParseALL(&href, Base); + + /* + * Check for an ALT. - FM + */ + if (present[HTML_AREA_ALT] && + non_empty(value[HTML_AREA_ALT])) { + StrAllocCopy(alt_string, value[HTML_AREA_ALT]); + } else if (present[HTML_AREA_TITLE] && + non_empty(value[HTML_AREA_TITLE])) { + /* + * Use the TITLE as an ALT. - FM + */ + StrAllocCopy(alt_string, value[HTML_AREA_TITLE]); + } + if (alt_string != NULL) { + TRANSLATE_AND_UNESCAPE_ENTITIES(&alt_string, + me->UsePlainSpace, + me->HiddenValue); + /* + * Make sure it's not just space(s). - FM + */ + LYTrimHead(alt_string); + LYTrimTail(alt_string); + if (*alt_string == '\0') { + StrAllocCopy(alt_string, href); + } + } else { + /* + * Use the HREF as an ALT. - FM + */ + StrAllocCopy(alt_string, href); + } + + LYAddMapElement(me->map_address, href, alt_string, + me->node_anchor, intern_flag); + FREE(href); + FREE(alt_string); + } + break; + + case HTML_PARAM: + /* + * We may need to look at this someday to deal with MAPs, OBJECTs or + * APPLETs optimally, but just ignore it for now. - FM + */ + break; + + case HTML_BODYTEXT: + CHECK_ID(HTML_BODYTEXT_ID); + /* + * We may need to look at this someday to deal with OBJECTs optimally, + * but just ignore it for now. - FM + */ + break; + + case HTML_TEXTFLOW: + CHECK_ID(HTML_BODYTEXT_ID); + /* + * We may need to look at this someday to deal with APPLETs optimally, + * but just ignore it for now. - FM + */ + break; + + case HTML_FIG: + if (present) + LYHandleFIG(me, present, value, + present[HTML_FIG_ISOBJECT], + present[HTML_FIG_IMAGEMAP], + present[HTML_FIG_ID] ? value[HTML_FIG_ID] : NULL, + present[HTML_FIG_SRC] ? value[HTML_FIG_SRC] : NULL, + YES, TRUE, &intern_flag); + else + LYHandleFIG(me, NULL, NULL, + 0, + 0, + NULL, + NULL, YES, TRUE, &intern_flag); + break; + + case HTML_OBJECT: + if (!me->object_started) { + /* + * This is an outer OBJECT start tag, i.e., not a nested OBJECT, so + * save its relevant attributes. - FM + */ + if (present) { + if (present[HTML_OBJECT_DECLARE]) + me->object_declare = TRUE; + if (present[HTML_OBJECT_SHAPES]) + me->object_shapes = TRUE; + if (present[HTML_OBJECT_ISMAP]) + me->object_ismap = TRUE; + if (present[HTML_OBJECT_USEMAP] && + non_empty(value[HTML_OBJECT_USEMAP])) { + StrAllocCopy(me->object_usemap, value[HTML_OBJECT_USEMAP]); + TRANSLATE_AND_UNESCAPE_TO_STD(&me->object_usemap); + if (*me->object_usemap == '\0') { + FREE(me->object_usemap); + } + } + if (present[HTML_OBJECT_ID] && + non_empty(value[HTML_OBJECT_ID])) { + StrAllocCopy(me->object_id, value[HTML_OBJECT_ID]); + TRANSLATE_AND_UNESCAPE_TO_STD(&me->object_id); + if (*me->object_id == '\0') { + FREE(me->object_id); + } + } + if (present[HTML_OBJECT_TITLE] && + non_empty(value[HTML_OBJECT_TITLE])) { + StrAllocCopy(me->object_title, value[HTML_OBJECT_TITLE]); + TRANSLATE_AND_UNESCAPE_ENTITIES(&me->object_title, TRUE, FALSE); + LYTrimHead(me->object_title); + LYTrimTail(me->object_title); + if (*me->object_title == '\0') { + FREE(me->object_title); + } + } + if (present[HTML_OBJECT_DATA] && + non_empty(value[HTML_OBJECT_DATA])) { + StrAllocCopy(me->object_data, value[HTML_OBJECT_DATA]); + TRANSLATE_AND_UNESCAPE_TO_STD(&me->object_data); + if (*me->object_data == '\0') { + FREE(me->object_data); + } + } + if (present[HTML_OBJECT_TYPE] && + non_empty(value[HTML_OBJECT_TYPE])) { + StrAllocCopy(me->object_type, value[HTML_OBJECT_TYPE]); + TRANSLATE_AND_UNESCAPE_ENTITIES(&me->object_type, TRUE, FALSE); + LYTrimHead(me->object_type); + LYTrimTail(me->object_type); + if (*me->object_type == '\0') { + FREE(me->object_type); + } + } + if (present[HTML_OBJECT_CLASSID] && + non_empty(value[HTML_OBJECT_CLASSID])) { + StrAllocCopy(me->object_classid, + value[HTML_OBJECT_CLASSID]); + TRANSLATE_AND_UNESCAPE_ENTITIES(&me->object_classid, TRUE, FALSE); + LYTrimHead(me->object_classid); + LYTrimTail(me->object_classid); + if (*me->object_classid == '\0') { + FREE(me->object_classid); + } + } + if (present[HTML_OBJECT_CODEBASE] && + non_empty(value[HTML_OBJECT_CODEBASE])) { + StrAllocCopy(me->object_codebase, + value[HTML_OBJECT_CODEBASE]); + TRANSLATE_AND_UNESCAPE_TO_STD(&me->object_codebase); + if (*me->object_codebase == '\0') { + FREE(me->object_codebase); + } + } + if (present[HTML_OBJECT_CODETYPE] && + non_empty(value[HTML_OBJECT_CODETYPE])) { + StrAllocCopy(me->object_codetype, + value[HTML_OBJECT_CODETYPE]); + TRANSLATE_AND_UNESCAPE_ENTITIES(&me->object_codetype, + TRUE, + FALSE); + LYTrimHead(me->object_codetype); + LYTrimTail(me->object_codetype); + if (*me->object_codetype == '\0') { + FREE(me->object_codetype); + } + } + if (present[HTML_OBJECT_NAME] && + non_empty(value[HTML_OBJECT_NAME])) { + StrAllocCopy(me->object_name, value[HTML_OBJECT_NAME]); + TRANSLATE_AND_UNESCAPE_ENTITIES(&me->object_name, TRUE, FALSE); + LYTrimHead(me->object_name); + LYTrimTail(me->object_name); + if (*me->object_name == '\0') { + FREE(me->object_name); + } + } + } + /* + * If we can determine now that we are not going to do anything + * special to the OBJECT element's SGML contents, like skipping it + * completely or collecting it up in order to add something after + * it, then generate any output that should be emitted in the place + * of the OBJECT start tag NOW, then don't initialize special + * handling but return, letting our SGML parser know that further + * content is to be parsed normally not literally. We could defer + * this until we have collected the contents and then recycle the + * contents (as was previously always done), but that has a higher + * chance of completely losing content in case of nesting errors in + * the input, incomplete transmissions, etc. - kw + */ + if ((!present || + (me->object_declare == FALSE && me->object_name == NULL && + me->object_shapes == FALSE && me->object_usemap == NULL))) { + if (!LYMapsOnly) { + if (!clickable_images || me->object_data == NULL || + !(me->object_data != NULL && + me->object_classid == NULL && + me->object_codebase == NULL && + me->object_codetype == NULL)) + FREE(me->object_data); + if (me->object_data) { + HTStartAnchor5(me, + (me->object_id + ? value[HTML_OBJECT_ID] + : NULL), + value[HTML_OBJECT_DATA], + value[HTML_OBJECT_TYPE], + tag_charset); + if ((me->object_type != NULL) && + !strncasecomp(me->object_type, "image/", 6)) + HTML_put_string(me, "(IMAGE)"); + else + HTML_put_string(me, "(OBJECT)"); + HTML_end_element(me, HTML_A, NULL); + } else if (me->object_id) + LYHandleID(me, me->object_id); + } + clear_objectdata(me); + /* + * We do NOT want the HTML_put_* functions that are going to be + * called for the OBJECT's character content to add to the + * chunk, so we don't push on the stack. Instead we keep a + * counter for open OBJECT tags that are treated this way, so + * HTML_end_element can skip handling the corresponding end tag + * that is going to arrive unexpectedly as far as our stack is + * concerned. + */ + status = HT_PARSER_OTHER_CONTENT; + if (me->sp[0].tag_number == HTML_FIG && + me->objects_figged_open > 0) { + ElementNumber = (HTMLElement) HTML_OBJECT_M; + } else { + me->objects_mixed_open++; + SET_SKIP_STACK(HTML_OBJECT); + } + } else if (me->object_declare == FALSE && me->object_name == NULL && + me->object_shapes == TRUE) { + LYHandleFIG(me, present, value, + 1, + 1 || me->object_ismap, + me->object_id, + ((me->object_data && !me->object_classid) + ? value[HTML_OBJECT_DATA] + : NULL), + NO, TRUE, &intern_flag); + clear_objectdata(me); + status = HT_PARSER_OTHER_CONTENT; + me->objects_figged_open++; + ElementNumber = HTML_FIG; + + } else { + /* + * Set flag that we are accumulating OBJECT content. - FM + */ + me->object_started = TRUE; + } + } + break; + + case HTML_OVERLAY: + if (clickable_images && me->inFIG && + present && present[HTML_OVERLAY_SRC] && + non_empty(value[HTML_OVERLAY_SRC])) { + StrAllocCopy(href, value[HTML_OVERLAY_SRC]); + LYLegitimizeHREF(me, &href, TRUE, TRUE); + if (*href) { + + if (me->inA) { + SET_SKIP_STACK(HTML_A); + HTML_end_element(me, HTML_A, include); + } + me->CurrentA = HTAnchor_findChildAndLink(me->node_anchor, /* Parent */ + NULL, /* Tag */ + href, /* Address */ + (HTLinkType *) 0); /* Type */ + HTML_put_character(me, ' '); + HText_appendCharacter(me->text, '+'); + me->CurrentANum = HText_beginAnchor(me->text, + me->inUnderline, + me->CurrentA); + if (me->inBoldH == FALSE) + HText_appendCharacter(me->text, LY_BOLD_START_CHAR); + HTML_put_string(me, "[OVERLAY]"); + if (me->inBoldH == FALSE) + HText_appendCharacter(me->text, LY_BOLD_END_CHAR); + HText_endAnchor(me->text, me->CurrentANum); + HTML_put_character(me, ' '); + me->in_word = NO; + } + FREE(href); + } + break; + + case HTML_APPLET: + me->inAPPLET = TRUE; + me->inAPPLETwithP = FALSE; + HTML_put_character(me, ' '); /* space char may be ignored */ + /* + * Load id_string if we have an ID or NAME. - FM + */ + if (present && present[HTML_APPLET_ID] && + non_empty(value[HTML_APPLET_ID])) { + StrAllocCopy(id_string, value[HTML_APPLET_ID]); + } else if (present && present[HTML_APPLET_NAME] && + non_empty(value[HTML_APPLET_NAME])) { + StrAllocCopy(id_string, value[HTML_APPLET_NAME]); + } + if (id_string) { + TRANSLATE_AND_UNESCAPE_TO_STD(&id_string); + LYHandleID(me, id_string); + FREE(id_string); + } + me->in_word = NO; + + /* + * If there's an ALT string, use it, unless the ALT string is + * zero-length and we are making all sources links. - FM + */ + if (present && present[HTML_APPLET_ALT] && value[HTML_APPLET_ALT] && + (!clickable_images || + (clickable_images && *value[HTML_APPLET_ALT] != '\0'))) { + StrAllocCopy(alt_string, value[HTML_APPLET_ALT]); + TRANSLATE_AND_UNESCAPE_ENTITIES(&alt_string, + me->UsePlainSpace, me->HiddenValue); + /* + * If it's all spaces and we are making sources links, treat it as + * zero-length. - FM + */ + if (clickable_images) { + LYTrimHead(alt_string); + LYTrimTail(alt_string); + if (*alt_string == '\0') { + StrAllocCopy(alt_string, "[APPLET]"); + } + } + + } else { + if (clickable_images) + StrAllocCopy(alt_string, "[APPLET]"); + else + StrAllocCopy(alt_string, ""); + } + + /* + * If we're making all sources links, get the source. - FM + */ + if (clickable_images && present && present[HTML_APPLET_CODE] && + non_empty(value[HTML_APPLET_CODE])) { + char *base = NULL; + + Base = (me->inBASE) + ? me->base_href + : me->node_anchor->address; + /* + * Check for a CODEBASE attribute. - FM + */ + if (present[HTML_APPLET_CODEBASE] && + non_empty(value[HTML_APPLET_CODEBASE])) { + StrAllocCopy(base, value[HTML_APPLET_CODEBASE]); + LYRemoveBlanks(base); + TRANSLATE_AND_UNESCAPE_TO_STD(&base); + /* + * Force it to be a directory. - FM + */ + if (*base == '\0') + StrAllocCopy(base, "/"); + LYAddHtmlSep(&base); + LYLegitimizeHREF(me, &base, TRUE, FALSE); + + HTParseALL(&base, Base); + } + + StrAllocCopy(href, value[HTML_APPLET_CODE]); + LYLegitimizeHREF(me, &href, TRUE, FALSE); + HTParseALL(&href, (base ? base : Base)); + FREE(base); + + if (*href) { + if (me->inA) { + if (me->inBoldA == TRUE && me->inBoldH == FALSE) + HText_appendCharacter(me->text, LY_BOLD_END_CHAR); + HText_endAnchor(me->text, me->CurrentANum); + HTML_put_character(me, '-'); + } + me->CurrentA = HTAnchor_findChildAndLink(me->node_anchor, /* Parent */ + NULL, /* Tag */ + href, /* Address */ + (HTLinkType *) 0); /* Type */ + me->CurrentANum = HText_beginAnchor(me->text, + me->inUnderline, + me->CurrentA); + if (me->inBoldH == FALSE) + HText_appendCharacter(me->text, LY_BOLD_START_CHAR); + HTML_put_string(me, alt_string); + if (me->inA == FALSE) { + if (me->inBoldH == FALSE) + HText_appendCharacter(me->text, LY_BOLD_END_CHAR); + HText_endAnchor(me->text, me->CurrentANum); + me->CurrentANum = 0; + } + HTML_put_character(me, ' '); /* space char may be ignored */ + me->in_word = NO; + } + FREE(href); + } else if (*alt_string) { + /* + * Just put up the ALT string, if non-zero. - FM + */ + HTML_put_string(me, alt_string); + HTML_put_character(me, ' '); /* space char may be ignored */ + me->in_word = NO; + } + FREE(alt_string); + FREE(id_string); + break; + + case HTML_BGSOUND: + /* + * If we're making all sources links, get the source. - FM + */ + if (clickable_images && present && present[HTML_BGSOUND_SRC] && + non_empty(value[HTML_BGSOUND_SRC])) { + StrAllocCopy(href, value[HTML_BGSOUND_SRC]); + LYLegitimizeHREF(me, &href, TRUE, TRUE); + if (*href == '\0') { + FREE(href); + break; + } + + if (me->inA) { + if (me->inBoldA == TRUE && me->inBoldH == FALSE) + HText_appendCharacter(me->text, LY_BOLD_END_CHAR); + HText_endAnchor(me->text, me->CurrentANum); + HTML_put_character(me, '-'); + } else { + HTML_put_character(me, ' '); /* space char may be ignored */ + me->in_word = NO; + } + me->CurrentA = HTAnchor_findChildAndLink(me->node_anchor, /* Parent */ + NULL, /* Tag */ + href, /* Address */ + (HTLinkType *) 0); /* Type */ + me->CurrentANum = HText_beginAnchor(me->text, + me->inUnderline, + me->CurrentA); + if (me->inBoldH == FALSE) + HText_appendCharacter(me->text, LY_BOLD_START_CHAR); + HTML_put_string(me, "[BGSOUND]"); + if (me->inA == FALSE) { + if (me->inBoldH == FALSE) + HText_appendCharacter(me->text, LY_BOLD_END_CHAR); + HText_endAnchor(me->text, me->CurrentANum); + me->CurrentANum = 0; + } + HTML_put_character(me, ' '); /* space char may be ignored */ + me->in_word = NO; + FREE(href); + } + break; + + case HTML_EMBED: + if (pseudo_inline_alts || clickable_images) + HTML_put_character(me, ' '); /* space char may be ignored */ + /* + * Load id_string if we have an ID or NAME. - FM + */ + if (present && present[HTML_EMBED_ID] && + non_empty(value[HTML_EMBED_ID])) { + StrAllocCopy(id_string, value[HTML_EMBED_ID]); + } else if (present && present[HTML_EMBED_NAME] && + non_empty(value[HTML_EMBED_NAME])) { + StrAllocCopy(id_string, value[HTML_EMBED_NAME]); + } + if (id_string) { + TRANSLATE_AND_UNESCAPE_TO_STD(&id_string); + LYHandleID(me, id_string); + FREE(id_string); + } + if (pseudo_inline_alts || clickable_images) + me->in_word = NO; + + /* + * If there's an ALT string, use it, unless the ALT string is + * zero-length and we are making all sources links. - FM + */ + if (present && present[HTML_EMBED_ALT] && value[HTML_EMBED_ALT] && + (!clickable_images || + (clickable_images && *value[HTML_EMBED_ALT] != '\0'))) { + StrAllocCopy(alt_string, value[HTML_EMBED_ALT]); + TRANSLATE_AND_UNESCAPE_ENTITIES(&alt_string, + me->UsePlainSpace, me->HiddenValue); + /* + * If it's all spaces and we are making sources links, treat it as + * zero-length. - FM + */ + if (clickable_images) { + LYTrimHead(alt_string); + LYTrimTail(alt_string); + if (*alt_string == '\0') { + StrAllocCopy(alt_string, "[EMBED]"); + } + } + } else { + if (pseudo_inline_alts || clickable_images) + StrAllocCopy(alt_string, "[EMBED]"); + else + StrAllocCopy(alt_string, ""); + } + + /* + * If we're making all sources links, get the source. - FM + */ + if (clickable_images && present && present[HTML_EMBED_SRC] && + non_empty(value[HTML_EMBED_SRC])) { + StrAllocCopy(href, value[HTML_EMBED_SRC]); + LYLegitimizeHREF(me, &href, TRUE, TRUE); + if (*href) { + if (me->inA) { + if (me->inBoldA == TRUE && me->inBoldH == FALSE) + HText_appendCharacter(me->text, LY_BOLD_END_CHAR); + HText_endAnchor(me->text, me->CurrentANum); + HTML_put_character(me, '-'); + } + me->CurrentA = HTAnchor_findChildAndLink(me->node_anchor, /* Parent */ + NULL, /* Tag */ + href, /* Address */ + (HTLinkType *) 0); /* Type */ + me->CurrentANum = HText_beginAnchor(me->text, + me->inUnderline, + me->CurrentA); + if (me->inBoldH == FALSE) + HText_appendCharacter(me->text, LY_BOLD_START_CHAR); + HTML_put_string(me, alt_string); + if (me->inBoldH == FALSE) + HText_appendCharacter(me->text, LY_BOLD_END_CHAR); + if (me->inA == FALSE) { + if (me->inBoldH == FALSE) + HText_appendCharacter(me->text, LY_BOLD_END_CHAR); + HText_endAnchor(me->text, me->CurrentANum); + me->CurrentANum = 0; + } + HTML_put_character(me, ' '); + me->in_word = NO; + } + FREE(href); + } else if (*alt_string) { + /* + * Just put up the ALT string, if non-zero. - FM + */ + HTML_put_string(me, alt_string); + HTML_put_character(me, ' '); /* space char may be ignored */ + me->in_word = NO; + } + FREE(alt_string); + FREE(id_string); + break; + + case HTML_CREDIT: + LYEnsureDoubleSpace(me); + LYResetParagraphAlignment(me); + me->inCREDIT = TRUE; + CHECK_ID(HTML_GEN_ID); + if (me->inUnderline == FALSE) + HText_appendCharacter(me->text, LY_UNDERLINE_START_CHAR); + HTML_put_string(me, "CREDIT:"); + if (me->inUnderline == FALSE) + HText_appendCharacter(me->text, LY_UNDERLINE_END_CHAR); + HTML_put_character(me, ' '); + CAN_JUSTIFY_START; + + if (me->inFIG) + /* + * Assume all text in the FIG container is intended to be + * paragraphed. - FM + */ + me->inFIGwithP = TRUE; + + if (me->inAPPLET) + /* + * Assume all text in the APPLET container is intended to be + * paragraphed. - FM + */ + me->inAPPLETwithP = TRUE; + + me->inLABEL = TRUE; + me->in_word = NO; + me->inP = FALSE; + break; + + case HTML_CAPTION: + LYEnsureDoubleSpace(me); + LYResetParagraphAlignment(me); + me->inCAPTION = TRUE; + CHECK_ID(HTML_CAPTION_ID); + if (me->inUnderline == FALSE) + HText_appendCharacter(me->text, LY_UNDERLINE_START_CHAR); + HTML_put_string(me, "CAPTION:"); + if (me->inUnderline == FALSE) + HText_appendCharacter(me->text, LY_UNDERLINE_END_CHAR); + HTML_put_character(me, ' '); + CAN_JUSTIFY_START; + + if (me->inFIG) + /* + * Assume all text in the FIG container is intended to be + * paragraphed. - FM + */ + me->inFIGwithP = TRUE; + + if (me->inAPPLET) + /* + * Assume all text in the APPLET container is intended to be + * paragraphed. - FM + */ + me->inAPPLETwithP = TRUE; + + me->inLABEL = TRUE; + me->in_word = NO; + me->inP = FALSE; + break; + + case HTML_FORM: + { + char *action = NULL; + char *method = NULL; + char *enctype = NULL; + const char *accept_cs = NULL; + + HTChildAnchor *source; + HTAnchor *link_dest; + + /* + * FORM may have been declared SGML_EMPTY in HTMLDTD.c, and + * SGML_character() in SGML.c may check for a FORM end tag to call + * HTML_end_element() directly (with a check in that to bypass + * decrementing of the HTML parser's stack), so if we have an open + * FORM, close that one now. - FM + */ + if (me->inFORM) { + CTRACE((tfp, "HTML: Missing FORM end tag. Faking it!\n")); + SET_SKIP_STACK(HTML_FORM); + HTML_end_element(me, HTML_FORM, include); + } + + /* + * Set to know we are in a new form. + */ + me->inFORM = TRUE; + EMIT_IFDEF_USE_JUSTIFY_ELTS(form_in_htext = TRUE); + + if (present && present[HTML_FORM_ACCEPT_CHARSET]) { + accept_cs = (value[HTML_FORM_ACCEPT_CHARSET] + ? value[HTML_FORM_ACCEPT_CHARSET] + : "UNKNOWN"); + } + + Base = (me->inBASE) + ? me->base_href + : me->node_anchor->address; + + if (present && present[HTML_FORM_ACTION] && + value[HTML_FORM_ACTION]) { + + StrAllocCopy(action, value[HTML_FORM_ACTION]); + LYLegitimizeHREF(me, &action, TRUE, TRUE); + + /* + * Check whether a base tag is in effect. Note that actions + * always are resolved w.r.t. to the base, even if the action + * is empty. - FM + */ + HTParseALL(&action, Base); + + } else { + StrAllocCopy(action, Base); + } + + source = HTAnchor_findChildAndLink(me->node_anchor, + NULL, + action, + (HTLinkType *) 0); + if ((link_dest = HTAnchor_followLink(source)) != NULL) { + /* + * Memory leak fixed. 05-28-94 Lynx 2-3-1 Garrett Arch Blythe + */ + char *cp_freeme = HTAnchor_address(link_dest); + + if (cp_freeme != NULL) { + StrAllocCopy(action, cp_freeme); + FREE(cp_freeme); + } else { + StrAllocCopy(action, ""); + } + } + + if (present && present[HTML_FORM_METHOD]) + StrAllocCopy(method, (value[HTML_FORM_METHOD] + ? value[HTML_FORM_METHOD] + : "GET")); + + if (present && present[HTML_FORM_ENCTYPE] && + non_empty(value[HTML_FORM_ENCTYPE])) { + StrAllocCopy(enctype, value[HTML_FORM_ENCTYPE]); + LYLowerCase(enctype); + } + + if (present) { + /* + * Check for a TITLE attribute, and if none is present, check + * for a SUBJECT attribute as a synonym. - FM + */ + if (present[HTML_FORM_TITLE] && + non_empty(value[HTML_FORM_TITLE])) { + StrAllocCopy(title, value[HTML_FORM_TITLE]); + } else if (present[HTML_FORM_SUBJECT] && + non_empty(value[HTML_FORM_SUBJECT])) { + StrAllocCopy(title, value[HTML_FORM_SUBJECT]); + } + if (non_empty(title)) { + TRANSLATE_AND_UNESCAPE_ENTITIES(&title, TRUE, FALSE); + LYTrimHead(title); + LYTrimTail(title); + if (*title == '\0') { + FREE(title); + } + } + } + + HText_beginForm(action, method, enctype, title, accept_cs); + + FREE(action); + FREE(method); + FREE(enctype); + FREE(title); + } + CHECK_ID(HTML_FORM_ID); + break; + + case HTML_FIELDSET: + LYEnsureDoubleSpace(me); + LYResetParagraphAlignment(me); + CHECK_ID(HTML_GEN_ID); + break; + + case HTML_LEGEND: + LYEnsureDoubleSpace(me); + LYResetParagraphAlignment(me); + CHECK_ID(HTML_CAPTION_ID); + break; + + case HTML_LABEL: + CHECK_ID(HTML_LABEL_ID); + break; + + case HTML_KEYGEN: + CHECK_ID(HTML_KEYGEN_ID); + break; + + case HTML_BUTTON: + { + InputFieldData I; + int chars; + BOOL faked_button = FALSE; + + /* init */ + memset(&I, 0, sizeof(I)); + I.name_cs = ATTR_CS_IN; + I.value_cs = ATTR_CS_IN; + + UPDATE_STYLE; + if (present && + present[HTML_BUTTON_TYPE] && + value[HTML_BUTTON_TYPE]) { + if (!strcasecomp(value[HTML_BUTTON_TYPE], "submit") || + !strcasecomp(value[HTML_BUTTON_TYPE], "reset")) { + /* + * It's a button for submitting or resetting a form. - FM + */ + I.type = value[HTML_BUTTON_TYPE]; + } else { + /* + * Ugh, it's a button for a script. - FM + */ + I.type = value[HTML_BUTTON_TYPE]; + CTRACE((tfp, "found button for a script\n")); + } + } else { + /* default, if no type given, is a submit button */ + I.type = "submit"; + } + + /* + * Before any input field, add a collapsible space if we're not in + * a PRE block, to promote a wrap there for any long values that + * would extend past the right margin from our current position in + * the line. If we are in a PRE block, start a new line if the + * last line already is within 6 characters of the wrap point for + * PRE blocks. - FM + */ + if (me->sp[0].tag_number != HTML_PRE && !me->inPRE && + me->sp->style->freeFormat) { + HTML_put_character(me, ' '); + me->in_word = NO; + } else if (HText_LastLineSize(me->text, FALSE) > (LYcolLimit - 6)) { + HTML_put_character(me, '\n'); + me->in_word = NO; + } + HTML_put_character(me, '('); + + if (!(present && present[HTML_BUTTON_NAME] && + value[HTML_BUTTON_NAME])) { + I.name = ""; + } else if (StrChr(value[HTML_BUTTON_NAME], '&') == NULL) { + I.name = value[HTML_BUTTON_NAME]; + } else { + StrAllocCopy(I_name, value[HTML_BUTTON_NAME]); + UNESCAPE_FIELDNAME_TO_STD(&I_name); + I.name = I_name; + } + + if (present && present[HTML_BUTTON_VALUE] && + non_empty(value[HTML_BUTTON_VALUE])) { + /* + * Convert any HTML entities or decimal escaping. - FM + */ + StrAllocCopy(I.value, value[HTML_BUTTON_VALUE]); + me->UsePlainSpace = TRUE; + TRANSLATE_AND_UNESCAPE_ENTITIES(&I.value, TRUE, me->HiddenValue); + me->UsePlainSpace = FALSE; + /* + * Convert any newlines or tabs to spaces, and trim any lead or + * trailing spaces. - FM + */ + LYReduceBlanks(I.value); + } else if (!strcasecomp(I.type, "button")) { + if (non_empty(I.name)) { + StrAllocCopy(I.value, I.name); + } else { + StrAllocCopy(I.value, "BUTTON"); + faked_button = TRUE; + } + } else if (I.value == 0) { + StrAllocCopy(I.value, "BUTTON"); + } + + if (present && present[HTML_BUTTON_READONLY]) + I.readonly = YES; + + if (present && present[HTML_BUTTON_DISABLED]) + I.disabled = YES; + + if (present && present[HTML_BUTTON_CLASS] && /* Not yet used. */ + non_empty(value[HTML_BUTTON_CLASS])) + I.iclass = value[HTML_BUTTON_CLASS]; + + if (present && present[HTML_BUTTON_ID] && + non_empty(value[HTML_BUTTON_ID])) { + I.id = value[HTML_BUTTON_ID]; + CHECK_ID(HTML_BUTTON_ID); + } + + if (present && present[HTML_BUTTON_LANG] && /* Not yet used. */ + non_empty(value[HTML_BUTTON_LANG])) + I.lang = value[HTML_BUTTON_LANG]; + + chars = HText_beginInput(me->text, me->inUnderline, &I); + /* + * Submit and reset buttons have values which don't change, so + * HText_beginInput() sets I.value to the string which should be + * displayed, and we'll enter that instead of underscore + * placeholders into the HText structure to see it instead of + * underscores when dumping or printing. We also won't worry about + * a wrap in PRE blocks, because the line editor never is invoked + * for submit or reset buttons. - LE & FM + */ + if (me->sp[0].tag_number == HTML_PRE || + !me->sp->style->freeFormat) { + /* + * We have a submit or reset button in a PRE block, so output + * the entire value from the markup. If it extends to the + * right margin, it will wrap there, and only the portion + * before that wrap will be highlighted on screen display + * (Yuk!) but we may as well show the rest of the full value on + * the next or more lines. - FM + */ + while (I.value[i]) + HTML_put_character(me, I.value[i++]); + } else { + /* + * The submit or reset button is not in a PRE block. Note that + * if a wrap occurs before outputting the entire value, the + * wrapped portion will not be highlighted or clearly indicated + * as part of the link for submission or reset (Yuk!). We'll + * replace any spaces in the submit or reset button value with + * nbsp, to promote a wrap at the space we ensured would be + * present before the start of the string, as when we use all + * underscores instead of the INPUT's actual value, but we + * could still get a wrap at the right margin, instead, if the + * value is greater than a line width for the current style. + * Also, if chars somehow ended up longer than the length of + * the actual value (shouldn't have), we'll continue padding + * with nbsp up to the length of chars. - FM + */ + for (i = 0; I.value[i]; i++) { + HTML_put_character(me, + (char) ((I.value[i] == ' ') + ? HT_NON_BREAK_SPACE + : I.value[i])); + } + while (i++ < chars) { + HTML_put_character(me, HT_NON_BREAK_SPACE); + } + } + HTML_put_character(me, ')'); + if (me->sp[0].tag_number != HTML_PRE && + me->sp->style->freeFormat) { + HTML_put_character(me, ' '); + me->in_word = NO; + } + if (faked_button) + FREE(I.value); + FREE(I_name); + } + break; + + case HTML_INPUT: + { + InputFieldData I; + int chars; + BOOL UseALTasVALUE = FALSE; + BOOL HaveSRClink = FALSE; + char *ImageSrc = NULL; + BOOL IsSubmitOrReset = FALSE; + HTkcode kcode = NOKANJI; + HTkcode specified_kcode = NOKANJI; + + /* init */ + memset(&I, 0, sizeof(I)); + I.name_cs = ATTR_CS_IN; + I.value_cs = ATTR_CS_IN; + + UPDATE_STYLE; + + /* + * Before any input field, add a collapsible space if we're not in + * a PRE block, to promote a wrap there for any long values that + * would extend past the right margin from our current position in + * the line. If we are in a PRE block, start a new line if the + * last line already is within 6 characters of the wrap point for + * PRE blocks. - FM + */ + if (me->sp[0].tag_number != HTML_PRE && !me->inPRE && + me->sp->style->freeFormat) { + HTML_put_character(me, ' '); + me->in_word = NO; + } else if (HText_LastLineSize(me->text, FALSE) > (LYcolLimit - 6)) { + HTML_put_character(me, '\n'); + me->in_word = NO; + } + + /* + * Get the TYPE and make sure we can handle it. - FM + */ + if (present && present[HTML_INPUT_TYPE] && + non_empty(value[HTML_INPUT_TYPE])) { + const char *not_impl = NULL; + char *usingval = NULL; + + I.type = value[HTML_INPUT_TYPE]; + + if (!strcasecomp(I.type, "range")) { + if (present[HTML_INPUT_MIN] && + non_empty(value[HTML_INPUT_MIN])) + I.min = value[HTML_INPUT_MIN]; + if (present[HTML_INPUT_MAX] && + non_empty(value[HTML_INPUT_MAX])) + I.max = value[HTML_INPUT_MAX]; + /* + * Not yet implemented. + */ +#ifdef NOTDEFINED + not_impl = "[RANGE Input]"; + if (me->inFORM) + HText_DisableCurrentForm(); +#endif /* NOTDEFINED */ + CTRACE((tfp, "HTML: Ignoring TYPE=\"range\"\n")); + break; + + } else if (!strcasecomp(I.type, "file")) { + if (present[HTML_INPUT_ACCEPT] && + non_empty(value[HTML_INPUT_ACCEPT])) + I.accept = value[HTML_INPUT_ACCEPT]; +#ifndef USE_FILE_UPLOAD + not_impl = "[FILE Input]"; + CTRACE((tfp, "Attempting to fake as: %s\n", I.type)); +#ifdef NOTDEFINED + if (me->inFORM) + HText_DisableCurrentForm(); +#endif /* NOTDEFINED */ + CTRACE((tfp, "HTML: Ignoring TYPE=\"file\"\n")); +#endif /* USE_FILE_UPLOAD */ + + } else if (!strcasecomp(I.type, "button")) { + /* + * Ugh, a button for a script. + */ + not_impl = "[BUTTON Input]"; + } + if (not_impl != NULL) { + if (me->inUnderline == FALSE) { + HText_appendCharacter(me->text, + LY_UNDERLINE_START_CHAR); + } + HTML_put_string(me, not_impl); + if (usingval != NULL) { + HTML_put_string(me, usingval); + FREE(usingval); + } else { + HTML_put_string(me, " (not implemented)"); + } + if (me->inUnderline == FALSE) { + HText_appendCharacter(me->text, + LY_UNDERLINE_END_CHAR); + } + } + } + + CTRACE((tfp, "Ok, we're trying type=[%s]\n", NONNULL(I.type))); + + /* + * Check for an unclosed TEXTAREA. + */ + if (me->inTEXTAREA) { + if (LYBadHTML(me)) { + LYShowBadHTML("Bad HTML: Missing TEXTAREA end tag.\n"); + } + } + + /* + * Check for an unclosed SELECT, try to close it if found. + */ + if (me->inSELECT) { + CTRACE((tfp, "HTML: Missing SELECT end tag, faking it...\n")); + if (me->sp->tag_number != HTML_SELECT) { + SET_SKIP_STACK(HTML_SELECT); + } + HTML_end_element(me, HTML_SELECT, include); + } + + /* + * Handle the INPUT as for a FORM. - FM + */ + if (!(present && present[HTML_INPUT_NAME] && + non_empty(value[HTML_INPUT_NAME]))) { + I.name = ""; + } else if (StrChr(value[HTML_INPUT_NAME], '&') == NULL) { + I.name = value[HTML_INPUT_NAME]; + } else { + StrAllocCopy(I_name, value[HTML_INPUT_NAME]); + UNESCAPE_FIELDNAME_TO_STD(&I_name); + I.name = I_name; + } + + if ((present && present[HTML_INPUT_ALT] && + non_empty(value[HTML_INPUT_ALT]) && + I.type && !strcasecomp(I.type, "image")) && + !(present && present[HTML_INPUT_VALUE] && + non_empty(value[HTML_INPUT_VALUE]))) { + /* + * This is a TYPE="image" using an ALT rather than VALUE + * attribute to indicate the link string for text clients or + * GUIs with image loading off, so set the flag to use that as + * if it were a VALUE attribute. - FM + */ + UseALTasVALUE = TRUE; + } + if (verbose_img && !clickable_images && + present && present[HTML_INPUT_SRC] && + non_empty(value[HTML_INPUT_SRC]) && + I.type && !strcasecomp(I.type, "image")) { + ImageSrc = MakeNewImageValue(value); + } else if (clickable_images == TRUE && + present && present[HTML_INPUT_SRC] && + non_empty(value[HTML_INPUT_SRC]) && + I.type && !strcasecomp(I.type, "image")) { + StrAllocCopy(href, value[HTML_INPUT_SRC]); + /* + * We have a TYPE="image" with a non-zero-length SRC attribute + * and want clickable images. Make the SRC's value a link if + * it's still not zero-length legitimizing it. - FM + */ + LYLegitimizeHREF(me, &href, TRUE, TRUE); + if (*href) { + + if (me->inA) { + SET_SKIP_STACK(HTML_A); + HTML_end_element(me, HTML_A, include); + } + me->CurrentA = HTAnchor_findChildAndLink(me->node_anchor, /* Parent */ + NULL, /* Tag */ + href, /* Address */ + (HTLinkType *) 0); /* Type */ + HText_beginAnchor(me->text, me->inUnderline, me->CurrentA); + if (me->inBoldH == FALSE) + HText_appendCharacter(me->text, LY_BOLD_START_CHAR); + HTML_put_string(me, VERBOSE_IMG(value, + HTML_INPUT_SRC, + "[IMAGE]")); + FREE(newtitle); + if (me->inBoldH == FALSE) + HText_appendCharacter(me->text, LY_BOLD_END_CHAR); + HText_endAnchor(me->text, 0); + HTML_put_character(me, '-'); + HaveSRClink = TRUE; + } + FREE(href); + } + CTRACE((tfp, "2.Ok, we're trying type=[%s] (present=%p)\n", + NONNULL(I.type), + (const void *) present)); + /* text+file don't go in here */ + if ((UseALTasVALUE == TRUE) || + (present && present[HTML_INPUT_VALUE] && + value[HTML_INPUT_VALUE] && + (*value[HTML_INPUT_VALUE] || + (I.type && (!strcasecomp(I.type, "checkbox") || + !strcasecomp(I.type, "radio")))))) { + + /* + * Convert any HTML entities or decimal escaping. - FM + */ + int CurrentCharSet = current_char_set; + BOOL CurrentEightBitRaw = HTPassEightBitRaw; + BOOLEAN CurrentUseDefaultRawMode = LYUseDefaultRawMode; + HTCJKlang CurrentHTCJK = HTCJK; + + if (I.type && !strcasecomp(I.type, "hidden")) { + me->HiddenValue = TRUE; + current_char_set = LATIN1; /* Default ISO-Latin1 */ + LYUseDefaultRawMode = TRUE; + HTMLSetCharacterHandling(current_char_set); + } + + CTRACE((tfp, "3.Ok, we're trying type=[%s]\n", NONNULL(I.type))); + if (!I.type) + me->UsePlainSpace = TRUE; + else if (!strcasecomp(I.type, "text") || +#ifdef USE_FILE_UPLOAD + !strcasecomp(I.type, "file") || +#endif + !strcasecomp(I.type, "submit") || + !strcasecomp(I.type, "image") || + !strcasecomp(I.type, "reset")) { + CTRACE((tfp, "normal field type: %s\n", NONNULL(I.type))); + me->UsePlainSpace = TRUE; + } + + StrAllocCopy(I_value, + ((UseALTasVALUE == TRUE) + ? value[HTML_INPUT_ALT] + : value[HTML_INPUT_VALUE])); + if (me->UsePlainSpace && !me->HiddenValue) { + I.value_cs = current_char_set; + } + CTRACE((tfp, "4.Ok, we're trying type=[%s]\n", NONNULL(I.type))); + TRANSLATE_AND_UNESCAPE_ENTITIES6(&I_value, + ATTR_CS_IN, + I.value_cs, + (BOOL) (me->UsePlainSpace && + !me->HiddenValue), + me->UsePlainSpace, + me->HiddenValue); + I.value = I_value; + if (me->UsePlainSpace == TRUE) { + /* + * Convert any newlines or tabs to spaces, and trim any + * lead or trailing spaces. - FM + */ + LYReduceBlanks(I.value); + } + me->UsePlainSpace = FALSE; + + if (I.type && !strcasecomp(I.type, "hidden")) { + me->HiddenValue = FALSE; + current_char_set = CurrentCharSet; + LYUseDefaultRawMode = CurrentUseDefaultRawMode; + HTMLSetCharacterHandling(current_char_set); + HTPassEightBitRaw = CurrentEightBitRaw; + HTCJK = CurrentHTCJK; + } + } else if (HaveSRClink == TRUE) { + /* + * We put up an [IMAGE] link and '-' for a TYPE="image" and + * didn't get a VALUE or ALT string, so fake a "Submit" value. + * If we didn't put up a link, then HText_beginInput() will use + * "[IMAGE]-Submit". - FM + */ + StrAllocCopy(I_value, "Submit"); + I.value = I_value; + } else if (ImageSrc) { + /* [IMAGE]-Submit with verbose images and not clickable images. + * Use ImageSrc if no other alt or value is supplied. --LE + */ + I.value = ImageSrc; + } + if (present && present[HTML_INPUT_READONLY]) + I.readonly = YES; + if (present && present[HTML_INPUT_CHECKED]) + I.checked = YES; + if (present && present[HTML_INPUT_SIZE] && + non_empty(value[HTML_INPUT_SIZE])) + I.size = atoi(value[HTML_INPUT_SIZE]); + LimitValue(I.size, MAX_LINE); + if (present && present[HTML_INPUT_MAXLENGTH] && + non_empty(value[HTML_INPUT_MAXLENGTH])) + I.maxlength = value[HTML_INPUT_MAXLENGTH]; + if (present && present[HTML_INPUT_DISABLED]) + I.disabled = YES; + + if (present && present[HTML_INPUT_ACCEPT_CHARSET]) { /* Not yet used. */ + I.accept_cs = (value[HTML_INPUT_ACCEPT_CHARSET] + ? value[HTML_INPUT_ACCEPT_CHARSET] + : "UNKNOWN"); + } + if (present && present[HTML_INPUT_ALIGN] && /* Not yet used. */ + non_empty(value[HTML_INPUT_ALIGN])) + I.align = value[HTML_INPUT_ALIGN]; + if (present && present[HTML_INPUT_CLASS] && /* Not yet used. */ + non_empty(value[HTML_INPUT_CLASS])) + I.iclass = value[HTML_INPUT_CLASS]; + if (present && present[HTML_INPUT_ERROR] && /* Not yet used. */ + non_empty(value[HTML_INPUT_ERROR])) + I.error = value[HTML_INPUT_ERROR]; + if (present && present[HTML_INPUT_HEIGHT] && /* Not yet used. */ + non_empty(value[HTML_INPUT_HEIGHT])) + I.height = value[HTML_INPUT_HEIGHT]; + if (present && present[HTML_INPUT_WIDTH] && /* Not yet used. */ + non_empty(value[HTML_INPUT_WIDTH])) + I.width = value[HTML_INPUT_WIDTH]; + if (present && present[HTML_INPUT_ID] && + non_empty(value[HTML_INPUT_ID])) { + I.id = value[HTML_INPUT_ID]; + CHECK_ID(HTML_INPUT_ID); + } + if (present && present[HTML_INPUT_LANG] && /* Not yet used. */ + non_empty(value[HTML_INPUT_LANG])) + I.lang = value[HTML_INPUT_LANG]; + if (present && present[HTML_INPUT_MD] && /* Not yet used. */ + non_empty(value[HTML_INPUT_MD])) + I.md = value[HTML_INPUT_MD]; + + chars = HText_beginInput(me->text, me->inUnderline, &I); + CTRACE((tfp, + "I.%s have %d chars, or something\n", + NONNULL(I.type), + chars)); + /* + * Submit and reset buttons have values which don't change, so + * HText_beginInput() sets I.value to the string which should be + * displayed, and we'll enter that instead of underscore + * placeholders into the HText structure to see it instead of + * underscores when dumping or printing. We also won't worry about + * a wrap in PRE blocks, because the line editor never is invoked + * for submit or reset buttons. - LE & FM + */ + if (I.type && + (!strcasecomp(I.type, "submit") || + !strcasecomp(I.type, "reset") || + !strcasecomp(I.type, "image"))) + IsSubmitOrReset = TRUE; + + if (I.type && chars == 3 && + !strcasecomp(I.type, "radio")) { + /* + * Put a (_) placeholder, and one space (collapsible) before + * the label that is expected to follow. - FM + */ + HTML_put_string(me, "(_)"); + HText_endInput(me->text); + chars = 0; + me->in_word = YES; + if (me->sp[0].tag_number != HTML_PRE && + me->sp->style->freeFormat) { + HTML_put_character(me, ' '); + me->in_word = NO; + } + } else if (I.type && chars == 3 && + !strcasecomp(I.type, "checkbox")) { + /* + * Put a [_] placeholder, and one space (collapsible) before + * the label that is expected to follow. - FM + */ + HTML_put_string(me, "[_]"); + HText_endInput(me->text); + chars = 0; + me->in_word = YES; + if (me->sp[0].tag_number != HTML_PRE && + me->sp->style->freeFormat) { + HTML_put_character(me, ' '); + me->in_word = NO; + } + } else if ((me->sp[0].tag_number == HTML_PRE || + !me->sp->style->freeFormat) + && chars > 6 && + IsSubmitOrReset == FALSE) { + /* + * This is not a submit or reset button, and we are in a PRE + * block with a field intended to exceed 6 character widths. + * The code inadequately handles INPUT fields in PRE tags if + * wraps occur (at the right margin) for the underscore + * placeholders. We'll put up a minimum of 6 underscores, + * since we should have wrapped artificially, above, if the + * INPUT begins within 6 columns of the right margin, and if + * any more would exceed the wrap column, we'll ignore them. + * Note that if we somehow get tripped up and a wrap still does + * occur before all 6 of the underscores are output, the + * wrapped ones won't be treated as part of the editing window, + * nor be highlighted when not editing (Yuk!). - FM + */ + for (i = 0; i < 6; i++) { + HTML_put_character(me, '_'); + chars--; + } + } + CTRACE((tfp, "I.%s, %d\n", NONNULL(I.type), IsSubmitOrReset)); + if (IsSubmitOrReset == FALSE) { + /* + * This is not a submit or reset button, so output the rest of + * the underscore placeholders, if any more are needed. - FM + */ + if (chars > 0) { + for (; chars > 0; chars--) + HTML_put_character(me, '_'); + HText_endInput(me->text); + } + } else { + if (HTCJK == JAPANESE) { + kcode = HText_getKcode(me->text); + HText_updateKcode(me->text, kanji_code); + specified_kcode = HText_getSpecifiedKcode(me->text); + HText_updateSpecifiedKcode(me->text, kanji_code); + } + if (me->sp[0].tag_number == HTML_PRE || + !me->sp->style->freeFormat) { + /* + * We have a submit or reset button in a PRE block, so + * output the entire value from the markup. If it extends + * to the right margin, it will wrap there, and only the + * portion before that wrap will be highlighted on screen + * display (Yuk!) but we may as well show the rest of the + * full value on the next or more lines. - FM + */ + while (I.value[i]) + HTML_put_character(me, I.value[i++]); + } else { + /* + * The submit or reset button is not in a PRE block. Note + * that if a wrap occurs before outputting the entire + * value, the wrapped portion will not be highlighted or + * clearly indicated as part of the link for submission or + * reset (Yuk!). We'll replace any spaces in the submit or + * reset button value with nbsp, to promote a wrap at the + * space we ensured would be present before the start of + * the string, as when we use all underscores instead of + * the INPUT's actual value, but we could still get a wrap + * at the right margin, instead, if the value is greater + * than a line width for the current style. Also, if chars + * somehow ended up longer than the length of the actual + * value (shouldn't have), we'll continue padding with nbsp + * up to the length of chars. - FM + */ + for (i = 0; I.value[i]; i++) + HTML_put_character(me, + (char) (I.value[i] == ' ' + ? HT_NON_BREAK_SPACE + : I.value[i])); + while (i++ < chars) + HTML_put_character(me, HT_NON_BREAK_SPACE); + } + if (HTCJK == JAPANESE) { + HText_updateKcode(me->text, kcode); + HText_updateSpecifiedKcode(me->text, specified_kcode); + } + } + if (chars != 0) { + HText_endInput(me->text); + } + FREE(ImageSrc); + if (strcasecomp(NonNull(I.type), "submit")) + FREE(I_value); + FREE(I_name); + } + break; + + case HTML_TEXTAREA: + /* + * Set to know we are in a textarea. + */ + me->inTEXTAREA = TRUE; + + /* + * Get ready for the value. + */ + HTChunkClear(&me->textarea); + if (present && present[HTML_TEXTAREA_NAME] && + value[HTML_TEXTAREA_NAME]) { + StrAllocCopy(me->textarea_name, value[HTML_TEXTAREA_NAME]); + me->textarea_name_cs = ATTR_CS_IN; + if (StrChr(value[HTML_TEXTAREA_NAME], '&') != NULL) { + UNESCAPE_FIELDNAME_TO_STD(&me->textarea_name); + } + } else { + StrAllocCopy(me->textarea_name, ""); + } + + if (present && present[HTML_TEXTAREA_ACCEPT_CHARSET]) { + if (value[HTML_TEXTAREA_ACCEPT_CHARSET]) { + StrAllocCopy(me->textarea_accept_cs, value[HTML_TEXTAREA_ACCEPT_CHARSET]); + TRANSLATE_AND_UNESCAPE_TO_STD(&me->textarea_accept_cs); + } else { + StrAllocCopy(me->textarea_accept_cs, "UNKNOWN"); + } + } else { + FREE(me->textarea_accept_cs); + } + + if (present && present[HTML_TEXTAREA_COLS] && + value[HTML_TEXTAREA_COLS] && + isdigit(UCH(*value[HTML_TEXTAREA_COLS]))) { + me->textarea_cols = atoi(value[HTML_TEXTAREA_COLS]); + } else { + int width; + + width = LYcolLimit - + me->new_style->leftIndent - me->new_style->rightIndent; + if (dump_output_immediately) /* don't waste too much for this */ + width = HTMIN(width, DFT_TEXTAREA_COLS); + if (width > 1 && (width - 1) * 6 < MAX_LINE - 3 - + me->new_style->leftIndent - me->new_style->rightIndent) + me->textarea_cols = width; + else + me->textarea_cols = DFT_TEXTAREA_COLS; + } + LimitValue(me->textarea_cols, MAX_TEXTAREA_COLS); + + if (present && present[HTML_TEXTAREA_ROWS] && + value[HTML_TEXTAREA_ROWS] && + isdigit(UCH(*value[HTML_TEXTAREA_ROWS]))) { + me->textarea_rows = atoi(value[HTML_TEXTAREA_ROWS]); + } else { + me->textarea_rows = DFT_TEXTAREA_ROWS; + } + LimitValue(me->textarea_rows, MAX_TEXTAREA_ROWS); + + /* + * Lynx treats disabled and readonly textarea's the same - + * unmodifiable in either case. + */ + me->textarea_readonly = NO; + if (present && present[HTML_TEXTAREA_READONLY]) + me->textarea_readonly = YES; + + me->textarea_disabled = NO; + if (present && present[HTML_TEXTAREA_DISABLED]) + me->textarea_disabled = YES; + + if (present && present[HTML_TEXTAREA_ID] + && non_empty(value[HTML_TEXTAREA_ID])) { + StrAllocCopy(id_string, value[HTML_TEXTAREA_ID]); + TRANSLATE_AND_UNESCAPE_TO_STD(&id_string); + if ((*id_string != '\0') && + (ID_A = HTAnchor_findChildAndLink(me->node_anchor, /* Parent */ + id_string, /* Tag */ + NULL, /* Address */ + (HTLinkType *) 0))) { /* Type */ + HText_beginAnchor(me->text, me->inUnderline, ID_A); + HText_endAnchor(me->text, 0); + StrAllocCopy(me->textarea_id, id_string); + } else { + FREE(me->textarea_id); + } + FREE(id_string); + } else { + FREE(me->textarea_id); + } + break; + + case HTML_SELECT: + /* + * Check for an already open SELECT block. - FM + */ + if (me->inSELECT) { + if (LYBadHTML(me)) { + LYShowBadHTML("Bad HTML: SELECT start tag in SELECT element. Faking SELECT end tag. *****\n"); + } + if (me->sp->tag_number != HTML_SELECT) { + SET_SKIP_STACK(HTML_SELECT); + } + HTML_end_element(me, HTML_SELECT, include); + } + + /* + * Start a new SELECT block. - FM + */ + LYHandleSELECT(me, + present, (STRING2PTR) value, + include, + TRUE); + break; + + case HTML_OPTION: + { + /* + * An option is a special case of an input field. + */ + InputFieldData I; + + /* + * Make sure we're in a select tag. + */ + if (!me->inSELECT) { + if (LYBadHTML(me)) { + LYShowBadHTML("Bad HTML: OPTION tag not within SELECT tag\n"); + } + + /* + * Too likely to cause a crash, so we'll ignore it. - FM + */ + break; + } + + if (!me->first_option) { + /* + * Finish the data off. + */ + HTChunkTerminate(&me->option); + + /* + * Finish the previous option @@@@@ + */ + HText_setLastOptionValue(me->text, + me->option.data, + me->LastOptionValue, + MIDDLE_ORDER, + me->LastOptionChecked, + me->UCLYhndl, + ATTR_CS_IN); + } + + /* + * If it's not a multiple option list and select popups are + * enabled, then don't use the checkbox/button method, and don't + * put anything on the screen yet. + */ + if (me->first_option || + HTCurSelectGroupType == F_CHECKBOX_TYPE || + LYSelectPopups == FALSE) { + if (HTCurSelectGroupType == F_CHECKBOX_TYPE || + LYSelectPopups == FALSE) { + /* + * Start a newline before each option. + */ + LYEnsureSingleSpace(me); + } else { + /* + * Add option list designation character. + */ + HText_appendCharacter(me->text, '['); + me->in_word = YES; + } + + /* + * Inititialize. + */ + memset(&I, 0, sizeof(I)); + I.name_cs = -1; + I.value_cs = current_char_set; + + I.type = "OPTION"; + + if ((present && present[HTML_OPTION_SELECTED]) || + (me->first_option && LYSelectPopups == FALSE && + HTCurSelectGroupType == F_RADIO_TYPE)) + I.checked = YES; + + if (present && present[HTML_OPTION_VALUE] && + value[HTML_OPTION_VALUE]) { + /* + * Convert any HTML entities or decimal escaping. - FM + */ + StrAllocCopy(I_value, value[HTML_OPTION_VALUE]); + me->HiddenValue = TRUE; + TRANSLATE_AND_UNESCAPE_ENTITIES6(&I_value, + ATTR_CS_IN, + ATTR_CS_IN, + NO, + me->UsePlainSpace, me->HiddenValue); + I.value_cs = ATTR_CS_IN; + me->HiddenValue = FALSE; + + I.value = I_value; + } + + if (me->select_disabled || + (0 && present && present[HTML_OPTION_DISABLED])) { + /* 2009/5/25 - suppress check for "disabled" attribute + * for Debian #525934 -TD + */ + I.disabled = YES; + } + + if (present && present[HTML_OPTION_ID] + && non_empty(value[HTML_OPTION_ID])) { + if ((ID_A = HTAnchor_findChildAndLink(me->node_anchor, /* Parent */ + value[HTML_OPTION_ID], /* Tag */ + NULL, /* Address */ + 0)) != NULL) { /* Type */ + HText_beginAnchor(me->text, me->inUnderline, ID_A); + HText_endAnchor(me->text, 0); + I.id = value[HTML_OPTION_ID]; + } + } + + HText_beginInput(me->text, me->inUnderline, &I); + + if (HTCurSelectGroupType == F_CHECKBOX_TYPE) { + /* + * Put a "[_]" placeholder, and one space (collapsible) + * before the label that is expected to follow. - FM + */ + HText_appendCharacter(me->text, '['); + HText_appendCharacter(me->text, '_'); + HText_appendCharacter(me->text, ']'); + HText_appendCharacter(me->text, ' '); + HText_setLastChar(me->text, ' '); /* absorb white space */ + me->in_word = NO; + } else if (LYSelectPopups == FALSE) { + /* + * Put a "(_)" placeholder, and one space (collapsible) + * before the label that is expected to follow. - FM + */ + HText_appendCharacter(me->text, '('); + HText_appendCharacter(me->text, '_'); + HText_appendCharacter(me->text, ')'); + HText_appendCharacter(me->text, ' '); + HText_setLastChar(me->text, ' '); /* absorb white space */ + me->in_word = NO; + } + } + + /* + * Get ready for the next value. + */ + HTChunkClear(&me->option); + if ((present && present[HTML_OPTION_SELECTED]) || + (me->first_option && LYSelectPopups == FALSE && + HTCurSelectGroupType == F_RADIO_TYPE)) + me->LastOptionChecked = TRUE; + else + me->LastOptionChecked = FALSE; + me->first_option = FALSE; + + if (present && present[HTML_OPTION_VALUE] && + value[HTML_OPTION_VALUE]) { + if (!I_value) { + /* + * Convert any HTML entities or decimal escaping. - FM + */ + StrAllocCopy(I_value, value[HTML_OPTION_VALUE]); + me->HiddenValue = TRUE; + TRANSLATE_AND_UNESCAPE_ENTITIES6(&I_value, + ATTR_CS_IN, + ATTR_CS_IN, + NO, + me->UsePlainSpace, me->HiddenValue); + me->HiddenValue = FALSE; + } + StrAllocCopy(me->LastOptionValue, I_value); + } else { + StrAllocCopy(me->LastOptionValue, me->option.data); + } + + /* + * If this is a popup option, print its option for use in selecting + * option by number. - LE + */ + if (HTCurSelectGroupType == F_RADIO_TYPE && + LYSelectPopups && + fields_are_numbered()) { + char marker[8]; + int opnum = HText_getOptionNum(me->text); + + if (opnum > 0 && opnum < 100000) { + sprintf(marker, "(%d)", opnum); + HTML_put_string(me, marker); + for (i = (int) strlen(marker); i < 5; ++i) { + HTML_put_character(me, '_'); + } + } + } + FREE(I_value); + } + break; + + case HTML_TABLE: + /* + * Not fully implemented. Just treat as a division with respect to any + * ALIGN attribute, with a default of HT_LEFT, or leave as a PRE block + * if we are presently in one. - FM + * + * Also notify simple table tracking code unless in a preformatted + * section, or (currently) non-left alignment. + * + * If page author is using a TABLE within PRE, it's probably formatted + * specifically to work well for Lynx without simple table tracking + * code. Cancel tracking, it would only make things worse. - kw + */ +#ifdef EXP_NESTED_TABLES + if (!nested_tables) +#endif + { + HText_cancelStbl(me->text); + } + + if (me->inA) { + SET_SKIP_STACK(HTML_A); + HTML_end_element(me, HTML_A, include); + } + if (me->Underline_Level > 0) { + SET_SKIP_STACK(HTML_U); + HTML_end_element(me, HTML_U, include); + } + me->inTABLE = TRUE; + if (me->sp->style->id == ST_Preformatted) { + UPDATE_STYLE; + CHECK_ID(HTML_TABLE_ID); + break; + } + if (me->Division_Level < (MAX_NESTING - 1)) { + me->Division_Level++; + } else { + CTRACE((tfp, + "HTML: ****** Maximum nesting of %d divisions/tables exceeded!\n", + MAX_NESTING)); + } + if (present && present[HTML_TABLE_ALIGN] && + non_empty(value[HTML_TABLE_ALIGN])) { + if (!strcasecomp(value[HTML_TABLE_ALIGN], "center")) { + if (no_table_center) { + me->DivisionAlignments[me->Division_Level] = HT_LEFT; + change_paragraph_style(me, styles[HTML_DLEFT]); + UPDATE_STYLE; + me->current_default_alignment = + styles[HTML_DLEFT]->alignment; + } else { + me->DivisionAlignments[me->Division_Level] = HT_CENTER; + change_paragraph_style(me, styles[HTML_DCENTER]); + UPDATE_STYLE; + me->current_default_alignment = + styles[HTML_DCENTER]->alignment; + } + + stbl_align = HT_CENTER; + + } else if (!strcasecomp(value[HTML_TABLE_ALIGN], "right")) { + me->DivisionAlignments[me->Division_Level] = HT_RIGHT; + change_paragraph_style(me, styles[HTML_DRIGHT]); + UPDATE_STYLE; + me->current_default_alignment = styles[HTML_DRIGHT]->alignment; + stbl_align = HT_RIGHT; + } else { + me->DivisionAlignments[me->Division_Level] = HT_LEFT; + change_paragraph_style(me, styles[HTML_DLEFT]); + UPDATE_STYLE; + me->current_default_alignment = styles[HTML_DLEFT]->alignment; + if (!strcasecomp(value[HTML_TABLE_ALIGN], "left") || + !strcasecomp(value[HTML_TABLE_ALIGN], "justify")) + stbl_align = HT_LEFT; + } + } else { + me->DivisionAlignments[me->Division_Level] = HT_LEFT; + change_paragraph_style(me, styles[HTML_DLEFT]); + UPDATE_STYLE; + me->current_default_alignment = styles[HTML_DLEFT]->alignment; + /* stbl_align remains HT_ALIGN_NONE */ + } + CHECK_ID(HTML_TABLE_ID); + HText_startStblTABLE(me->text, stbl_align); + break; + + case HTML_TR: + /* + * Not fully implemented. Just start a new row, if needed, act on an + * ALIGN attribute if present, and check for an ID link. - FM + * Also notify simple table tracking code. - kw + */ + if (me->inA) { + SET_SKIP_STACK(HTML_A); + HTML_end_element(me, HTML_A, include); + } + if (me->Underline_Level > 0) { + SET_SKIP_STACK(HTML_U); + HTML_end_element(me, HTML_U, include); + } + UPDATE_STYLE; + if (!HText_LastLineEmpty(me->text, FALSE)) { + HText_setLastChar(me->text, ' '); /* absorb white space */ + HText_appendCharacter(me->text, '\r'); + } + me->in_word = NO; + + if (me->sp->style->id == ST_Preformatted) { + CHECK_ID(HTML_TR_ID); + me->inP = FALSE; + break; + } + if (LYoverride_default_alignment(me)) { + me->sp->style->alignment = styles[me->sp[0].tag_number]->alignment; + } else if (me->List_Nesting_Level >= 0 || + ((me->Division_Level < 0) && + (me->sp->style->id == ST_Normal || + me->sp->style->id == ST_Preformatted))) { + me->sp->style->alignment = HT_LEFT; + } else { + me->sp->style->alignment = (short) me->current_default_alignment; + } + if (present && present[HTML_TR_ALIGN] && value[HTML_TR_ALIGN]) { + if (!strcasecomp(value[HTML_TR_ALIGN], "center") && + !(me->List_Nesting_Level >= 0 && !me->inP)) { + if (no_table_center) + me->sp->style->alignment = HT_LEFT; + else + me->sp->style->alignment = HT_CENTER; + stbl_align = HT_CENTER; + } else if (!strcasecomp(value[HTML_TR_ALIGN], "right") && + !(me->List_Nesting_Level >= 0 && !me->inP)) { + me->sp->style->alignment = HT_RIGHT; + stbl_align = HT_RIGHT; + } else if (!strcasecomp(value[HTML_TR_ALIGN], "left") || + !strcasecomp(value[HTML_TR_ALIGN], "justify")) { + me->sp->style->alignment = HT_LEFT; + stbl_align = HT_LEFT; + } + } + + CHECK_ID(HTML_TR_ID); + me->inP = FALSE; + HText_startStblTR(me->text, stbl_align); + break; + + case HTML_THEAD: + case HTML_TFOOT: + case HTML_TBODY: + HText_endStblTR(me->text); + /* + * Not fully implemented. Just check for an ID link. - FM + */ + if (me->inA) { + SET_SKIP_STACK(HTML_A); + HTML_end_element(me, HTML_A, include); + } + if (me->Underline_Level > 0) { + SET_SKIP_STACK(HTML_U); + HTML_end_element(me, HTML_U, include); + } + UPDATE_STYLE; + if (me->inTABLE) { + if (present && present[HTML_TR_ALIGN] && value[HTML_TR_ALIGN]) { + if (!strcasecomp(value[HTML_TR_ALIGN], "center")) { + stbl_align = HT_CENTER; + } else if (!strcasecomp(value[HTML_TR_ALIGN], "right")) { + stbl_align = HT_RIGHT; + } else if (!strcasecomp(value[HTML_TR_ALIGN], "left") || + !strcasecomp(value[HTML_TR_ALIGN], "justify")) { + stbl_align = HT_LEFT; + } + } + HText_startStblRowGroup(me->text, stbl_align); + } + CHECK_ID(HTML_TR_ID); + break; + + case HTML_COL: + case HTML_COLGROUP: + /* + * Not fully implemented. Just check for an ID link. - FM + */ + if (me->inA) { + SET_SKIP_STACK(HTML_A); + HTML_end_element(me, HTML_A, include); + } + if (me->Underline_Level > 0) { + SET_SKIP_STACK(HTML_U); + HTML_end_element(me, HTML_U, include); + } + UPDATE_STYLE; + if (me->inTABLE) { + int span = 1; + + if (present && present[HTML_COL_SPAN] && + value[HTML_COL_SPAN] && + isdigit(UCH(*value[HTML_COL_SPAN]))) + span = atoi(value[HTML_COL_SPAN]); + if (present && present[HTML_COL_ALIGN] && value[HTML_COL_ALIGN]) { + if (!strcasecomp(value[HTML_COL_ALIGN], "center")) { + stbl_align = HT_CENTER; + } else if (!strcasecomp(value[HTML_COL_ALIGN], "right")) { + stbl_align = HT_RIGHT; + } else if (!strcasecomp(value[HTML_COL_ALIGN], "left") || + !strcasecomp(value[HTML_COL_ALIGN], "justify")) { + stbl_align = HT_LEFT; + } + } + HText_startStblCOL(me->text, span, stbl_align, + (BOOL) (ElementNumber == HTML_COLGROUP)); + } + CHECK_ID(HTML_COL_ID); + break; + + case HTML_TH: + case HTML_TD: + if (me->inA) { + SET_SKIP_STACK(HTML_A); + HTML_end_element(me, HTML_A, include); + } + if (me->Underline_Level > 0) { + SET_SKIP_STACK(HTML_U); + HTML_end_element(me, HTML_U, include); + } + UPDATE_STYLE; + CHECK_ID(HTML_TD_ID); + /* + * Not fully implemented. Just add a collapsible space and break - FM + * Also notify simple table tracking code. - kw + */ + HTML_put_character(me, ' '); + { + int colspan = 1, rowspan = 1; + + if (present && present[HTML_TD_COLSPAN] && + value[HTML_TD_COLSPAN] && + isdigit(UCH(*value[HTML_TD_COLSPAN]))) + colspan = atoi(value[HTML_TD_COLSPAN]); + if (present && present[HTML_TD_ROWSPAN] && + value[HTML_TD_ROWSPAN] && + isdigit(UCH(*value[HTML_TD_ROWSPAN]))) + rowspan = atoi(value[HTML_TD_ROWSPAN]); + if (present && present[HTML_TD_ALIGN] && value[HTML_TD_ALIGN]) { + if (!strcasecomp(value[HTML_TD_ALIGN], "center")) { + stbl_align = HT_CENTER; + } else if (!strcasecomp(value[HTML_TD_ALIGN], "right")) { + stbl_align = HT_RIGHT; + } else if (!strcasecomp(value[HTML_TD_ALIGN], "left") || + !strcasecomp(value[HTML_TD_ALIGN], "justify")) { + stbl_align = HT_LEFT; + } + } + HText_startStblTD(me->text, colspan, rowspan, stbl_align, + (BOOL) (ElementNumber == HTML_TH)); + } + me->in_word = NO; + break; + + case HTML_MATH: + /* + * We're getting it as Literal text, which, until we can process it, + * we'll display as is, within brackets to alert the user. - FM + */ + HTChunkClear(&me->math); + CHECK_ID(HTML_GEN_ID); + break; + + default: + break; + + } /* end switch */ + + if (ElementNumber >= HTML_ELEMENTS || + HTML_dtd.tags[ElementNumber].contents != SGML_EMPTY) { + if (me->skip_stack > 0) { + CTRACE((tfp, + "HTML:begin_element: internal call (level %d), leaving on stack - `%s'\n", + me->skip_stack, NONNULL(GetHTStyleName(me->sp->style)))); + me->skip_stack--; + return status; + } + if (me->sp == me->stack) { + if (me->stack_overrun == FALSE) { + HTAlert(HTML_STACK_OVERRUN); + CTRACE((tfp, + "HTML: ****** Maximum nesting of %d tags exceeded!\n", + MAX_NESTING)); + me->stack_overrun = TRUE; + } + return HT_ERROR; + } + + CTRACE((tfp, + "HTML:begin_element[%d]: adding style to stack - %s (%s)\n", + (int) STACKLEVEL(me), + NONNULL(GetHTStyleName(me->new_style)), + HTML_dtd.tags[ElementNumber].name)); + (me->sp)--; + me->sp[0].style = me->new_style; /* Stack new style */ + me->sp[0].tag_number = ElementNumber; +#ifdef USE_JUSTIFY_ELTS + if (wait_for_this_stacked_elt < 0 && + HTML_dtd.tags[ElementNumber].can_justify == FALSE) + wait_for_this_stacked_elt = (int) (me->stack - me->sp) + MAX_NESTING; +#endif + } +#ifdef USE_JUSTIFY_ELTS + if (in_DT && ElementNumber == HTML_DD) + in_DT = FALSE; + else if (ElementNumber == HTML_DT) + in_DT = TRUE; +#endif + +#if defined(USE_COLOR_STYLE) +/* end really empty tags straight away */ + + if (ReallyEmptyTagNum(element_number)) { + CTRACE2(TRACE_STYLE, + (tfp, "STYLE.begin_element:ending \"EMPTY\" element style\n")); + HText_characterStyle(me->text, hcode, STACK_OFF); + + FastTrimColorClass(HTML_dtd.tags[element_number].name, + HTML_dtd.tags[element_number].name_len, + Style_className, + &Style_className_end, &hcode); + } +#endif /* USE_COLOR_STYLE */ + return status; +} + +/* End Element + * ----------- + * + * When we end an element, the style must be returned to that + * in effect before that element. Note that anchors (etc?) + * don't have an associated style, so that we must scan down the + * stack for an element with a defined style. (In fact, the styles + * should be linked to the whole stack not just the top one.) + * TBL 921119 + */ +static int HTML_end_element(HTStructured * me, int element_number, + char **include) +{ + static char empty[1]; + + int i = 0; + int status = HT_OK; + char *temp = NULL, *cp = NULL; + BOOL BreakFlag = FALSE; + BOOL intern_flag = FALSE; + +#ifdef USE_COLOR_STYLE + BOOL skip_stack_requested = FALSE; +#endif + EMIT_IFDEF_USE_JUSTIFY_ELTS(BOOL reached_awaited_stacked_elt = FALSE); + + if ((me->sp >= (me->stack + MAX_NESTING - 1) || + element_number != me->sp[0].tag_number) && + HTML_dtd.tags[element_number].contents != SGML_EMPTY) { + CTRACE((tfp, + "HTML: end of element %s when expecting end of %s\n", + HTML_dtd.tags[element_number].name, + (me->sp == me->stack + MAX_NESTING - 1) ? "none" : + (me->sp->tag_number < 0) ? "*invalid tag*" : + (me->sp->tag_number >= HTML_ELEMENTS) ? "special tag" : + HTML_dtd.tags[me->sp->tag_number].name)); + } + + /* + * If we're seeking MAPs, skip everything that's not a MAP or AREA tag. - + * FM + */ + if (LYMapsOnly) { + if (!(element_number == HTML_MAP || element_number == HTML_AREA || + element_number == HTML_OBJECT)) { + return HT_OK; + } + } + + /* + * Pop state off stack if we didn't declare the element SGML_EMPTY in + * HTMLDTD.c. - FM & KW + */ + if (HTML_dtd.tags[element_number].contents != SGML_EMPTY) { +#ifdef USE_COLOR_STYLE + skip_stack_requested = (BOOL) (me->skip_stack > 0); +#endif + if ((element_number != me->sp[0].tag_number) && + me->skip_stack <= 0 && + HTML_dtd.tags[HTML_LH].contents != SGML_EMPTY && + (me->sp[0].tag_number == HTML_UL || + me->sp[0].tag_number == HTML_OL || + me->sp[0].tag_number == HTML_MENU || + me->sp[0].tag_number == HTML_DIR || + me->sp[0].tag_number == HTML_LI) && + (element_number == HTML_H1 || + element_number == HTML_H2 || + element_number == HTML_H3 || + element_number == HTML_H4 || + element_number == HTML_H5 || + element_number == HTML_H6)) { + /* + * Set the break flag if we're popping a dummy HTML_LH substituted + * for an HTML_H# encountered in a list. + */ + BreakFlag = TRUE; + } + if (me->skip_stack == 0 && element_number == HTML_OBJECT && + me->sp[0].tag_number == HTML_OBJECT_M && + (me->sp < (me->stack + MAX_NESTING - 1))) + me->sp[0].tag_number = HTML_OBJECT; + if (me->skip_stack > 0) { + CTRACE2(TRACE_STYLE, + (tfp, + "HTML:end_element: Internal call (level %d), leaving on stack - %s\n", + me->skip_stack, NONNULL(GetHTStyleName(me->sp->style)))); + me->skip_stack--; + } else if (element_number == HTML_OBJECT && + me->sp[0].tag_number != HTML_OBJECT && + me->sp[0].tag_number != HTML_OBJECT_M && + me->objects_mixed_open > 0 && + !(me->objects_figged_open > 0 && + me->sp[0].tag_number == HTML_FIG)) { + /* + * Ignore non-corresponding OBJECT tags that we didn't push because + * the SGML parser was supposed to go on parsing the contents + * non-literally. - kw + */ + CTRACE2(TRACE_STYLE, + (tfp, "HTML:end_element[%d]: %s (level %d), %s - %s\n", + (int) STACKLEVEL(me), + "Special OBJECT handling", me->objects_mixed_open, + "leaving on stack", + NONNULL(GetHTStyleName(me->sp->style)))); + me->objects_mixed_open--; + } else if (me->stack_overrun == TRUE && + element_number != me->sp[0].tag_number) { + /* + * Ignore non-corresponding tags if we had a stack overrun. This + * is not a completely fail-safe strategy for protection against + * any seriously adverse consequences of a stack overrun, and the + * rendering of the document will not be as intended, but we expect + * overruns to be rare, and this should offer reasonable protection + * against crashes if an overrun does occur. - FM + */ + return HT_OK; /* let's pretend... */ + } else if (element_number == HTML_SELECT && + me->sp[0].tag_number != HTML_SELECT) { + /* + * Ignore non-corresponding SELECT tags, since we probably popped + * it and closed the SELECT block to deal with markup which amounts + * to a nested SELECT, or an out of order FORM end tag. - FM + */ + return HT_OK; + } else if ((element_number != me->sp[0].tag_number) && + HTML_dtd.tags[HTML_LH].contents == SGML_EMPTY && + (me->sp[0].tag_number == HTML_UL || + me->sp[0].tag_number == HTML_OL || + me->sp[0].tag_number == HTML_MENU || + me->sp[0].tag_number == HTML_DIR || + me->sp[0].tag_number == HTML_LI) && + (element_number == HTML_H1 || + element_number == HTML_H2 || + element_number == HTML_H3 || + element_number == HTML_H4 || + element_number == HTML_H5 || + element_number == HTML_H6)) { + /* + * It's an H# for which we substituted an HTML_LH, which we've + * declared as SGML_EMPTY, so just return. - FM + */ + return HT_OK; + } else if (me->sp < (me->stack + MAX_NESTING - 1)) { +#ifdef USE_JUSTIFY_ELTS + if (wait_for_this_stacked_elt == me->stack - me->sp + MAX_NESTING) + reached_awaited_stacked_elt = TRUE; +#endif + if (element_number == HTML_OBJECT) { + if (me->sp[0].tag_number == HTML_FIG && + me->objects_figged_open > 0) { + /* + * It's an OBJECT for which we substituted a FIG, so pop + * the FIG and pretend that's what we are being called for. + * - kw + */ + CTRACE2(TRACE_STYLE, + (tfp, + "HTML:end_element[%d]: %s (level %d), %s - %s\n", + (int) STACKLEVEL(me), + "Special OBJECT->FIG handling", + me->objects_figged_open, + "treating as end FIG", + NONNULL(GetHTStyleName(me->sp->style)))); + me->objects_figged_open--; + element_number = HTML_FIG; + } + } + (me->sp)++; + CTRACE2(TRACE_STYLE, + (tfp, + "HTML:end_element[%d]: Popped style off stack - %s\n", + (int) STACKLEVEL(me), + NONNULL(GetHTStyleName(me->sp->style)))); + } else { + CTRACE2(TRACE_STYLE, (tfp, + "Stack underflow error! Tried to pop off more styles than exist in stack\n")); + } + } + if (BreakFlag == TRUE) { +#ifdef USE_JUSTIFY_ELTS + if (reached_awaited_stacked_elt) + wait_for_this_stacked_elt = -1; +#endif + return HT_OK; /* let's pretend... */ + } + + /* + * Check for unclosed TEXTAREA. - FM + */ + if (me->inTEXTAREA && element_number != HTML_TEXTAREA) { + if (LYBadHTML(me)) { + LYShowBadHTML("Bad HTML: Missing TEXTAREA end tag\n"); + } + } + + if (!me->text && !LYMapsOnly) { + UPDATE_STYLE; + } + + /* + * Handle the end tag. - FM + */ + switch (element_number) { + + case HTML_HTML: + if (me->inA || me->inSELECT || me->inTEXTAREA) { + if (LYBadHTML(me)) { + char *msg = NULL; + + HTSprintf0(&msg, + "Bad HTML: %s%s%s%s%s not closed before HTML end tag *****\n", + me->inSELECT ? "SELECT" : "", + (me->inSELECT && me->inTEXTAREA) ? ", " : "", + me->inTEXTAREA ? "TEXTAREA" : "", + (((me->inSELECT || me->inTEXTAREA) && me->inA) + ? ", " + : ""), + me->inA ? "A" : ""); + LYShowBadHTML(msg); + FREE(msg); + } + } + break; + + case HTML_HEAD: + if (me->inBASE && + (LYIsUIPage3(me->node_anchor->address, UIP_LIST_PAGE, 0) || + LYIsUIPage3(me->node_anchor->address, UIP_ADDRLIST_PAGE, 0))) { + /* If we are parsing the List Page, and have a BASE after we are + * done with the HEAD element, propagate it back to the node_anchor + * object. The base should have been inserted by showlist() to + * record what document the List Page is about, and other functions + * may later look for it in the anchor. - kw + */ + StrAllocCopy(me->node_anchor->content_base, me->base_href); + } + if (HText_hasToolbar(me->text)) + HText_appendParagraph(me->text); + break; + + case HTML_TITLE: + HTChunkTerminate(&me->title); + HTAnchor_setTitle(me->node_anchor, me->title.data); + HTChunkClear(&me->title); + /* + * Check if it's a bookmark file, and if so, and multiple bookmark + * support is on, or it's off but this isn't the default bookmark file + * (e.g., because it was on before, and this is another bookmark file + * that has been retrieved as a previous document), insert the current + * description string and filepath for it. We pass the strings back to + * the SGML parser so that any 8 bit or multibyte/CJK characters will + * be handled by the parser's state and charset routines. - FM + */ + if (non_empty(me->node_anchor->bookmark)) { + if ((LYMultiBookmarks != MBM_OFF) || + (non_empty(bookmark_page) && + strcmp(me->node_anchor->bookmark, bookmark_page))) { + if (!include) + include = &me->xinclude; + for (i = 0; i <= MBM_V_MAXFILES; i++) { + if (MBM_A_subbookmark[i] && + !strcmp(MBM_A_subbookmark[i], + me->node_anchor->bookmark)) { + StrAllocCat(*include, "

    "); + StrAllocCat(*include, gettext("Description:")); + StrAllocCat(*include, " "); + StrAllocCopy(temp, + ((MBM_A_subdescript[i] && + *MBM_A_subdescript[i]) ? + MBM_A_subdescript[i] : gettext("(none)"))); + LYEntify(&temp, TRUE); + StrAllocCat(*include, temp); + StrAllocCat(*include, "
       "); + StrAllocCat(*include, gettext("Filepath:")); + StrAllocCat(*include, " "); + StrAllocCopy(temp, + ((MBM_A_subbookmark[i] && + *MBM_A_subbookmark[i]) + ? MBM_A_subbookmark[i] + : gettext("(unknown)"))); + LYEntify(&temp, TRUE); + StrAllocCat(*include, temp); + FREE(temp); + StrAllocCat(*include, "

    "); + break; + } + } + } + } + break; + + case HTML_STYLE: + /* + * We're getting it as Literal text, which, for now, we'll just ignore. + * - FM + */ + HTChunkTerminate(&me->style_block); + CTRACE2(TRACE_STYLE, + (tfp, "HTML: STYLE content =\n%s\n", + me->style_block.data)); + HTChunkClear(&me->style_block); + break; + + case HTML_SCRIPT: + /* + * We're getting it as Literal text, which, for now, we'll just ignore. + * - FM + */ + HTChunkTerminate(&me->script); + CTRACE((tfp, "HTML: SCRIPT content =\n%s\n", + me->script.data)); + HTChunkClear(&me->script); + break; + + case HTML_BODY: + if (me->inA || me->inSELECT || me->inTEXTAREA) { + if (LYBadHTML(me)) { + char *msg = NULL; + + HTSprintf0(&msg, + "Bad HTML: %s%s%s%s%s not closed before BODY end tag *****\n", + me->inSELECT ? "SELECT" : "", + (me->inSELECT && me->inTEXTAREA) ? ", " : "", + me->inTEXTAREA ? "TEXTAREA" : "", + (((me->inSELECT || me->inTEXTAREA) && me->inA) + ? ", " + : ""), + me->inA ? "A" : ""); + LYShowBadHTML(msg); + FREE(msg); + } + } + break; + + case HTML_FRAMESET: + change_paragraph_style(me, me->sp->style); /* Often won't really change */ + break; + + case HTML_NOFRAMES: + case HTML_IFRAME: + LYEnsureDoubleSpace(me); + LYResetParagraphAlignment(me); + change_paragraph_style(me, me->sp->style); /* Often won't really change */ + break; + + case HTML_BANNER: + case HTML_MARQUEE: + case HTML_BLOCKQUOTE: + case HTML_BQ: + case HTML_ADDRESS: + /* + * Set flag to know that style has ended. Fall through. + i_prior_style = -1; + */ + change_paragraph_style(me, me->sp->style); + UPDATE_STYLE; + if (me->sp->tag_number == element_number) + LYEnsureDoubleSpace(me); + if (me->List_Nesting_Level >= 0) + HText_NegateLineOne(me->text); + break; + + case HTML_CENTER: + case HTML_DIV: + if (me->Division_Level >= 0) + me->Division_Level--; + if (me->Division_Level >= 0) { + if (me->sp->style->alignment != + me->DivisionAlignments[me->Division_Level]) { + if (me->inP) + LYEnsureSingleSpace(me); + me->sp->style->alignment = + me->DivisionAlignments[me->Division_Level]; + } + } + change_paragraph_style(me, me->sp->style); + if (me->style_change) { + actually_set_style(me); + if (me->List_Nesting_Level >= 0) + HText_NegateLineOne(me->text); + } else if (me->inP) + LYEnsureSingleSpace(me); + me->current_default_alignment = me->sp->style->alignment; + break; + + case HTML_H1: /* header styles */ + case HTML_H2: + case HTML_H3: + case HTML_H4: + case HTML_H5: + case HTML_H6: + if (me->Division_Level >= 0) { + me->sp->style->alignment = + me->DivisionAlignments[me->Division_Level]; + } else if (me->sp->style->id == ST_HeadingCenter || + me->sp->style->id == ST_Heading1) { + me->sp->style->alignment = HT_CENTER; + } else if (me->sp->style->id == ST_HeadingRight) { + me->sp->style->alignment = HT_RIGHT; + } else { + me->sp->style->alignment = HT_LEFT; + } + change_paragraph_style(me, me->sp->style); + UPDATE_STYLE; + if (styles[element_number]->font & HT_BOLD) { + if (me->inBoldA == FALSE && me->inBoldH == TRUE) { + HText_appendCharacter(me->text, LY_BOLD_END_CHAR); + } + me->inBoldH = FALSE; + } + if (me->List_Nesting_Level >= 0) + HText_NegateLineOne(me->text); + if (me->Underline_Level > 0 && me->inUnderline == FALSE) { + HText_appendCharacter(me->text, LY_UNDERLINE_START_CHAR); + me->inUnderline = TRUE; + } + break; + + case HTML_P: + LYHandlePlike(me, + (const BOOL *) 0, (STRING2PTR) 0, + include, 0, + FALSE); + break; + + case HTML_FONT: + me->inFONT = FALSE; + break; + + case HTML_B: /* Physical character highlighting */ + case HTML_BLINK: + case HTML_I: + case HTML_U: + + case HTML_CITE: /* Logical character highlighting */ + case HTML_EM: + case HTML_STRONG: + /* + * Ignore any emphasis end tags if the Underline_Level is not set. - + * FM + */ + if (me->Underline_Level <= 0) + break; + + /* + * Adjust the Underline level counter, and turn off underlining if + * appropriate. - FM + */ + me->Underline_Level--; + if (me->inUnderline && me->Underline_Level < 1) { + HText_appendCharacter(me->text, LY_UNDERLINE_END_CHAR); + me->inUnderline = FALSE; + CTRACE((tfp, "Ending underline\n")); + } else { + CTRACE((tfp, "Underline Level is %d\n", me->Underline_Level)); + } + break; + + case HTML_ABBR: /* Miscellaneous character containers */ + case HTML_ACRONYM: + case HTML_AU: + case HTML_AUTHOR: + case HTML_BIG: + case HTML_CODE: + case HTML_DFN: + case HTML_KBD: + case HTML_SAMP: + case HTML_SMALL: + case HTML_SUP: + case HTML_TT: + case HTML_VAR: + break; + + case HTML_SUB: + HText_appendCharacter(me->text, ']'); + break; + + case HTML_DEL_2: + case HTML_DEL: + case HTML_S: + case HTML_STRIKE: + HTML_put_character(me, ' '); + if (me->inUnderline == FALSE) + HText_appendCharacter(me->text, LY_UNDERLINE_START_CHAR); + HTML_put_string(me, ":DEL]"); + if (me->inUnderline == FALSE) + HText_appendCharacter(me->text, LY_UNDERLINE_END_CHAR); + HTML_put_character(me, ' '); + me->in_word = NO; + break; + + case HTML_INS_2: + case HTML_INS: + HTML_put_character(me, ' '); + if (me->inUnderline == FALSE) + HText_appendCharacter(me->text, LY_UNDERLINE_START_CHAR); + HTML_put_string(me, ":INS]"); + if (me->inUnderline == FALSE) + HText_appendCharacter(me->text, LY_UNDERLINE_END_CHAR); + HTML_put_character(me, ' '); + me->in_word = NO; + break; + + case HTML_Q: + if (me->Quote_Level > 0) + me->Quote_Level--; + /* + * Should check LANG and/or DIR attributes, and the + * me->node_anchor->charset and/or yet to be added structure elements, + * to determine whether we should use chevrons, but for now we'll + * always use double- or single-quotes. - FM + */ + if (!(me->Quote_Level & 1)) + HTML_put_character(me, '"'); + else + HTML_put_character(me, '\''); + break; + + case HTML_PRE: /* Formatted text */ + /* + * Set to know that we are no longer in a PRE block. + */ + HText_appendCharacter(me->text, '\n'); + me->inPRE = FALSE; + /* FALLTHRU */ + case HTML_LISTING: /* Literal text */ + /* FALLTHRU */ + case HTML_XMP: + /* FALLTHRU */ + case HTML_PLAINTEXT: + if (me->comment_start) + HText_appendText(me->text, me->comment_start); + change_paragraph_style(me, me->sp->style); /* Often won't really change */ + if (me->List_Nesting_Level >= 0) { + UPDATE_STYLE; + HText_NegateLineOne(me->text); + } + break; + + case HTML_NOTE: + case HTML_FN: + change_paragraph_style(me, me->sp->style); /* Often won't really change */ + UPDATE_STYLE; + if (me->sp->tag_number == element_number) + LYEnsureDoubleSpace(me); + if (me->List_Nesting_Level >= 0) + HText_NegateLineOne(me->text); + me->inLABEL = FALSE; + break; + + case HTML_OL: + me->OL_Counter[me->List_Nesting_Level < 11 ? + me->List_Nesting_Level : 11] = OL_VOID; + /* FALLTHRU */ + case HTML_DL: + /* FALLTHRU */ + case HTML_UL: + /* FALLTHRU */ + case HTML_MENU: + /* FALLTHRU */ + case HTML_DIR: + me->List_Nesting_Level--; + CTRACE((tfp, "HTML_end_element: Reducing List Nesting Level to %d\n", + me->List_Nesting_Level)); +#ifdef USE_JUSTIFY_ELTS + if (element_number == HTML_DL) + in_DT = FALSE; /*close the term that was without definition. */ +#endif + change_paragraph_style(me, me->sp->style); /* Often won't really change */ + UPDATE_STYLE; + if (me->List_Nesting_Level >= 0) + LYEnsureSingleSpace(me); + break; + + case HTML_SPAN: + /* + * Should undo anything we did based on LANG and/or DIR attributes, and + * the me->node_anchor->charset and/or yet to be added structure + * elements. - FM + */ + break; + + case HTML_BDO: + /* + * Should undo anything we did based on DIR (and/or LANG) attributes, + * and the me->node_anchor->charset and/or yet to be added structure + * elements. - FM + */ + break; + + case HTML_A: + /* + * Ignore any spurious A end tags. - FM + */ + if (me->inA == FALSE) + break; + /* + * Set to know that we are no longer in an anchor. + */ + me->inA = FALSE; +#ifdef MARK_HIDDEN_LINKS + if (non_empty(hidden_link_marker) && + HText_isAnchorBlank(me->text, me->CurrentANum)) { + HText_appendText(me->text, hidden_link_marker); + } +#endif + UPDATE_STYLE; + if (me->inBoldA == TRUE && me->inBoldH == FALSE) + HText_appendCharacter(me->text, LY_BOLD_END_CHAR); + HText_endAnchor(me->text, me->CurrentANum); + me->CurrentANum = 0; + me->inBoldA = FALSE; + if (me->Underline_Level > 0 && me->inUnderline == FALSE) { + HText_appendCharacter(me->text, LY_UNDERLINE_START_CHAR); + me->inUnderline = TRUE; + } + break; + + case HTML_MAP: + FREE(me->map_address); + break; + + case HTML_BODYTEXT: + /* + * We may need to look at this someday to deal with OBJECTs optimally, + * but just ignore it for now. - FM + */ + change_paragraph_style(me, me->sp->style); /* Often won't really change */ + break; + + case HTML_TEXTFLOW: + /* + * We may need to look at this someday to deal with APPLETs optimally, + * but just ignore it for now. - FM + */ + change_paragraph_style(me, me->sp->style); /* Often won't really change */ + break; + + case HTML_FIG: + LYHandleFIG(me, NULL, NULL, + 0, + 0, + NULL, + NULL, NO, FALSE, &intern_flag); + break; + + case HTML_OBJECT: + /* + * Finish the data off. + */ + { + int s = 0, e = 0; + char *start = NULL, *first_end = NULL, *last_end = NULL; + char *first_map = NULL, *last_map = NULL; + BOOL have_param = FALSE; + char *data = NULL; + + HTChunkTerminate(&me->object); + data = me->object.data; + while ((cp = StrChr(data, '<')) != NULL) { + /* + * Look for nested OBJECTs. This procedure could get tripped + * up if invalid comments are present in the content, or if an + * OBJECT end tag is present in a quoted attribute. - FM + */ + if (!StrNCmp(cp, "" */ + if (*matched == 0 || (*matched == 1 && chunk[0] == '-')) + keep = TRUE; + } else { + /* look for "" */ + if ((*matched == 1 || *matched == 2) && chunk[0] == '-') { + *comment = FALSE; + keep = TRUE; + } + } else { + keep = TRUE; + } + break; + default: + if (isalpha(uc) || uc == '/') { + keep = !*comment; + } else { + keep = FALSE; + } + break; + } + + if (keep) { + if (*matched < (SCAN_LEN - 2)) { + chunk[*matched] = (char) uc; + *matched += 1; + if (uc == '>') { + unsigned n; + + chunk[*matched] = 0; + for (n = 0; n < TABLESIZE(table); ++n) { + if (!strcmp(chunk, table[n].name)) { + *scanned = table[n].code; + CTRACE2(TRACE_SGML, (tfp, "...matched '%s'\n", + chunk)); + break; + } + } + } + } else { + *matched = 0; + } + } else { + *matched = 0; + } + chunk[*matched] = 0; + + CTRACE2(TRACE_SGML, (tfp, "%s %s%s:%d:%s\n", + keep ? "KEEP" : "SKIP", + *comment ? "*" : "", + visible_char(uc), *matched, chunk)); + + fputc(ch, ofp); + status = TRUE; + } + return status; +} + +/* + * Adds a link to a bookmark file, creating the file if it doesn't already + * exist, and making sure that no_cache is set for a pre-existing, cached file, + * so that the change will be evident on return to to that file. - FM + */ +void save_bookmark_link(const char *address, + const char *title) +{ + FILE *fp; + BOOLEAN first_time = FALSE; + const char *filename; + char *bookmark_URL = NULL; + char filename_buffer[LY_MAXPATH]; + char *Address = NULL; + char *Title = NULL; + char *trailing_tag = NULL; + int i, c; + bstring *string_data = NULL; + bstring *tmp_data = NULL; + DocAddress WWWDoc; + HTParentAnchor *tmpanchor; + HText *text; + + /* + * Make sure we were passed something to save. - FM + */ + if (isEmpty(address)) { + HTAlert(MALFORMED_ADDRESS); + return; + } + + /* + * Offer a choice of bookmark files, or get the default. - FMG + */ + filename = get_bookmark_filename(&bookmark_URL); + + /* + * If filename is NULL, must create a new file. If filename is a space, an + * invalid bookmark file was selected, or if zero-length, the user + * cancelled. Ignore request in both cases. Otherwise, make a copy before + * anything might change the static get_bookmark_filename() buffer. - FM + */ + if (filename == NULL) { + first_time = TRUE; + filename_buffer[0] = '\0'; + } else { + if (*filename == '\0' || !strcmp(filename, " ")) { + FREE(bookmark_URL); + return; + } + LYStrNCpy(filename_buffer, filename, sizeof(filename_buffer) - 1); + } + + /* + * If BookmarkPage is NULL, something went wrong, so ignore the request. - + * FM + */ + if (isEmpty(BookmarkPage)) { + FREE(bookmark_URL); + return; + } + + /* + * If the link will be added to the same bookmark file, get confirmation. + * - FM + */ + if (LYMultiBookmarks != MBM_OFF) { + const char *url = HTLoadedDocumentURL(); + const char *page = ((*BookmarkPage == '.') + ? (BookmarkPage + 1) + : BookmarkPage); + + if (strstr(url, page) != NULL) { + LYMBM_statusline(MULTIBOOKMARKS_SELF); + c = LYgetch_single(); + if (c != 'L') { + FREE(bookmark_URL); + return; + } + } + } + + /* + * Allow user to change the title. - FM + */ + do { + if (HTCJK == JAPANESE) { + switch (kanji_code) { + case EUC: + BStrAlloc(tmp_data, MAX_LINE + 2 * (int) strlen(title)); + TO_EUC((const unsigned char *) title, (unsigned char *) tmp_data->str); + break; + case SJIS: + BStrAlloc(tmp_data, MAX_LINE + (int) strlen(title)); + TO_SJIS((const unsigned char *) title, (unsigned char *) tmp_data->str); + break; + default: + break; + } + BStrCopy0(string_data, tmp_data ? tmp_data->str : title); + } else { + BStrCopy0(string_data, title); + } + LYReduceBlanks(string_data->str); + LYMBM_statusline(TITLE_PROMPT); + LYgetBString(&string_data, FALSE, 0, NORECALL); + if (isBEmpty(string_data)) { + LYMBM_statusline(CANCELLED); + LYSleepMsg(); + FREE(bookmark_URL); + BStrFree(tmp_data); + return; + } + } while (!havevisible(string_data->str)); + + /* + * Create the Title with any left-angle-brackets converted to < entities + * and any ampersands converted to & entities. - FM + * + * Convert 8-bit letters to &#xUUUU to avoid dependencies from display + * character set which may need changing. Do NOT convert any 8-bit chars + * if we have CJK display. - LP + */ + LYformTitle(&Title, string_data->str); + LYEntify(&Title, TRUE); + if (UCSaveBookmarksInUnicode && + have8bit(Title) && (!LYHaveCJKCharacterSet)) { + char *p = title_convert8bit(Title); + + if (p != 0) { + FREE(Title); + Title = p; + } + } + + /* + * Create the bookmark file, if it doesn't exist already, Otherwise, open + * the pre-existing bookmark file. - FM + */ + SetDefaultMode(O_TEXT); + if (first_time) { + /* + * Seek it in the home path. - FM + */ + LYAddPathToHome(filename_buffer, + sizeof(filename_buffer), + BookmarkPage); + } + CTRACE((tfp, "\nsave_bookmark_link: SEEKING %s\n AS %s\n\n", + BookmarkPage, filename_buffer)); + if ((fp = fopen(filename_buffer, (first_time ? TXT_W : TXT_A))) == NULL) { + LYMBM_statusline(BOOKMARK_OPEN_FAILED); + LYSleepAlert(); + FREE(Title); + FREE(bookmark_URL); + BStrFree(tmp_data); + return; + } + + /* + * Convert all ampersands in the address to & entities. - FM + */ + StrAllocCopy(Address, address); + LYEntify(&Address, FALSE); + + /* + * Copy the existing bookmark file to a temporary file, to allow update. + */ + if (!first_time) { + BOOLEAN empty_file = TRUE; + FILE *bp = tmpfile(); + char chunk[SCAN_LEN]; + int ch; + int offset_Chr = 0; + int offset_Tag = 0; + int limit_Edit = -1; + int last_OL = -1; + int last_UL = -1; + int count_OL = 0; + int count_UL = 0; + int matched = 0; + int comment = FALSE; + char found_tag[scanIgnore + 1]; + SCAN_TRAILING scanned = scanUnknown; + + rewind(fp); + memset(found_tag, 0, sizeof(found_tag)); + while (scan_trailing(bp, fp, chunk, &matched, &comment, &scanned)) { + empty_file = FALSE; + + /* remember where the most recent tag starts */ + if (matched == 0) + offset_Tag = offset_Chr; + + found_tag[scanned] = TRUE; + switch (scanned) { + case scanUnknown: + case scanIgnore: + break; + case scanHeadOL: + ++count_OL; + break; + case scanHeadUL: + ++count_UL; + break; + case scanTailOL: + count_OL--; + last_OL = offset_Tag; + break; + case scanTailUL: + count_UL--; + last_UL = offset_Tag; + break; + case scanTailBody: + case scanTailHtml: + if (limit_Edit < 0) + limit_Edit = offset_Tag; + break; + } + + ++offset_Chr; + } + + fflush(bp); + rewind(bp); + + /* if no BODY/HTML tag ends the file, treat it as the whole file */ + if (limit_Edit < 0) + limit_Edit = offset_Chr; + + /* if the last UL/OL offsets differ, at least one is valid */ + if (last_UL > last_OL) { + if (limit_Edit > last_UL) + limit_Edit = last_UL; + } else if (last_OL > last_UL) { + if (limit_Edit > last_OL) + limit_Edit = last_OL; + } else { + /* the initial file uses an ordered list; users may edit */ + if (count_UL > count_OL) + StrAllocCopy(trailing_tag, ""); + else + StrAllocCopy(trailing_tag, ""); + } + + if (TRACE) { + CTRACE((tfp, "Existing file length %d, insert at %d\n", + offset_Chr, limit_Edit)); + if (limit_Edit == last_UL) { + CTRACE((tfp, "...insert before last : %d\n", last_UL)); + } else if (limit_Edit == last_OL) { + CTRACE((tfp, "...insert before last : %d\n", last_OL)); + } else if (trailing_tag != NULL) { + CTRACE((tfp, "...append with %s\n", trailing_tag)); + } + } + + rewind(fp); + if (ftruncate(fileno(fp), 0) == -1) { + CTRACE((tfp, "%s: %s\n", filename_buffer, strerror(errno))); + } + + offset_Chr = 0; + while ((ch = fgetc(bp)) != EOF && !ferror(bp)) { + if (offset_Chr <= limit_Edit) + fputc(ch, fp); + else { + char buffer[2]; + + buffer[0] = (char) ch; + buffer[1] = 0; + StrAllocCat(trailing_tag, buffer); + } + ++offset_Chr; + } + fclose(bp); + + if (!found_tag[scanTailBody]) + StrAllocCat(trailing_tag, ""); + if (!found_tag[scanTailHtml]) + StrAllocCat(trailing_tag, ""); + + if (empty_file) + first_time = TRUE; + } + + /* + * If we created a new bookmark file, write the headers. - FM + * Once and forever... + */ + if (first_time) { + fprintf(fp, "%s\n", LYNX_DOCTYPE); + fprintf(fp, "\n"); + fprintf(fp, "\n"); +#if defined(SH_EX) && !defined(_WINDOWS) /* 1997/12/11 (Thu) 19:13:40 */ + if (HTCJK != JAPANESE) + LYAddMETAcharsetToFD(fp, -1); + else + fprintf(fp, "\n", + "http-equiv=\"content-type\"", + "content=\"" STR_HTML ";charset=iso-2022-jp\""); +#else + LYAddMETAcharsetToFD(fp, -1); +#endif /* !_WINDOWS */ + fprintf(fp, "%s\n\n", BOOKMARK_TITLE); + fprintf(fp, "\n"); +#ifdef _WINDOWS + fprintf(fp, "

    %s", + gettext(" You can delete links by the 'R' key
    \n

      \n")); +#else + fprintf(fp, "

      %s
      \n%s\n\n

      \n\n
        \n", + gettext("\ + You can delete links using the remove bookmark command. It is usually\n\ + the 'R' key but may have been remapped by you or your system\n\ + administrator."), + gettext("\ + This file also may be edited with a standard text editor to delete\n\ + outdated or invalid links, or to change their order."), + gettext("\ +Note: if you edit this file manually\n\ + you should not change the format within the lines\n\ + or add other HTML markup.\n\ + Make sure any bookmark link is saved as a single line.")); +#endif /* _WINDOWS */ + } + + /* + * Add the bookmark link, in Mosaic hotlist or Lynx format. - FM + */ + if (is_mosaic_hotlist) { + time_t NowTime = time(NULL); + char *TimeString = (char *) ctime(&NowTime); + + /* + * TimeString has a \n at the end. + */ + fprintf(fp, "%s %s%s\n", Address, TimeString, Title); + } else { + fprintf(fp, "
      1. %s
      2. \n", Address, Title); + if (trailing_tag != NULL) + fputs(trailing_tag, fp); + } + free(trailing_tag); + LYCloseOutput(fp); + + SetDefaultMode(O_BINARY); + /* + * If this is a cached bookmark file, set nocache for it so we'll see the + * new bookmark link when that cache is retrieved. - FM + */ + if (!first_time && nhist > 0 && bookmark_URL) { + for (i = 0; i < nhist; i++) { + if (HDOC(i).bookmark && + !strcmp(HDOC(i).address, bookmark_URL)) { + WWWDoc.address = HDOC(i).address; + WWWDoc.post_data = NULL; + WWWDoc.post_content_type = NULL; + WWWDoc.bookmark = HDOC(i).bookmark; + WWWDoc.isHEAD = FALSE; + WWWDoc.safe = FALSE; + tmpanchor = HTAnchor_findAddress(&WWWDoc); + if ((text = (HText *) HTAnchor_document(tmpanchor)) != NULL) { + HText_setNoCache(text); + } + break; + } + } + } + + /* + * Clean up and report success. + */ + BStrFree(string_data); + BStrFree(tmp_data); + FREE(Title); + FREE(Address); + FREE(bookmark_URL); + LYMBM_statusline(OPERATION_DONE); + LYSleepMsg(); +} + +/* + * Remove a link from a bookmark file. The calling function is expected to + * have used get_filename_link(), pass us the link number as cur, the + * MBM_A_subbookmark[] string as cur_bookmark_page, and to have set up no_cache + * itself. - FM + */ +void remove_bookmark_link(int cur, + char *cur_bookmark_page) +{ + FILE *fp, *nfp; + char *buf = NULL; + int n; + +#ifdef VMS + char filename_buffer[NAM$C_MAXRSS + 12]; + char newfile[NAM$C_MAXRSS + 12]; + +#define keep_tempfile FALSE +#else + char filename_buffer[LY_MAXPATH]; + char newfile[LY_MAXPATH]; + BOOLEAN keep_tempfile = FALSE; + +#ifdef UNIX + struct stat stat_buf; + BOOLEAN regular = FALSE; +#endif /* UNIX */ +#endif /* VMS */ + char homepath[LY_MAXPATH]; + + CTRACE((tfp, "remove_bookmark_link: deleting link number: %d\n", cur)); + + if (!cur_bookmark_page) + return; + LYAddPathToHome(filename_buffer, + sizeof(filename_buffer), + cur_bookmark_page); + CTRACE((tfp, "\nremove_bookmark_link: SEEKING %s\n AS %s\n\n", + cur_bookmark_page, filename_buffer)); + if ((fp = fopen(filename_buffer, TXT_R)) == NULL) { + HTAlert(BOOKMARK_OPEN_FAILED_FOR_DEL); + return; + } + + LYAddPathToHome(homepath, sizeof(homepath), ""); + if ((nfp = LYOpenScratch(newfile, homepath)) == 0) { + LYCloseInput(fp); + HTAlert(BOOKSCRA_OPEN_FAILED_FOR_DEL); + return; + } +#ifdef UNIX + /* + * Explicitly preserve bookmark file mode on Unix. - DSL + */ + if (stat(filename_buffer, &stat_buf) == 0) { + regular = (BOOLEAN) (S_ISREG(stat_buf.st_mode) && stat_buf.st_nlink == 1); + (void) chmod(newfile, HIDE_CHMOD); + if ((nfp = LYReopenTemp(newfile)) == NULL) { + (void) LYCloseInput(fp); + HTAlert(BOOKTEMP_REOPEN_FAIL_FOR_DEL); + return; + } + } +#endif /* UNIX */ + + if (is_mosaic_hotlist) { + int del_line = cur * 2; /* two lines per entry */ + + n = -3; /* skip past cookie and name lines */ + while (LYSafeGets(&buf, fp) != NULL) { + n++; + if (n == del_line || n == del_line + 1) + continue; /* remove two lines */ + if (fputs(buf, nfp) == EOF) + goto failure; + } + + } else { + char *cp; + BOOLEAN retain; + int seen; + + n = -1; + while (LYSafeGets(&buf, fp) != NULL) { + int keep_ol = FALSE; + + retain = TRUE; + seen = 0; + cp = buf; + if ((cur == 0) && LYstrstr(cp, "
        1. ")) + keep_ol = TRUE; /* Do not erase, this corrects a bug in an + older version */ + while (n < cur && (cp = LYstrstr(cp, "") || + LYstrstr((cp + 1), "\n"); + retain = FALSE; + } + cp += 8; + } + if (retain && fputs(buf, nfp) == EOF) + goto failure; + } + } + + FREE(buf); + CTRACE((tfp, "remove_bookmark_link: files: %s %s\n", + newfile, filename_buffer)); + + LYCloseInput(fp); + fp = NULL; + if (fflush(nfp) == EOF) { + CTRACE((tfp, "fflush(nfp): %s", LYStrerror(errno))); + goto failure; + } + LYCloseTempFP(nfp); + nfp = NULL; +#if defined(DOSPATH) || defined(__EMX__) + remove(filename_buffer); +#endif /* DOSPATH */ + +#ifdef UNIX + /* + * By copying onto the bookmark file, rather than renaming it, we can + * preserve the original ownership of the file, provided that it is + * writable by the current process. + * + * Changed to copy 1998-04-26 -- gil + * + * But if the copy fails, for example because the filesystem is full, we + * are left with a corrupt bookmark file. Changed back to use the previous + * mechanism [try rename(), then mv for EXDEV], except in usual cases (not + * a regular file e.g., symbolic link, or has hard links). This will let + * bookmarks survive a filesystem full condition in the "normal" case + * (bookmark is on same filesystem as home directory, is a regular file, + * has no additional hard links). + * + * If we first tried LYCopyFile, and that fails, also fall back to trying + * the other stuff. That gives a chance to recover in case the LYCopyFile + * left a corrupt target file. + * + * If there is an error, and that error may mean that the bookmark file has + * been corrupted, don't remove the temporary newfile (which should always + * be uncorrupted) in place, it may still be used to recover manually. If + * this applies, produce an additional message to that effect. The temp + * file will still be removed by normal program exit cleanup. - kw + * 1999-11-12 + */ + if (!regular) { + if (LYCopyFile(newfile, filename_buffer) == 0) { + (void) LYRemoveTemp(newfile); + return; + } + LYSleepAlert(); /* give a chance to see error from cp - kw */ + HTUserMsg(BOOKTEMP_COPY_FAIL); + keep_tempfile = TRUE; + } +#endif /* UNIX */ + + if (rename(newfile, filename_buffer) != -1) { +#ifdef MULTI_USER_UNIX + if (regular) + chmod(filename_buffer, stat_buf.st_mode & 07777); +#endif + HTSYS_purge(filename_buffer); + return; + } else { +#ifndef VMS + /* + * Rename won't work across file systems. Check if this is the case + * and do something appropriate. Used to be ODD_RENAME + */ +#if defined(_WINDOWS) || defined(WIN_EX) +#if defined(WIN_EX) + if (GetLastError() == ERROR_NOT_SAME_DEVICE) +#else /* !_WIN_EX */ + if (errno == ENOTSAM) +#endif /* _WIN_EX */ + { + if (rename(newfile, filename_buffer) != 0) { + if (LYCopyFile(newfile, filename_buffer) == 0) + remove(newfile); + } + } +#else + if (errno == EXDEV) { + static const char MV_FMT[] = "%s %s %s"; + char *buffer = 0; + const char *program; + + if ((program = HTGetProgramPath(ppMV)) != NULL) { + HTAddParam(&buffer, MV_FMT, 1, program); + HTAddParam(&buffer, MV_FMT, 2, newfile); + HTAddParam(&buffer, MV_FMT, 3, filename_buffer); + HTEndParam(&buffer, MV_FMT, 3); + if (LYSystem(buffer) == 0) { +#ifdef MULTI_USER_UNIX + if (regular) + chmod(filename_buffer, stat_buf.st_mode & 07777); +#endif + FREE(buffer); + return; + } + } + FREE(buffer); + keep_tempfile = TRUE; + goto failure; + } + CTRACE((tfp, "rename(): %s", LYStrerror(errno))); +#endif /* _WINDOWS */ +#endif /* !VMS */ + +#ifdef VMS + HTAlert(ERROR_RENAMING_SCRA); +#else + HTAlert(ERROR_RENAMING_TEMP); +#endif /* VMS */ + if (TRACE) + perror("renaming the file"); + } + + failure: + FREE(buf); + HTAlert(BOOKMARK_DEL_FAILED); + if (nfp) + LYCloseTempFP(nfp); + if (fp != NULL) + LYCloseInput(fp); + if (keep_tempfile) { + HTUserMsg2(gettext("File may be recoverable from %s during this session"), + newfile); + } else { + (void) LYRemoveTemp(newfile); + } +} + +/* + * Allows user to select sub-bookmarks files. - FMG & FM + */ +int select_multi_bookmarks(void) +{ + int c; + + /* + * If not enabled, pick the "default" (0). + */ + if (LYMultiBookmarks == MBM_OFF || LYHaveSubBookmarks() == FALSE) { + if (MBM_A_subbookmark[0]) /* If it exists! */ + return (0); + else + return (-1); + } + + /* + * For ADVANCED users, we can just mess with the status line to save the 2 + * redraws of the screen, if LYMBMAdvnced is TRUE. '=' will still show the + * screen and let them do it the "long" way. + */ + if (LYMultiBookmarks == MBM_ADVANCED && (user_mode == ADVANCED_MODE)) { + LYMBM_statusline(MULTIBOOKMARKS_SELECT); + get_advanced_choice: + c = LYgetch(); +#ifdef VMS + if (HadVMSInterrupt) { + HadVMSInterrupt = FALSE; + c = LYCharINTERRUPT2; + } +#endif /* VMS */ + if (LYisNonAlnumKeyname(c, LYK_PREV_DOC) || LYCharIsINTERRUPT_HARD(c)) { + /* + * Treat left-arrow, ^G, or ^C as cancel. + */ + return (-2); + } + if (LYisNonAlnumKeyname(c, LYK_REFRESH)) { + /* + * Refresh the screen. + */ + lynx_force_repaint(); + LYrefresh(); + goto get_advanced_choice; + } + if (LYisNonAlnumKeyname(c, LYK_ACTIVATE)) { + /* + * Assume default bookmark file on ENTER or right-arrow. + */ + return (MBM_A_subbookmark[0] ? 0 : -1); + } + switch (c) { + case '=': + /* + * Get the choice via the menu. + */ + return (select_menu_multi_bookmarks()); + + default: + /* + * Convert to an array index, act on it if valid. + * Otherwise, get another keystroke. + */ + if ((c = LYMBM2index(c)) < 0) { + goto get_advanced_choice; + } + } + /* + * See if we have a bookmark like that. + */ + return (MBM_A_subbookmark[c] ? c : -1); + } else { + /* + * Get the choice via the menu. + */ + return (select_menu_multi_bookmarks()); + } +} + +/* + * Allows user to select sub-bookmarks files. - FMG & FM + */ +int select_menu_multi_bookmarks(void) +{ + int c, d, MBM_tmp_count, MBM_allow; + int MBM_screens, MBM_from, MBM_to, MBM_current; + + /* + * If not enabled, pick the "default" (0). + */ + if (LYMultiBookmarks == MBM_OFF) + return (0); + + /* + * Filip M. Gieszczykiewicz (filipg@paranoia.com) & FM + * --------------------------------------------------- + * MBM_A_subbookmark[n] - Hold values of the respective "multi_bookmarkn" + * in the lynxrc file. + * + * MBM_A_subdescript[n] - Hold description entries in the lynxrc file. + * + * Note: MBM_A_subbookmark[0] is defined to be same value as + * "bookmark_file" in the lynxrc file and/or the startup + * "bookmark_page". + * + * We make the display of bookmarks depend on rows we have available. + * + * We load BookmarkPage with the valid MBM_A_subbookmark[n] via + * get_bookmark_filename(). Otherwise, that function returns a zero-length + * string to indicate a cancel, a single space to indicate an invalid + * choice, or NULL to indicate an inaccessible file. + */ + MBM_allow = (LYlines - 7); /* We need 7 for header and footer */ + /* + * Screen big enough? + */ + if (MBM_allow <= 0) { + /* + * Too small. + */ + HTAlert(MULTIBOOKMARKS_SMALL); + return (-2); + } + + MBM_screens = (MBM_V_MAXFILES / MBM_allow) + 1; /* int rounds off low. */ + + MBM_current = 1; /* Gotta start somewhere :-) */ + + for (;;) { + MBM_from = MBM_allow * MBM_current - MBM_allow; + if (MBM_from < 0) + MBM_from = 0; /* 0 is default bookmark... */ + if (MBM_current != 1) + MBM_from++; + + MBM_to = (MBM_allow * MBM_current); + if (MBM_to > MBM_V_MAXFILES) + MBM_to = MBM_V_MAXFILES; + + /* + * Display menu of bookmarks. NOTE that we avoid printw()'s to + * increase the chances that any non-ASCII or multibyte/CJK characters + * will be handled properly. - FM + */ + LYclear(); + LYmove(1, 5); + lynx_start_h1_color(); + if (MBM_screens > 1) { + char *shead_buffer = 0; + + HTSprintf0(&shead_buffer, + MULTIBOOKMARKS_SHEAD_MASK, MBM_current, MBM_screens); + LYaddstr(shead_buffer); + FREE(shead_buffer); + } else { + LYaddstr(MULTIBOOKMARKS_SHEAD); + } + + lynx_stop_h1_color(); + + MBM_tmp_count = 0; + for (c = MBM_from; c <= MBM_to; c++) { + LYmove(3 + MBM_tmp_count, 5); + LYaddch(UCH(LYindex2MBM(c))); + LYaddstr(" : "); + if (MBM_A_subdescript[c]) + LYaddstr(MBM_A_subdescript[c]); + LYmove(3 + MBM_tmp_count, 36); + LYaddch('('); + if (MBM_A_subbookmark[c]) + LYaddstr(MBM_A_subbookmark[c]); + LYaddch(')'); + MBM_tmp_count++; + } + + /* + * Don't need to show it if it all fits on one screen! + */ + if (MBM_screens > 1) { + LYmove(LYlines - 2, 0); + LYaddstr("'"); + lynx_start_bold(); + LYaddstr("["); + lynx_stop_bold(); + LYaddstr("' "); + LYaddstr(PREVIOUS); + LYaddstr(", '"); + lynx_start_bold(); + LYaddstr("]"); + lynx_stop_bold(); + LYaddstr("' "); + LYaddstr(NEXT_SCREEN); + } + + LYMBM_statusline(MULTIBOOKMARKS_SAVE); + + for (;;) { + c = LYgetch(); +#ifdef VMS + if (HadVMSInterrupt) { + HadVMSInterrupt = FALSE; + c = 7; + } +#endif /* VMS */ + + if ((d = LYMBM2index(c)) >= 0) { + /* + * See if we have a bookmark like that. + */ + if (non_empty(MBM_A_subbookmark[d])) + return (d); + + show_bookmark_not_defined(); + LYMBM_statusline(MULTIBOOKMARKS_SAVE); + } else if (LYisNonAlnumKeyname(c, LYK_PREV_DOC) || + c == 7 || c == 3) { + /* + * Treat left-arrow, ^G, or ^C as cancel. + */ + return (-2); + } else if (LYisNonAlnumKeyname(c, LYK_REFRESH)) { + /* + * Refresh the screen. + */ + lynx_force_repaint(); + LYrefresh(); + } else if (LYisNonAlnumKeyname(c, LYK_ACTIVATE)) { + /* + * Assume default bookmark file on ENTER or right-arrow. + */ + return (MBM_A_subbookmark[0] ? 0 : -1); + } else if ((c == ']' || LYisNonAlnumKeyname(c, LYK_NEXT_PAGE)) && + MBM_screens > 1) { + /* + * Next range, if available. + */ + if (++MBM_current > MBM_screens) + MBM_current = 1; + break; + } + + else if ((c == '[' || LYisNonAlnumKeyname(c, LYK_PREV_PAGE)) && + MBM_screens > 1) { + /* + * Previous range, if available. + */ + if (--MBM_current <= 0) + MBM_current = MBM_screens; + break; + } + } + } +} + +/* + * This function returns TRUE if we have sub-bookmarks defined. Otherwise + * (i.e., only the default bookmark file is defined), it returns FALSE. - FM + */ +BOOLEAN LYHaveSubBookmarks(void) +{ + int i; + + for (i = 1; i < MBM_V_MAXFILES; i++) { + if (non_empty(MBM_A_subbookmark[i])) + return (TRUE); + } + + return (FALSE); +} + +/* + * This function passes a string to _statusline(), making sure it is at the + * bottom of the screen if LYMultiBookmarks is not MBM_OFF, otherwise, letting + * it go to the normal statusline position based on the current user mode. We + * want to use _statusline() so that any multibyte/CJK characters in the string + * will be handled properly. - FM + */ +void LYMBM_statusline(const char *text) +{ + if (LYMultiBookmarks != MBM_OFF && user_mode == NOVICE_MODE) { + LYStatusLine = (LYlines - 1); + _statusline(text); + LYStatusLine = -1; + } else { + _statusline(text); + } +} + +/* + * Check whether we have any visible (non-blank) chars. + */ +static BOOLEAN havevisible(const char *Title) +{ + BOOLEAN result = FALSE; + const char *p = Title; + unsigned char c; + long unicode; + + UCTransToUni(0, -1); /* reset internal state */ + for (; *p; p++) { + c = UCH(TOASCII(*p)); + if (c > 32 && c < 127) { + result = TRUE; + break; + } + if (c <= 32 || c == 127) + continue; + if (LYHaveCJKCharacterSet || !UCCanUniTranslateFrom(current_char_set)) { + result = TRUE; + break; + } + unicode = UCTransToUni(*p, current_char_set); + if (unicode == ucNeedMore) + continue; + if (unicode > 32 && unicode < 127) { + result = TRUE; + break; + } + if (unicode <= 32 || unicode == 0xa0 || unicode == 0xad) + continue; + if (unicode < 0x2000 || unicode >= 0x200f) { + result = TRUE; + break; + } + } + return (result); +} + +/* + * Check whether string have 8 bit chars. + */ +static BOOLEAN have8bit(const char *Title) +{ + const char *p = Title; + + for (; *p; p++) { + if (UCH(*p) > 127) + return (TRUE); + } + return (FALSE); /* if we came here */ +} + +/* + * Ok, title have 8-bit characters and they are in display charset. Bookmarks + * is a permanent file. To avoid dependencies from display character set which + * may be changed with time we store 8-bit characters as numeric character + * reference (NCR), so where the character encoded as unicode number in form of + * &#xUUUU; + * + * To make bookmarks more readable for human (&#xUUUU certainly not) we add a + * comment with '7-bit approximation' from the converted string. This is a + * valid HTML and bookmarks code. + * + * We do not want use META charset tag in bookmarks file: it will never be + * changed later :-( + * + * NCR's translation is part of I18N and HTML4.0 supported starting with Lynx + * 2.7.2, Netscape 4.0 and MSIE 4.0. Older versions fail. + */ +static char *title_convert8bit(const char *Title) +{ + const char *p = Title; + char *p0; + char *q; + char *comment = NULL; + char *ncr = NULL; + char *buf = NULL; + int charset_in = current_char_set; + int charset_out = UCGetLYhndl_byMIME("us-ascii"); + + for (; *p; p++) { + char temp[2]; + + LYStrNCpy(temp, p, sizeof(temp) - 1); + if (UCH(*temp) <= 127) { + StrAllocCat(comment, temp); + StrAllocCat(ncr, temp); + } else if (charset_out >= 0) { + long unicode; + char replace_buf[32]; + + if (UCTransCharStr(replace_buf, (int) sizeof(replace_buf), *temp, + charset_in, charset_out, YES) > 0) + StrAllocCat(comment, replace_buf); + + unicode = UCTransToUni(*temp, charset_in); + + StrAllocCat(ncr, "&#"); + sprintf(replace_buf, "%ld", unicode); + StrAllocCat(ncr, replace_buf); + StrAllocCat(ncr, ";"); + } + } + + if (comment != NULL) { + /* + * Cleanup comment, collapse multiple dashes into one dash, skip '>'. + */ + for (q = p0 = comment; *p0; p0++) { + if (UCH(TOASCII(*p0)) >= 32 && + *p0 != '>' && + (q == comment || *p0 != '-' || *(q - 1) != '-')) { + *q++ = *p0; + } + } + *q = '\0'; + + /* + * valid bookmark should be a single line (no linebreaks!). + */ + StrAllocCat(buf, ""); + StrAllocCat(buf, ncr); + + FREE(comment); + } + FREE(ncr); + return (buf); +} + +/* + * Since this is the "Default Bookmark File", we save it as a global, and as + * the first MBM_A_subbookmark entry. + */ +void set_default_bookmark_page(char *value) +{ + if (value != 0) { + if (bookmark_page == NULL + || strcmp(bookmark_page, value)) { + StrAllocCopy(bookmark_page, value); + } + StrAllocCopy(BookmarkPage, bookmark_page); + StrAllocCopy(MBM_A_subbookmark[0], bookmark_page); + StrAllocCopy(MBM_A_subdescript[0], MULTIBOOKMARKS_DEFAULT); + } +} diff --git a/src/LYBookmark.h b/src/LYBookmark.h new file mode 100644 index 0000000..a9eb494 --- /dev/null +++ b/src/LYBookmark.h @@ -0,0 +1,25 @@ +#ifndef LYBOOKMARK_H +#define LYBOOKMARK_H + +#ifndef LYSTRUCTS_H +#include +#endif /* LYSTRUCTS_H */ + +#ifdef __cplusplus +extern "C" { +#endif + extern BOOLEAN LYHaveSubBookmarks(void); + extern const char *get_bookmark_filename(char **name); + extern int LYMBM2index(int ch); + extern unsigned LYindex2MBM(int n); + extern int select_menu_multi_bookmarks(void); + extern int select_multi_bookmarks(void); + extern void LYMBM_statusline(const char *text); + extern void remove_bookmark_link(int cur, char *cur_bookmark_page); + extern void save_bookmark_link(const char *address, const char *title); + extern void set_default_bookmark_page(char *value); + +#ifdef __cplusplus +} +#endif +#endif /* LYBOOKMARK_H */ diff --git a/src/LYCgi.c b/src/LYCgi.c new file mode 100644 index 0000000..72493b2 --- /dev/null +++ b/src/LYCgi.c @@ -0,0 +1,757 @@ +/* + * $LynxId: LYCgi.c,v 1.72 2018/03/18 18:56:05 tom Exp $ + * Lynx CGI support LYCgi.c + * ================ + * + * Authors + * GL George Lindholm + * + * History + * 15 Jun 95 Created as way to provide a lynx based service with + * dynamic pages without the need for a http daemon. GL + * 27 Jun 95 Added (command line) support. Various cleanup + * and bug fixes. GL + * 04 Sep 97 Added support for PATH_INFO scripts. JKT + * + * Bugs + * If the called scripts aborts before sending the mime headers then + * lynx hangs. + * + * Should do something about SIGPIPE, (but then it should never happen) + * + * No support for redirection. Or mime-types. + * + * Should try and parse for a HTTP 1.1 header in case we are "calling" a + * nph- script. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +static char **env = NULL; /* Environment variables */ +static unsigned envc_size = 0; /* Slots in environment array */ +static unsigned envc = 0; /* Slots used so far */ +static HTList *alloced = NULL; + +#if defined(LYNXCGI_LINKS) && !defined(__MINGW32__) +static char *user_agent = NULL; +static char *server_software = NULL; +static char *accept_language = NULL; +static char *post_len = NULL; +#endif /* LYNXCGI_LINKS */ + +static void add_environment_value(const char *env_value); + +#define PERROR(msg) CTRACE((tfp, "LYNXCGI: %s: %s\n", msg, LYStrerror(errno))) + +#define PUTS(buf) (*target->isa->put_block)(target, buf, strlen(buf)) + +#ifdef LY_FIND_LEAKS +static void free_alloced_lynxcgi(void) +{ + void *ptr; + + while ((ptr = HTList_removeLastObject(alloced)) != NULL) { + FREE(ptr); + } + FREE(alloced); +#ifdef LYNXCGI_LINKS + FREE(user_agent); + FREE(server_software); +#endif +} +#endif /* LY_FIND_LEAKS */ + +static void remember_alloced(void *ptr) +{ + if (!alloced) { + alloced = HTList_new(); +#ifdef LY_FIND_LEAKS + atexit(free_alloced_lynxcgi); +#endif + } + HTList_addObject(alloced, ptr); +} + +/* + * Simple routine for expanding the environment array and adding a value to + * it + */ +static void add_environment_value(const char *env_value) +{ + if (envc == envc_size) { /* Need some more slots */ + envc_size += 10; + if (env) { + env = (char **) realloc(env, + sizeof(env[0]) * (envc_size + 2)); + /* + terminator and base 0 */ + } else { + env = (char **) malloc(sizeof(env[0]) * (envc_size + 2)); + /* + terminator and base 0 */ + remember_alloced(env); + } + if (env == NULL) { + outofmem(__FILE__, "LYCgi"); + } + } + + env[envc++] = DeConst(env_value); + env[envc] = NULL; /* Make sure it is always properly terminated */ +} + +/* + * Add the value of an existing environment variable to those passed on to the + * lynxcgi script. + */ +void add_lynxcgi_environment(const char *variable_name) +{ + char *env_value; + + env_value = LYGetEnv(variable_name); + if (env_value != NULL) { + char *add_value = NULL; + + HTSprintf0(&add_value, "%s=%s", variable_name, env_value); + add_environment_value(add_value); + remember_alloced(add_value); + } +} + +#ifdef __MINGW32__ +static int LYLoadCGI(const char *arg, + HTParentAnchor *anAnchor, + HTFormat format_out, + HTStream *sink) +{ + (void) arg; + (void) anAnchor; + (void) format_out; + (void) sink; + return -1; +} +#else +#ifdef LYNXCGI_LINKS +/* + * Wrapper for exec_ok(), confirming with user if the link text is not visible + * in the status line. + */ +static BOOL can_exec_cgi(const char *linktext, const char *linkargs) +{ + const char *format = gettext("Do you want to execute \"%s\"?"); + char *message = NULL; + char *command = NULL; + char *p; + BOOL result = TRUE; + + if (!exec_ok(HTLoadedDocumentURL(), linktext, CGI_PATH)) { + /* exec_ok gives out msg. */ + result = FALSE; + } else { + StrAllocCopy(command, linktext); + if (non_empty(linkargs)) { + HTSprintf(&command, " %s", linkargs); + } + HTUnEscape(command); + for (p = command; *p; ++p) + if (*p == '+') + *p = ' '; + HTSprintf0(&message, format, command); + result = HTConfirm(message); + FREE(message); + FREE(command); + } + return result; +} +#endif /* LYNXCGI_LINKS */ + +static int LYLoadCGI(const char *arg, + HTParentAnchor *anAnchor, + HTFormat format_out, + HTStream *sink) +{ + int status = 0; + +#ifdef LYNXCGI_LINKS +#ifndef VMS + char *cp; + struct stat stat_buf; + char *pgm = NULL; /* executable */ + char *pgm_args = NULL; /* and its argument(s) */ + int statrv; + char *orig_pgm = NULL; /* Path up to ? as given, URL-escaped */ + char *document_root = NULL; /* Corrected value of DOCUMENT_ROOT */ + char *path_info = NULL; /* PATH_INFO extracted from pgm */ + char *pgm_buff = NULL; /* PATH_INFO extraction buffer */ + char *path_translated; /* From document_root/path_info */ + + if (isEmpty(arg) || strlen(arg) <= 8) { + HTAlert(BAD_REQUEST); + status = -2; + return (status); + + } else { + if (StrNCmp(arg, "lynxcgi://localhost", 19) == 0) { + StrAllocCopy(pgm, arg + 19); + } else { + StrAllocCopy(pgm, arg + 8); + } + if ((cp = StrChr(pgm, '?')) != NULL) { /* Need to terminate executable */ + *cp++ = '\0'; + pgm_args = cp; + } + } + + StrAllocCopy(orig_pgm, pgm); + if (trimPoundSelector(pgm) != NULL) { + /* + * Strip a #fragment from path. In this case any pgm_args found above + * will also be bogus, since the '?' came after the '#' and is part of + * the fragment. Note that we don't handle the case where a '#' + * appears after a '?' properly according to URL rules. - kw + */ + pgm_args = NULL; + } + HTUnEscape(pgm); + + /* BEGIN WebSter Mods */ + /* If pgm is not stat-able, see if PATH_INFO data is at the end of pgm */ + if ((statrv = stat(pgm, &stat_buf)) < 0) { + StrAllocCopy(pgm_buff, pgm); + while (statrv < 0 || (statrv = stat(pgm_buff, &stat_buf)) < 0) { + if ((cp = strrchr(pgm_buff, '/')) != NULL) { + *cp = '\0'; + statrv = 1; /* force new stat() - kw */ + } else { + PERROR("strrchr(pgm_buff, '/') returned NULL"); + break; + } + } + + if (statrv < 0) { + /* Did not find PATH_INFO data */ + PERROR("stat() of pgm_buff failed"); + } else { + /* Found PATH_INFO data. Strip it off of pgm and into path_info. */ + StrAllocCopy(path_info, pgm + strlen(pgm_buff)); + /* The following is safe since pgm_buff was derived from pgm + by stripping stuff off its end and by HTUnEscaping, so we + know we have enough memory allocated for pgm. Note that + pgm_args may still point into that memory, so we cannot + reallocate pgm here. - kw */ + strcpy(pgm, pgm_buff); + CTRACE((tfp, + "LYNXCGI: stat() of %s succeeded, path_info=\"%s\".\n", + pgm_buff, path_info)); + } + FREE(pgm_buff); + } + /* END WebSter Mods */ + + if (statrv != 0) { + /* + * Neither the path as given nor any components examined by backing up + * were stat()able. - kw + */ + HTAlert(gettext("Unable to access cgi script")); + PERROR("stat() failed"); + status = -4; + + } else +#ifdef _WINDOWS /* 1998/01/14 (Wed) 09:16:04 */ +#define isExecutable(mode) (mode & (S_IXUSR)) +#else +#define isExecutable(mode) (mode & (S_IXUSR|S_IXGRP|S_IXOTH)) +#endif + if (!(S_ISREG(stat_buf.st_mode) && isExecutable(stat_buf.st_mode))) { + /* + * Not a runnable file, See if we can load it using "file:" code. + */ + char *new_arg = NULL; + + /* + * But try "file:" only if the file we are looking at is the path as + * given (no path_info was extracted), otherwise it will be to + * confusing to know just what file is loaded. - kw + */ + if (path_info) { + CTRACE((tfp, + "%s is not a file and %s not an executable, giving up.\n", + orig_pgm, pgm)); + FREE(path_info); + FREE(pgm); + FREE(orig_pgm); + status = -4; + return (status); + } + + LYLocalFileToURL(&new_arg, orig_pgm); + + CTRACE((tfp, "%s is not an executable file, passing the buck.\n", arg)); + status = HTLoadFile(new_arg, anAnchor, format_out, sink); + FREE(new_arg); + + } else if (path_info && + anAnchor != HTMainAnchor && + !(reloading && anAnchor->document) && + strcmp(arg, HTLoadedDocumentURL()) && + HText_AreDifferent(anAnchor, arg) && + HTUnEscape(orig_pgm) && + !can_exec_cgi(orig_pgm, "")) { + /* + * If we have extra path info and are not just reloading the current, + * check the full file path (after unescaping) now to catch forbidden + * segments. - kw + */ + status = HT_NOT_LOADED; + + } else if (no_lynxcgi) { + HTUserMsg(CGI_DISABLED); + status = HT_NOT_LOADED; + + } else if (no_bookmark_exec && + anAnchor != HTMainAnchor && + !(reloading && anAnchor->document) && + strcmp(arg, HTLoadedDocumentURL()) && + HText_AreDifferent(anAnchor, arg) && + HTLoadedDocumentBookmark()) { + /* + * If we are reloading a lynxcgi document that had already been loaded, + * the various checks above should allow it even if no_bookmark_exec is + * TRUE an we are not now coming from a bookmark page. - kw + */ + HTUserMsg(BOOKMARK_EXEC_DISABLED); + status = HT_NOT_LOADED; + + } else if (anAnchor != HTMainAnchor && + !(reloading && anAnchor->document) && + strcmp(arg, HTLoadedDocumentURL()) && + HText_AreDifferent(anAnchor, arg) && + !can_exec_cgi(pgm, pgm_args)) { + /* + * If we are reloading a lynxcgi document that had already been loaded, + * the various checks above should allow it even if exec_ok() would + * reject it because we are not now coming from a document with a URL + * allowed by TRUSTED_LYNXCGI rules. - kw + */ + status = HT_NOT_LOADED; + + } else { + HTFormat format_in; + HTStream *target = NULL; /* Unconverted data */ + int fd1[2], fd2[2]; + char buf[MAX_LINE]; + int pid; + +#ifdef HAVE_TYPE_UNIONWAIT + union wait wstatus; + +#else + int wstatus; +#endif + + fd1[0] = -1; + fd1[1] = -1; + fd2[0] = -1; + fd2[1] = -1; + + if (anAnchor->isHEAD || keep_mime_headers) { + + /* Show output as plain text */ + format_in = WWW_PLAINTEXT; + } else { + + /* Decode full HTTP response */ + format_in = HTAtom_for("www/mime"); + } + + target = HTStreamStack(format_in, + format_out, + sink, anAnchor); + + if (target == NULL) { + char *tmp = 0; + + HTSprintf0(&tmp, CANNOT_CONVERT_I_TO_O, + HTAtom_name(format_in), + HTAtom_name(format_out)); + HTAlert(tmp); + FREE(tmp); + status = HT_NOT_LOADED; + + } else if (anAnchor->post_data && pipe(fd1) < 0) { + HTAlert(CONNECT_SET_FAILED); + PERROR("pipe() failed"); + status = -3; + + } else if (pipe(fd2) < 0) { + HTAlert(CONNECT_SET_FAILED); + PERROR("pipe() failed"); + close(fd1[0]); + close(fd1[1]); + status = -3; + + } else { + static BOOL first_time = TRUE; /* One time setup flag */ + + if (first_time) { /* Set up static environment variables */ + first_time = FALSE; /* Only once */ + + add_environment_value("REMOTE_HOST=localhost"); + add_environment_value("REMOTE_ADDR=127.0.0.1"); + + HTSprintf0(&user_agent, "HTTP_USER_AGENT=%s/%s libwww/%s", + LYNX_NAME, LYNX_VERSION, HTLibraryVersion); + add_environment_value(user_agent); + + HTSprintf0(&server_software, "SERVER_SOFTWARE=%s/%s", + LYNX_NAME, LYNX_VERSION); + add_environment_value(server_software); + } + fflush(stdout); + fflush(stderr); + CTRACE_FLUSH(tfp); + + if ((pid = fork()) > 0) { /* The good, */ + ssize_t chars; + off_t total_chars; + + close(fd2[1]); + + if (anAnchor->post_data) { + ssize_t written; + int remaining, total_written = 0; + + close(fd1[0]); + + /* We have form data to push across the pipe */ + if (TRACE) { + CTRACE((tfp, + "LYNXCGI: Doing post, content-type '%s'\n", + anAnchor->post_content_type)); + CTRACE((tfp, "LYNXCGI: Writing:\n")); + trace_bstring(anAnchor->post_data); + CTRACE((tfp, "----------------------------------\n")); + } + remaining = BStrLen(anAnchor->post_data); + while ((written = write(fd1[1], + BStrData(anAnchor->post_data) + total_written, + (size_t) remaining)) != 0) { + if (written < 0) { +#ifdef EINTR + if (errno == EINTR) + continue; +#endif /* EINTR */ +#ifdef ERESTARTSYS + if (errno == ERESTARTSYS) + continue; +#endif /* ERESTARTSYS */ + PERROR("write() of POST data failed"); + break; + } + CTRACE((tfp, "LYNXCGI: Wrote %d bytes of POST data.\n", + (int) written)); + total_written += (int) written; + remaining -= (int) written; + if (remaining == 0) + break; + } + if (remaining != 0) { + CTRACE((tfp, "LYNXCGI: %d bytes remain unwritten!\n", + remaining)); + } + close(fd1[1]); + } + + HTReadProgress(total_chars = 0, (off_t) 0); + while ((chars = read(fd2[0], buf, sizeof(buf))) != 0) { + if (chars < 0) { +#ifdef EINTR + if (errno == EINTR) + continue; +#endif /* EINTR */ +#ifdef ERESTARTSYS + if (errno == ERESTARTSYS) + continue; +#endif /* ERESTARTSYS */ + PERROR("read() of CGI output failed"); + break; + } + total_chars += (int) chars; + HTReadProgress(total_chars, (off_t) 0); + CTRACE((tfp, "LYNXCGI: Rx: %.*s\n", (int) chars, buf)); + (*target->isa->put_block) (target, buf, (int) chars); + } + + if (chars < 0 && total_chars == 0) { + status = HT_NOT_LOADED; + (*target->isa->_abort) (target, NULL); + target = NULL; + } else if (chars != 0) { + status = HT_PARTIAL_CONTENT; + } else { + status = HT_LOADED; + } + +#ifndef HAVE_WAITPID + while (wait(&wstatus) != pid) ; /* do nothing */ +#else + while (-1 == waitpid(pid, &wstatus, 0)) { /* wait for child */ +#ifdef EINTR + if (errno == EINTR) + continue; +#endif /* EINTR */ +#ifdef ERESTARTSYS + if (errno == ERESTARTSYS) + continue; +#endif /* ERESTARTSYS */ + break; + } +#endif /* !HAVE_WAITPID */ + close(fd2[0]); + + } else if (pid == 0) { /* The Bad, */ + char **argv = NULL; + int argv_cnt = 3; /* name, one arg and terminator */ + char **cur_argv = NULL; + int exec_errno; + + /* Set up output pipe */ + close(fd2[0]); + dup2(fd2[1], fileno(stdout)); /* Should check success code */ + dup2(fd2[1], fileno(stderr)); + close(fd2[1]); + + if (non_empty(language)) { + HTSprintf0(&accept_language, "HTTP_ACCEPT_LANGUAGE=%s", language); + add_environment_value(accept_language); + } + + if (non_empty(pref_charset)) { + cp = NULL; + StrAllocCopy(cp, "HTTP_ACCEPT_CHARSET="); + StrAllocCat(cp, pref_charset); + add_environment_value(cp); + } + + if (anAnchor->post_data && + anAnchor->post_content_type) { + cp = NULL; + StrAllocCopy(cp, "CONTENT_TYPE="); + StrAllocCat(cp, anAnchor->post_content_type); + add_environment_value(cp); + } + + if (anAnchor->post_data) { /* post script, read stdin */ + close(fd1[1]); + dup2(fd1[0], fileno(stdin)); + close(fd1[0]); + + /* Build environment variables */ + + add_environment_value("REQUEST_METHOD=POST"); + + HTSprintf0(&post_len, "CONTENT_LENGTH=%d", + BStrLen(anAnchor->post_data)); + add_environment_value(post_len); + } else { + close(fileno(stdin)); + + if (anAnchor->isHEAD) { + add_environment_value("REQUEST_METHOD=HEAD"); + } + } + + /* + * Set up argument line, mainly for scripts + */ + if (pgm_args != NULL) { + for (cp = pgm_args; *cp != '\0'; cp++) { + if (*cp == '+') { + argv_cnt++; + } + } + } + + argv = (char **) malloc((unsigned) argv_cnt * sizeof(char *)); + + if (argv == NULL) { + outofmem(__FILE__, "LYCgi"); + } + + cur_argv = argv + 1; /* For argv[0] */ + if (pgm_args != NULL) { + char *cr; + + /* Data for a get/search form */ + if (is_www_index) { + add_environment_value("REQUEST_METHOD=SEARCH"); + } else if (!anAnchor->isHEAD && !anAnchor->post_data) { + add_environment_value("REQUEST_METHOD=GET"); + } + + cp = NULL; + StrAllocCopy(cp, "QUERY_STRING="); + StrAllocCat(cp, pgm_args); + add_environment_value(cp); + + /* + * Split up arguments into argv array + */ + cp = pgm_args; + cr = cp; + while (1) { + if (*cp == '\0') { + *(cur_argv++) = HTUnEscape(cr); + break; + + } else if (*cp == '+') { + *cp++ = '\0'; + *(cur_argv++) = HTUnEscape(cr); + cr = cp; + } + cp++; + } + } else if (!anAnchor->isHEAD && !anAnchor->post_data) { + add_environment_value("REQUEST_METHOD=GET"); + } + *cur_argv = NULL; /* Terminate argv */ + argv[0] = pgm; + + /* Begin WebSter Mods -jkt */ + if (non_empty(LYCgiDocumentRoot)) { + /* Add DOCUMENT_ROOT to env */ + cp = NULL; + StrAllocCopy(cp, "DOCUMENT_ROOT="); + StrAllocCat(cp, LYCgiDocumentRoot); + add_environment_value(cp); + } + if (path_info != NULL) { + /* Add PATH_INFO to env */ + cp = NULL; + StrAllocCopy(cp, "PATH_INFO="); + StrAllocCat(cp, path_info); + add_environment_value(cp); + } + if (non_empty(LYCgiDocumentRoot) && path_info != NULL) { + /* Construct and add PATH_TRANSLATED to env */ + StrAllocCopy(document_root, LYCgiDocumentRoot); + LYTrimHtmlSep(document_root); + path_translated = document_root; + StrAllocCat(path_translated, path_info); + cp = NULL; + StrAllocCopy(cp, "PATH_TRANSLATED="); + StrAllocCat(cp, path_translated); + add_environment_value(cp); + FREE(path_translated); + } + /* End WebSter Mods -jkt */ + + execve(argv[0], argv, env); + exec_errno = errno; + PERROR("execve failed"); + printf("Content-Type: " STR_PLAINTEXT "\r\n\r\n"); + if (!anAnchor->isHEAD) { + printf("exec of %s failed", pgm); + printf(": %s.\r\n", LYStrerror(exec_errno)); + } + fflush(stdout); + fflush(stderr); + _exit(1); + + } else { /* and the Ugly */ + HTAlert(CONNECT_FAILED); + PERROR("fork() failed"); + close(fd1[0]); + close(fd1[1]); + close(fd2[0]); + close(fd2[1]); + status = -1; + } + + } + if (target != NULL) { + (*target->isa->_free) (target); + } + } + FREE(path_info); + FREE(pgm); + FREE(orig_pgm); +#else /* VMS */ + HTStream *target; + char *buf = 0; + + target = HTStreamStack(WWW_HTML, + format_out, + sink, anAnchor); + + HTSprintf0(&buf, "\n\n%s\n\n\n", + gettext("Good Advice")); + PUTS(buf); + + HTSprintf0(&buf, "

          %s

          \n", gettext("Good Advice")); + PUTS(buf); + + HTSprintf0(&buf, "%s %s
          .\n", gettext("this link")); + PUTS(buf); + + HTSprintf0(&buf, "

          %s\n", + gettext("It provides state of the art CGI script support.\n")); + PUTS(buf); + + HTSprintf0(&buf, "\n\n"); + PUTS(buf); + + (*target->isa->_free) (target); + FREE(buf); + status = HT_LOADED; +#endif /* VMS */ +#else /* LYNXCGI_LINKS */ + HTUserMsg(CGI_NOT_COMPILED); + status = HT_NOT_LOADED; +#endif /* LYNXCGI_LINKS */ + + (void) arg; + (void) anAnchor; + (void) format_out; + (void) sink; + + return (status); +} +#endif /* __MINGW32__ */ + +#ifdef GLOBALDEF_IS_MACRO +#define _LYCGI_C_GLOBALDEF_1_INIT { "lynxcgi", LYLoadCGI, 0 } +GLOBALDEF(HTProtocol, LYLynxCGI, _LYCGI_C_GLOBALDEF_1_INIT); +#else +GLOBALDEF HTProtocol LYLynxCGI = +{"lynxcgi", LYLoadCGI, 0}; +#endif /* GLOBALDEF_IS_MACRO */ diff --git a/src/LYCgi.h b/src/LYCgi.h new file mode 100644 index 0000000..6b90f2d --- /dev/null +++ b/src/LYCgi.h @@ -0,0 +1,16 @@ +#ifndef LYCGI_H +#define LYCGI_H + +#ifndef HTUTILS_H +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + extern void add_lynxcgi_environment(const char *variable_name); + +#ifdef __cplusplus +} +#endif +#endif /* LYGETFILE_H */ diff --git a/src/LYCharSets.c b/src/LYCharSets.c new file mode 100644 index 0000000..f5f1633 --- /dev/null +++ b/src/LYCharSets.c @@ -0,0 +1,1138 @@ +/* + * $LynxId: LYCharSets.c,v 1.74 2024/03/15 16:15:07 tom Exp $ + */ +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +HTkcode kanji_code = NOKANJI; +BOOLEAN LYHaveCJKCharacterSet = FALSE; +BOOLEAN DisplayCharsetMatchLocale = TRUE; +BOOL force_old_UCLYhndl_on_reload = FALSE; +int forced_UCLYhdnl; +int LYNumCharsets = 0; /* Will be initialized later by UC_Register. */ +int current_char_set = -1; /* will be initialized later in LYMain.c */ +int linedrawing_char_set = -1; +STRING2PTR p_entity_values = NULL; /* Pointer, for HTML_put_entity() */ + + /* obsolete and probably not used(???) */ + /* will be initialized in HTMLUseCharacterSet */ +#ifdef USE_CHARSET_CHOICE +charset_subset_t charset_subsets[MAXCHARSETS]; +BOOL custom_display_charset = FALSE; +BOOL custom_assumed_doc_charset = FALSE; + +#ifndef ALL_CHARSETS_IN_O_MENU_SCREEN +int display_charset_map[MAXCHARSETS]; +int assumed_doc_charset_map[MAXCHARSETS]; + +const char *display_charset_choices[MAXCHARSETS + 1]; +const char *assumed_charset_choices[MAXCHARSETS + 1]; +int displayed_display_charset_idx; +#endif +#endif /* USE_CHARSET_CHOICE */ + +/* + * New character sets now declared with UCInit() in UCdomap.c + * + * INSTRUCTIONS for adding new character sets which do not have + * Unicode tables now in UCdomap.h + * + * + * [We hope you need not correct/add old-style mapping below as in ISO_LATIN1[] + * or SevenBitApproximations[] any more - it works now via new chartrans + * mechanism, but kept for compatibility only: we should cleanup the stuff, + * but this is not so easy...] + * + * Currently we only declare some charset's properties here (such as MIME + * names, etc.), it does not include real mapping. + * + * There is a place marked "Add your new character sets HERE" in this file. + * Make up a character set and add it in the same style as the ISO_LATIN1 set + * below, giving it a unique name. + * + * Add the name of the set to LYCharSets. Similarly add the appropriate + * information to the tables below: LYchar_set_names, LYCharSet_UC, + * LYlowest_eightbit. These 4 tables all MUST have the same order. (And this + * is the order you will see in Lynx Options Menu, which is why few + * unicode-based charsets are listed here). + * + */ + +/* Entity values -- for ISO Latin 1 local representation + * + * This MUST match exactly the table referred to in the DTD! + */ +static const char *ISO_Latin1[] = +{ + "\306", /* capital AE diphthong (ligature) (Æ) - AElig */ + "\301", /* capital A, acute accent (Á) - Aacute */ + "\302", /* capital A, circumflex accent (Â) - Acirc */ + "\300", /* capital A, grave accent (À) - Agrave */ + "\305", /* capital A, ring - Aring (Å) */ + "\303", /* capital A, tilde - Atilde (Ã) */ + "\304", /* capital A, dieresis or umlaut mark (Ä) - Auml */ + "\307", /* capital C, cedilla - Ccedil (Ç) */ + "\320", /* capital Eth or D with stroke (Ð) - Dstrok */ + "\320", /* capital Eth, Icelandic (Ð) - ETH */ + "\311", /* capital E, acute accent (É) - Eacute */ + "\312", /* capital E, circumflex accent (Ê) - Ecirc */ + "\310", /* capital E, grave accent (È) - Egrave */ + "\313", /* capital E, dieresis or umlaut mark (Ë) - Euml */ + "\315", /* capital I, acute accent (Í) - Iacute */ + "\316", /* capital I, circumflex accent (Î) - Icirc */ + "\314", /* capital I, grave accent (Ì) - Igrave */ + "\317", /* capital I, dieresis or umlaut mark (Ï) - Iuml */ + "\321", /* capital N, tilde (Ñ) - Ntilde */ + "\323", /* capital O, acute accent (Ó) - Oacute */ + "\324", /* capital O, circumflex accent (Ô) - Ocirc */ + "\322", /* capital O, grave accent (Ò) - Ograve */ + "\330", /* capital O, slash (Ø) - Oslash */ + "\325", /* capital O, tilde (Õ) - Otilde */ + "\326", /* capital O, dieresis or umlaut mark (Ö) - Ouml */ + "\336", /* capital THORN, Icelandic (Þ) - THORN */ + "\332", /* capital U, acute accent (Ú) - Uacute */ + "\333", /* capital U, circumflex accent (Û) - Ucirc */ + "\331", /* capital U, grave accent (Ù) - Ugrave */ + "\334", /* capital U, dieresis or umlaut mark (Ü) - Uuml */ + "\335", /* capital Y, acute accent (Ý) - Yacute */ + "\341", /* small a, acute accent (á) - aacute */ + "\342", /* small a, circumflex accent (â) - acirc */ + "\264", /* spacing acute (´) - acute */ + "\346", /* small ae diphthong (ligature) (æ) - aelig */ + "\340", /* small a, grave accent (à) - agrave */ + "\046", /* ampersand (&) - amp */ + "\345", /* small a, ring (å) - aring */ + "\343", /* small a, tilde (ã) - atilde */ + "\344", /* small a, dieresis or umlaut mark (ä) - auml */ + "\246", /* broken vertical bar (¦) - brkbar */ + "\246", /* broken vertical bar (¦) - brvbar */ + "\347", /* small c, cedilla (ç) - ccedil */ + "\270", /* spacing cedilla (¸) - cedil */ + "\242", /* cent sign (¢) - cent */ + "\251", /* copyright sign (©) - copy */ + "\244", /* currency sign (¤) - curren */ + "\260", /* degree sign (°) - deg */ + "\250", /* spacing dieresis (¨) - die */ + "\367", /* division sign (÷) - divide */ + "\351", /* small e, acute accent (é) - eacute */ + "\352", /* small e, circumflex accent (ê) - ecirc */ + "\350", /* small e, grave accent (è) - egrave */ + "-", /* dash the width of emsp - emdash */ + "\002", /* emsp, em space - not collapsed NEVER CHANGE THIS - emsp */ + "-", /* dash the width of ensp - endash */ + "\002", /* ensp, en space - not collapsed NEVER CHANGE THIS - ensp */ + "\360", /* small eth, Icelandic (ð) - eth */ + "\353", /* small e, dieresis or umlaut mark (ë) - euml */ + "\275", /* fraction 1/2 (½) - frac12 */ + "\274", /* fraction 1/4 (¼) - frac14 */ + "\276", /* fraction 3/4 (¾) - frac34 */ + "\076", /* greater than (>) - gt */ + "\257", /* spacing macron (¯) - hibar */ + "\355", /* small i, acute accent (í) - iacute */ + "\356", /* small i, circumflex accent (î) - icirc */ + "\241", /* inverted exclamation mark (¡) - iexcl */ + "\354", /* small i, grave accent (ì) - igrave */ + "\277", /* inverted question mark (¿) - iquest */ + "\357", /* small i, dieresis or umlaut mark (ï) - iuml */ + "\253", /* angle quotation mark, left («) - laquo */ + "\074", /* less than (<) - lt */ + "\257", /* spacing macron (¯) - macr */ + "-", /* dash the width of emsp - mdash */ + "\265", /* micro sign (µ) - micro */ + "\267", /* middle dot (·) - middot */ + "\001", /* nbsp non-breaking space NEVER CHANGE THIS - nbsp */ + "-", /* dash the width of ensp - ndash */ + "\254", /* negation sign (¬) - not */ + "\361", /* small n, tilde (ñ) - ntilde */ + "\363", /* small o, acute accent (ó) - oacute */ + "\364", /* small o, circumflex accent (ô) - ocirc */ + "\362", /* small o, grave accent (ò) - ograve */ + "\252", /* feminine ordinal indicator (ª) - ordf */ + "\272", /* masculine ordinal indicator (º) - ordm */ + "\370", /* small o, slash (ø) - oslash */ + "\365", /* small o, tilde (õ) - otilde */ + "\366", /* small o, dieresis or umlaut mark (ö) - ouml */ + "\266", /* paragraph sign (¶) - para */ + "\261", /* plus-or-minus sign (±) - plusmn */ + "\243", /* pound sign (£) - pound */ + "\042", /* quote '"' (") - quot */ + "\273", /* angle quotation mark, right (») - raquo */ + "\256", /* circled R registered sign (®) - reg */ + "\247", /* section sign (§) - sect */ + "\007", /* soft hyphen (­) NEVER CHANGE THIS - shy */ + "\271", /* superscript 1 (¹) - sup1 */ + "\262", /* superscript 2 (²) - sup2 */ + "\263", /* superscript 3 (³) - sup3 */ + "\337", /* small sharp s, German (sz ligature) (ß) - szlig */ + "\002", /* thin space - not collapsed NEVER CHANGE THIS - thinsp */ + "\376", /* small thorn, Icelandic (þ) - thorn */ + "\327", /* multiplication sign (×) - times */ + "(TM)", /* circled TM trade mark sign (™) - trade */ + "\372", /* small u, acute accent (ú) - uacute */ + "\373", /* small u, circumflex accent (û) - ucirc */ + "\371", /* small u, grave accent (ù) - ugrave */ + "\250", /* spacing dieresis (¨) - uml */ + "\374", /* small u, dieresis or umlaut mark (ü) - uuml */ + "\375", /* small y, acute accent (ý) - yacute */ + "\245", /* yen sign (¥) - yen */ + "\377", /* small y, dieresis or umlaut mark (ÿ) - yuml */ +}; + +/* Entity values -- 7 bit character approximations + * + * This MUST match exactly the table referred to in the DTD! + */ +const char *SevenBitApproximations[] = +{ + "AE", /* capital AE diphthong (ligature) (Æ) - AElig */ + "A", /* capital A, acute accent (Á) - Aacute */ + "A", /* capital A, circumflex accent (Â) - Acirc */ + "A", /* capital A, grave accent (À) - Agrave */ + "A", /* capital A, ring - Aring (Å) */ + "A", /* capital A, tilde - Atilde (Ã) */ +#ifdef LY_UMLAUT + "Ae", /* capital A, dieresis or umlaut mark (Ä) - Auml */ +#else + "A", /* capital A, dieresis or umlaut mark (Ä) - Auml */ +#endif /* LY_UMLAUT */ + "C", /* capital C, cedilla (Ç) - Ccedil */ + "Dj", /* capital D with stroke (Ð) - Dstrok */ + "DH", /* capital Eth, Icelandic (Ð) - ETH */ + "E", /* capital E, acute accent (É) - Eacute */ + "E", /* capital E, circumflex accent (Ê) - Ecirc */ + "E", /* capital E, grave accent (È) - Egrave */ + "E", /* capital E, dieresis or umlaut mark (Ë) - Euml */ + "I", /* capital I, acute accent (Í) - Iacute */ + "I", /* capital I, circumflex accent (Î) - Icirc */ + "I", /* capital I, grave accent (Ì) - Igrave */ + "I", /* capital I, dieresis or umlaut mark (Ï) - Iuml */ + "N", /* capital N, tilde - Ntilde (Ñ) */ + "O", /* capital O, acute accent (Ó) - Oacute */ + "O", /* capital O, circumflex accent (Ô) - Ocirc */ + "O", /* capital O, grave accent (Ò) - Ograve */ + "O", /* capital O, slash (Ø) - Oslash */ + "O", /* capital O, tilde (Õ) - Otilde */ +#ifdef LY_UMLAUT + "Oe", /* capital O, dieresis or umlaut mark (Ö) - Ouml */ +#else + "O", /* capital O, dieresis or umlaut mark (Ö) - Ouml */ +#endif /* LY_UMLAUT */ + "P", /* capital THORN, Icelandic (Þ) - THORN */ + "U", /* capital U, acute accent (Ú) - Uacute */ + "U", /* capital U, circumflex accent (Û) - Ucirc */ + "U", /* capital U, grave accent (Ù) - Ugrave */ +#ifdef LY_UMLAUT + "Ue", /* capital U, dieresis or umlaut mark (Ü) - Uuml */ +#else + "U", /* capital U, dieresis or umlaut mark (Ü) - Uuml */ +#endif /* LY_UMLAUT */ + "Y", /* capital Y, acute accent (Ý) - Yacute */ + "a", /* small a, acute accent (á) - aacute */ + "a", /* small a, circumflex accent (â) - acirc */ + "'", /* spacing acute (´) - acute */ + "ae", /* small ae diphthong (ligature) (æ) - aelig */ + "`a", /* small a, grave accent (è) - agrave */ + "&", /* ampersand (&) - amp */ + "a", /* small a, ring (å) - aring */ + "a", /* small a, tilde (ã) - atilde */ +#ifdef LY_UMLAUT + "ae", /* small a, dieresis or umlaut mark (ä) - auml */ +#else + "a", /* small a, dieresis or umlaut mark (ä) - auml */ +#endif /* LY_UMLAUT */ + "|", /* broken vertical bar (¦) - brkbar */ + "|", /* broken vertical bar (¦) - brvbar */ + "c", /* small c, cedilla (ç) - ccedil */ + ",", /* spacing cedilla (¸) - cedil */ + "-c-", /* cent sign (¢) - cent */ + "(c)", /* copyright sign (©) - copy */ + "CUR", /* currency sign (¤) - curren */ + "DEG", /* degree sign (°) - deg */ + "\042", /* spacing dieresis (¨) - die */ + "/", /* division sign (÷) - divide */ + "e", /* small e, acute accent (é) - eacute */ + "e", /* small e, circumflex accent (ê) - ecirc */ + "e", /* small e, grave accent (è) - egrave */ + "-", /* dash the width of emsp - emdash */ + "\002", /* emsp NEVER CHANGE THIS - emsp */ + "-", /* dash the width of ensp - endash */ + "\002", /* ensp NEVER CHANGE THIS - ensp */ + "dh", /* small eth, Icelandic eth (ð) */ + "e", /* small e, dieresis or umlaut mark (ë) - euml */ + " 1/2", /* fraction 1/2 (½) - frac12 */ + " 1/4", /* fraction 1/4 (¼) - frac14 */ + " 3/4", /* fraction 3/4 (¾) - frac34 */ + ">", /* greater than (>) - gt */ + "-", /* spacing macron (¯) - hibar */ + "i", /* small i, acute accent (í) - iacute */ + "i", /* small i, circumflex accent (î) - icirc */ + "!", /* inverted exclamation mark (¡) - iexcl */ + "`i", /* small i, grave accent (ì) - igrave */ + "?", /* inverted question mark (¿) - iquest */ + "i", /* small i, dieresis or umlaut mark (ï) - iuml */ + "<<", /* angle quotation mark, left («) - laquo */ + "<", /* less than - lt (<) */ + "-", /* spacing macron (¯) - macr */ + "-", /* dash the width of emsp - mdash */ + "u", /* micro sign (µ) - micro */ + ".", /* middle dot (·) - middot */ + "\001", /* nbsp non-breaking space NEVER CHANGE THIS - nbsp */ + "-", /* dash the width of ensp - ndash */ + "NOT", /* negation sign (¬) - not */ + "n", /* small n, tilde (ñ) - ntilde */ + "o", /* small o, acute accent (ó) - oacute */ + "o", /* small o, circumflex accent (ô) - ocirc */ + "o", /* small o, grave accent (ò) - ograve */ + "-a", /* feminine ordinal indicator (ª) - ordf */ + "-o", /* masculine ordinal indicator (º) - ordm */ + "o", /* small o, slash (ø) - oslash */ + "o", /* small o, tilde (õ) - otilde */ +#ifdef LY_UMLAUT + "oe", /* small o, dieresis or umlaut mark (ö) - ouml */ +#else + "o", /* small o, dieresis or umlaut mark (ö) - ouml */ +#endif /* LY_UMLAUT */ + "P:", /* paragraph sign (¶) - para */ + "+-", /* plus-or-minus sign (±) - plusmn */ + "-L-", /* pound sign (£) - pound */ + "\"", /* quote '"' (") - quot */ + ">>", /* angle quotation mark, right (») - raquo */ + "(R)", /* circled R registered sign (®) - reg */ + "S:", /* section sign (§) - sect */ + "\007", /* soft hyphen (­) NEVER CHANGE THIS - shy */ + "^1", /* superscript 1 (¹) - sup1 */ + "^2", /* superscript 2 (²) - sup2 */ + "^3", /* superscript 3 (³) - sup3 */ + "ss", /* small sharp s, German (sz ligature) (ß) - szlig */ + "\002", /* thin space - not collapsed NEVER CHANGE THIS - thinsp */ + "p", /* small thorn, Icelandic (þ) - thorn */ + "*", /* multiplication sign (×) - times */ + "(TM)", /* circled TM trade mark sign (™) - trade */ + "u", /* small u, acute accent (ú) - uacute */ + "u", /* small u, circumflex accent (û) - ucirc */ + "u", /* small u, grave accent (ù) - ugrave */ + "\042", /* spacing dieresis (¨) - uml */ +#ifdef LY_UMLAUT + "ue", /* small u, dieresis or umlaut mark (ü) - uuml */ +#else + "u", /* small u, dieresis or umlaut mark (ü) - uuml */ +#endif /* LY_UMLAUT */ + "y", /* small y, acute accent (ý) - yacute */ + "YEN", /* yen sign (¥) - yen */ + "y", /* small y, dieresis or umlaut mark (ÿ) - yuml */ +}; + +/* + * Add your new character sets HERE (but only if you can't construct Unicode + * tables for them). - FM + */ + +/* + * Add the array name to LYCharSets + */ +STRING2PTR LYCharSets[MAXCHARSETS] = +{ + ISO_Latin1, /* ISO Latin 1 */ + SevenBitApproximations, /* 7 Bit Approximations */ +}; + +/* + * Add the name that the user will see below. The order of LYCharSets and + * LYchar_set_names MUST be the same + */ +const char *LYchar_set_names[MAXCHARSETS + 1] = +{ + "Western (ISO-8859-1)", + "7 bit approximations (US-ASCII)", + (char *) 0 +}; + +/* + * Associate additional pieces of info with each of the charsets listed above. + * Will be automatically modified (and extended) by charset translations which + * are loaded using the chartrans mechanism. Most important piece of info to + * put here is a MIME charset name. Used for chartrans (see UCDefs.h). The + * order of LYCharSets and LYCharSet_UC MUST be the same. + * + * Note that most of the charsets added by the new mechanism in src/chrtrans + * don't show up here at all. They don't have to. + */ +LYUCcharset LYCharSet_UC[MAXCHARSETS] = +{ + /* + * Zero position placeholder and HTMLGetEntityUCValue() reference. - FM + */ + {-1, "iso-8859-1", UCT_ENC_8BIT, 0, + UCT_REP_IS_LAT1, + UCT_CP_IS_LAT1, UCT_R_LAT1, UCT_R_LAT1}, + + /* + * Placeholders for Unicode tables. - FM + */ + {-1, "us-ascii", UCT_ENC_7BIT, 0, + UCT_REP_SUBSETOF_LAT1, + UCT_CP_SUBSETOF_LAT1, UCT_R_ASCII, UCT_R_ASCII}, + +}; + +/* + * Add the code of the the lowest character with the high bit set that can be + * directly displayed. The order of LYCharSets and LYlowest_eightbit MUST be + * the same. + * + * (If charset have chartrans unicode table, LYlowest_eightbit will be + * verified/modified anyway.) + */ +int LYlowest_eightbit[MAXCHARSETS] = +{ + 160, /* ISO Latin 1 */ + 999, /* 7 bit approximations */ +}; + +/* + * Function to set the handling of selected character sets based on the current + * LYUseDefaultRawMode value. - FM + */ +void HTMLSetCharacterHandling(int i) +{ + int chndl = safeUCGetLYhndl_byMIME(UCAssume_MIMEcharset); + BOOLEAN LYRawMode_flag = LYRawMode; + int UCLYhndl_for_unspec_flag = UCLYhndl_for_unspec; + + if (LYCharSet_UC[i].enc != UCT_ENC_CJK) { + HTCJK = NOCJK; + kanji_code = NOKANJI; + if (i == chndl) + LYRawMode = LYUseDefaultRawMode; + else + LYRawMode = (BOOL) (!LYUseDefaultRawMode); + + HTPassEightBitNum = (BOOL) ((LYCharSet_UC[i].codepoints & UCT_CP_SUPERSETOF_LAT1) + || (LYCharSet_UC[i].like8859 & UCT_R_HIGH8BIT)); + + if (LYRawMode) { + HTPassEightBitRaw = (BOOL) (LYlowest_eightbit[i] <= 160); + } else { + HTPassEightBitRaw = FALSE; + } + if (LYRawMode || i == chndl) { + HTPassHighCtrlRaw = (BOOL) (LYlowest_eightbit[i] <= 130); + } else { + HTPassHighCtrlRaw = FALSE; + } + + HTPassHighCtrlNum = FALSE; + + } else { /* CJK encoding: */ + const char *mime = LYCharSet_UC[i].MIMEname; + + if (!strcmp(mime, "euc-cn")) { + HTCJK = CHINESE; + kanji_code = EUC; + } else if (!strcmp(mime, "euc-jp")) { + HTCJK = JAPANESE; + kanji_code = EUC; + } else if (!strcmp(mime, "shift_jis")) { + HTCJK = JAPANESE; + kanji_code = SJIS; + } else if (!strcmp(mime, "euc-kr")) { + HTCJK = KOREAN; + kanji_code = EUC; + } else if (!strcmp(mime, "big5")) { + HTCJK = TAIPEI; + kanji_code = EUC; + } + + /* for any CJK: */ + if (!LYUseDefaultRawMode) + HTCJK = NOCJK; + LYRawMode = (BOOL) (IS_CJK_TTY ? TRUE : FALSE); + HTPassEightBitRaw = FALSE; + HTPassEightBitNum = FALSE; + HTPassHighCtrlRaw = (BOOL) (IS_CJK_TTY ? TRUE : FALSE); + HTPassHighCtrlNum = FALSE; + } + + /* + * Comment for coding below: + * UCLYhndl_for_unspec is "current" state with LYRawMode, but + * UCAssume_MIMEcharset is independent from LYRawMode: holds the history + * and may be changed from 'O'ptions menu only. - LP + */ + if (LYRawMode) { + UCLYhndl_for_unspec = i; /* UCAssume_MIMEcharset not changed! */ + } else { + if (chndl != i && + (LYCharSet_UC[i].enc != UCT_ENC_CJK || + LYCharSet_UC[chndl].enc != UCT_ENC_CJK)) { + UCLYhndl_for_unspec = chndl; /* fall to UCAssume_MIMEcharset */ + } else { + UCLYhndl_for_unspec = LATIN1; /* UCAssume_MIMEcharset not changed! */ + } + } + +#ifdef USE_SLANG + if (LYlowest_eightbit[i] > 191) { + /* + * Higher than this may output cntrl chars to screen. - KW + */ + SLsmg_Display_Eight_Bit = 191; + } else { + SLsmg_Display_Eight_Bit = LYlowest_eightbit[i]; + } +#endif /* USE_SLANG */ + + ena_csi(LYlowest_eightbit[current_char_set] > 155); + + /* some diagnostics */ + if (TRACE) { + if (LYRawMode_flag != LYRawMode) + CTRACE((tfp, + "HTMLSetCharacterHandling: LYRawMode changed %s -> %s\n", + (LYRawMode_flag ? "ON" : "OFF"), + (LYRawMode ? "ON" : "OFF"))); + if (UCLYhndl_for_unspec_flag != UCLYhndl_for_unspec) + CTRACE((tfp, + "HTMLSetCharacterHandling: UCLYhndl_for_unspec changed %d -> %d\n", + UCLYhndl_for_unspec_flag, + UCLYhndl_for_unspec)); + } + + return; +} + +/* + * Function to set HTCJK based on "in" and "out" charsets. + */ +void Set_HTCJK(const char *inMIMEname, + const char *outMIMEname) +{ + /* need not check for synonyms: MIMEname's got from LYCharSet_UC */ + + if (LYRawMode) { + if ((!strcmp(inMIMEname, "euc-jp") || +#ifdef USE_JAPANESEUTF8_SUPPORT + !strcmp(inMIMEname, "utf-8") || +#endif + !strcmp(inMIMEname, "shift_jis")) && + (!strcmp(outMIMEname, "euc-jp") || + !strcmp(outMIMEname, "shift_jis"))) { + HTCJK = JAPANESE; + } else if (!strcmp(inMIMEname, "euc-cn") && + !strcmp(outMIMEname, "euc-cn")) { + HTCJK = CHINESE; + } else if (!strcmp(inMIMEname, "big5") && + !strcmp(outMIMEname, "big5")) { + HTCJK = TAIPEI; + } else if (!strcmp(inMIMEname, "euc-kr") && + !strcmp(outMIMEname, "euc-kr")) { + HTCJK = KOREAN; + } else { + HTCJK = NOCJK; + } + } else { + HTCJK = NOCJK; + } +} + +/* + * Function to set the LYDefaultRawMode value based on the selected character + * set. - FM + * + * Currently unused: the default value so obvious that LYUseDefaultRawMode + * utilized directly by someone's mistake. - LP + */ +static void HTMLSetRawModeDefault(int i) +{ + LYDefaultRawMode = (BOOL) (LYCharSet_UC[i].enc == UCT_ENC_CJK); + return; +} + +/* + * Function to set the LYUseDefaultRawMode value based on the selected + * character set and the current LYRawMode value. - FM + */ +void HTMLSetUseDefaultRawMode(int i, + int modeflag) +{ + if (LYCharSet_UC[i].enc != UCT_ENC_CJK) { + + int chndl = safeUCGetLYhndl_byMIME(UCAssume_MIMEcharset); + + if (i == chndl) + LYUseDefaultRawMode = (BOOLEAN) modeflag; + else + LYUseDefaultRawMode = (BOOL) (!modeflag); + } else /* CJK encoding: */ + LYUseDefaultRawMode = (BOOLEAN) modeflag; + + return; +} + +/* + * Function to set the LYHaveCJKCharacterSet value based on the selected + * character set. - FM + */ +static void HTMLSetHaveCJKCharacterSet(int i) +{ + LYHaveCJKCharacterSet = (BOOL) (LYCharSet_UC[i].enc == UCT_ENC_CJK); + return; +} + +/* + * Function to set the DisplayCharsetMatchLocale value based on the selected + * character set. It is used in UPPER8 for 8bit case-insensitive search by + * matching def7_uni.tbl images. - LP + */ +static void HTMLSetDisplayCharsetMatchLocale(int i) +{ + BOOLEAN match; + + if (LYHaveCJKCharacterSet) { + /* + * We have no intention to pass CJK via UCTransChar if that happened. + * Let someone from CJK correct this if necessary. + */ + match = TRUE; /* old-style */ + } else if (!strncasecomp(LYCharSet_UC[i].MIMEname, "cp", 2) || + !strncasecomp(LYCharSet_UC[i].MIMEname, "windows", 7)) { + match = FALSE; /* Windows locale is never seen on UNIX */ + } else { +#if defined(LOCALE) + match = !UCForce8bitTOUPPER; /* disable locale (from lynx.cfg) */ +#else + match = (LYCharSet_UC[i].enc == UCT_ENC_UTF8); +#endif + } + + DisplayCharsetMatchLocale = match; + return; +} + +/* + * lynx 2.8/2.7.2(and more early) compatibility code: "human-readable" charset + * names changes with time so we map that history names to MIME here to get old + * lynx.cfg and (especially) .lynxrc always recognized. Please update this + * table when you change "fullname" of any present charset. + */ +typedef struct _names_pairs { + const char *fullname; + const char *MIMEname; +} names_pairs; +/* *INDENT-OFF* */ +static const names_pairs OLD_charset_names[] = +{ + {"ISO Latin 1", "iso-8859-1"}, + {"ISO Latin 2", "iso-8859-2"}, + {"WinLatin1 (cp1252)", "windows-1252"}, + {"DEC Multinational", "dec-mcs"}, + {"Macintosh (8 bit)", "macintosh"}, + {"NeXT character set", "next"}, + {"KOI8-R Cyrillic", "koi8-r"}, + {"Chinese", "euc-cn"}, + {"Japanese (EUC)", "euc-jp"}, + {"Japanese (SJIS)", "shift_jis"}, + {"Korean", "euc-kr"}, + {"Taipei (Big5)", "big5"}, + {"Vietnamese (VISCII)", "viscii"}, + {"7 bit approximations", "us-ascii"}, + {"Transparent", "x-transparent"}, + {"DosLatinUS (cp437)", "cp437"}, + {"IBM PC character set", "cp437"}, + {"DosLatin1 (cp850)", "cp850"}, + {"IBM PC codepage 850", "cp850"}, + {"DosLatin2 (cp852)", "cp852"}, + {"PC Latin2 CP 852", "cp852"}, + {"DosCyrillic (cp866)", "cp866"}, + {"DosArabic (cp864)", "cp864"}, + {"DosGreek (cp737)", "cp737"}, + {"DosBaltRim (cp775)", "cp775"}, + {"DosGreek2 (cp869)", "cp869"}, + {"DosHebrew (cp862)", "cp862"}, + {"WinLatin2 (cp1250)", "windows-1250"}, + {"WinCyrillic (cp1251)", "windows-1251"}, + {"WinGreek (cp1253)", "windows-1253"}, + {"WinHebrew (cp1255)", "windows-1255"}, + {"WinArabic (cp1256)", "windows-1256"}, + {"WinBaltRim (cp1257)", "windows-1257"}, + {"ISO Latin 3", "iso-8859-3"}, + {"ISO Latin 4", "iso-8859-4"}, + {"ISO 8859-5 Cyrillic", "iso-8859-5"}, + {"ISO 8859-6 Arabic", "iso-8859-6"}, + {"ISO 8859-7 Greek", "iso-8859-7"}, + {"ISO 8859-8 Hebrew", "iso-8859-8"}, + {"ISO-8859-8-I", "iso-8859-8"}, + {"ISO-8859-8-E", "iso-8859-8"}, + {"ISO 8859-9 (Latin 5)", "iso-8859-9"}, + {"ISO 8859-10", "iso-8859-10"}, + {"UNICODE UTF 8", "utf-8"}, + {"RFC 1345 w/o Intro", "mnemonic+ascii+0"}, + {"RFC 1345 Mnemonic", "mnemonic"}, + {NULL, NULL}, /* terminated with NULL */ +}; +/* *INDENT-ON* */ + +/* + * lynx 2.8/2.7.2 compatibility code: read "character_set" parameter from + * lynx.cfg and .lynxrc in both MIME name and "human-readable" name (old and + * new style). Returns -1 if not recognized. + */ +int UCGetLYhndl_byAnyName(char *value) +{ + int i; + + if (value == NULL) + return -1; + + LYTrimTrailing(value); + CTRACE((tfp, "UCGetLYhndl_byAnyName(%s)\n", value)); + + /* search by name */ + for (i = 0; (i < MAXCHARSETS && LYchar_set_names[i]); i++) { + if (!strcmp(value, LYchar_set_names[i])) { + return i; /* OK */ + } + } + + /* search by old name from 2.8/2.7.2 version */ + for (i = 0; (OLD_charset_names[i].fullname); i++) { + if (!strcmp(value, OLD_charset_names[i].fullname)) { + return UCGetLYhndl_byMIME(OLD_charset_names[i].MIMEname); /* OK */ + } + } + + return UCGetLYhndl_byMIME(value); /* by MIME */ +} + +/* + * Entity names -- Ordered by ISO Latin 1 value. + * --------------------------------------------- + * For conversions of DECIMAL escaped entities. + * Must be in order of ascending value. + */ +static const char *LYEntityNames[] = +{ +/* NAME DECIMAL VALUE */ + "nbsp", /* 160, non breaking space */ + "iexcl", /* 161, inverted exclamation mark */ + "cent", /* 162, cent sign */ + "pound", /* 163, pound sign */ + "curren", /* 164, currency sign */ + "yen", /* 165, yen sign */ + "brvbar", /* 166, broken vertical bar, (brkbar) */ + "sect", /* 167, section sign */ + "uml", /* 168, spacing dieresis */ + "copy", /* 169, copyright sign */ + "ordf", /* 170, feminine ordinal indicator */ + "laquo", /* 171, angle quotation mark, left */ + "not", /* 172, negation sign */ + "shy", /* 173, soft hyphen */ + "reg", /* 174, circled R registered sign */ + "hibar", /* 175, spacing macron */ + "deg", /* 176, degree sign */ + "plusmn", /* 177, plus-or-minus sign */ + "sup2", /* 178, superscript 2 */ + "sup3", /* 179, superscript 3 */ + "acute", /* 180, spacing acute (96) */ + "micro", /* 181, micro sign */ + "para", /* 182, paragraph sign */ + "middot", /* 183, middle dot */ + "cedil", /* 184, spacing cedilla */ + "sup1", /* 185, superscript 1 */ + "ordm", /* 186, masculine ordinal indicator */ + "raquo", /* 187, angle quotation mark, right */ + "frac14", /* 188, fraction 1/4 */ + "frac12", /* 189, fraction 1/2 */ + "frac34", /* 190, fraction 3/4 */ + "iquest", /* 191, inverted question mark */ + "Agrave", /* 192, capital A, grave accent */ + "Aacute", /* 193, capital A, acute accent */ + "Acirc", /* 194, capital A, circumflex accent */ + "Atilde", /* 195, capital A, tilde */ + "Auml", /* 196, capital A, dieresis or umlaut mark */ + "Aring", /* 197, capital A, ring */ + "AElig", /* 198, capital AE diphthong (ligature) */ + "Ccedil", /* 199, capital C, cedilla */ + "Egrave", /* 200, capital E, grave accent */ + "Eacute", /* 201, capital E, acute accent */ + "Ecirc", /* 202, capital E, circumflex accent */ + "Euml", /* 203, capital E, dieresis or umlaut mark */ + "Igrave", /* 204, capital I, grave accent */ + "Iacute", /* 205, capital I, acute accent */ + "Icirc", /* 206, capital I, circumflex accent */ + "Iuml", /* 207, capital I, dieresis or umlaut mark */ + "ETH", /* 208, capital Eth, Icelandic (or Latin2 Dstrok) */ + "Ntilde", /* 209, capital N, tilde */ + "Ograve", /* 210, capital O, grave accent */ + "Oacute", /* 211, capital O, acute accent */ + "Ocirc", /* 212, capital O, circumflex accent */ + "Otilde", /* 213, capital O, tilde */ + "Ouml", /* 214, capital O, dieresis or umlaut mark */ + "times", /* 215, multiplication sign */ + "Oslash", /* 216, capital O, slash */ + "Ugrave", /* 217, capital U, grave accent */ + "Uacute", /* 218, capital U, acute accent */ + "Ucirc", /* 219, capital U, circumflex accent */ + "Uuml", /* 220, capital U, dieresis or umlaut mark */ + "Yacute", /* 221, capital Y, acute accent */ + "THORN", /* 222, capital THORN, Icelandic */ + "szlig", /* 223, small sharp s, German (sz ligature) */ + "agrave", /* 224, small a, grave accent */ + "aacute", /* 225, small a, acute accent */ + "acirc", /* 226, small a, circumflex accent */ + "atilde", /* 227, small a, tilde */ + "auml", /* 228, small a, dieresis or umlaut mark */ + "aring", /* 229, small a, ring */ + "aelig", /* 230, small ae diphthong (ligature) */ + "ccedil", /* 231, small c, cedilla */ + "egrave", /* 232, small e, grave accent */ + "eacute", /* 233, small e, acute accent */ + "ecirc", /* 234, small e, circumflex accent */ + "euml", /* 235, small e, dieresis or umlaut mark */ + "igrave", /* 236, small i, grave accent */ + "iacute", /* 237, small i, acute accent */ + "icirc", /* 238, small i, circumflex accent */ + "iuml", /* 239, small i, dieresis or umlaut mark */ + "eth", /* 240, small eth, Icelandic */ + "ntilde", /* 241, small n, tilde */ + "ograve", /* 242, small o, grave accent */ + "oacute", /* 243, small o, acute accent */ + "ocirc", /* 244, small o, circumflex accent */ + "otilde", /* 245, small o, tilde */ + "ouml", /* 246, small o, dieresis or umlaut mark */ + "divide", /* 247, division sign */ + "oslash", /* 248, small o, slash */ + "ugrave", /* 249, small u, grave accent */ + "uacute", /* 250, small u, acute accent */ + "ucirc", /* 251, small u, circumflex accent */ + "uuml", /* 252, small u, dieresis or umlaut mark */ + "yacute", /* 253, small y, acute accent */ + "thorn", /* 254, small thorn, Icelandic */ + "yuml", /* 255, small y, dieresis or umlaut mark */ +}; + +/* + * Function to return the entity names of ISO-8859-1 8-bit characters. - FM + */ +const char *HTMLGetEntityName(UCode_t code) +{ +#define IntValue code + int MaxValue = (TABLESIZE(LYEntityNames) - 1); + + if (IntValue < 0 || IntValue > MaxValue) { + return ""; + } + + return LYEntityNames[IntValue]; +} + +/* + * Function to return the UCode_t (long int) value for entity names. It + * returns 0 if not found. + * + * unicode_entities[] handles all the names from old style entities[] too. + * Lynx now calls unicode_entities[] only through this function: + * HTMLGetEntityUCValue(). Note, we need not check for special characters here + * in function or even before it, we should check them *after* invoking this + * function, see put_special_unicodes() in SGML.c. + * + * In the future we will try to isolate all calls to entities[] in favor of new + * unicode-based chartrans scheme. - LP + */ +UCode_t HTMLGetEntityUCValue(const char *name) +{ +#include + + UCode_t value = 0; + size_t i, high, low; + int diff = 0; + size_t number_of_unicode_entities = TABLESIZE(unicode_entities); + + /* + * Make sure we have a non-zero length name. - FM + */ + if (isEmpty(name)) + return (value); + + /* + * Try UC_entity_info unicode_entities[]. + */ + for (low = 0, high = number_of_unicode_entities; + high > low; + diff < 0 ? (low = i + 1) : (high = i)) { + /* + * Binary search. + */ + i = (low + (high - low) / 2); + diff = AS_cmp(unicode_entities[i].name, name); /* Case sensitive! */ + if (diff == 0) { + value = unicode_entities[i].code; + break; + } + } + return (value); +} + +/* + * Original comment - + * Assume these are Microsoft code points, inflicted on us by FrontPage. - FM + * + * MS FrontPage uses syntax like ™ in 128-159 range and doesn't follow + * Unicode standards for this area. Windows-1252 codepoints are assumed here. + * + * However see - + * http://www.whatwg.org/specs/web-apps/current-work/multipage/infrastructure.html#character-encodings-0 + */ +UCode_t LYcp1252ToUnicode(UCode_t code) +{ + if ((code == 1) || + (code > 127 && code < 160)) { + switch (code) { + case 1: + /* + * WHITE SMILING FACE + */ + code = 0x263a; + break; + case 128: + /* + * EURO currency sign + */ + code = 0x20ac; + break; + case 130: + /* + * SINGLE LOW-9 QUOTATION MARK (sbquo) + */ + code = 0x201a; + break; + case 131: + /* + * LATIN SMALL LETTER F WITH HOOK + */ + code = 0x192; + break; + case 132: + /* + * DOUBLE LOW-9 QUOTATION MARK (bdquo) + */ + code = 0x201e; + break; + case 133: + /* + * HORIZONTAL ELLIPSIS (hellip) + */ + code = 0x2026; + break; + case 134: + /* + * DAGGER (dagger) + */ + code = 0x2020; + break; + case 135: + /* + * DOUBLE DAGGER (Dagger) + */ + code = 0x2021; + break; + case 136: + /* + * MODIFIER LETTER CIRCUMFLEX ACCENT + */ + code = 0x2c6; + break; + case 137: + /* + * PER MILLE SIGN (permil) + */ + code = 0x2030; + break; + case 138: + /* + * LATIN CAPITAL LETTER S WITH CARON + */ + code = 0x160; + break; + case 139: + /* + * SINGLE LEFT-POINTING ANGLE QUOTATION MARK (lsaquo) + */ + code = 0x2039; + break; + case 140: + /* + * LATIN CAPITAL LIGATURE OE + */ + code = 0x152; + break; + case 142: + /* + * LATIN CAPITAL LETTER Z WITH CARON + */ + code = 0x17d; + break; + case 145: + /* + * LEFT SINGLE QUOTATION MARK (lsquo) + */ + code = 0x2018; + break; + case 146: + /* + * RIGHT SINGLE QUOTATION MARK (rsquo) + */ + code = 0x2019; + break; + case 147: + /* + * LEFT DOUBLE QUOTATION MARK (ldquo) + */ + code = 0x201c; + break; + case 148: + /* + * RIGHT DOUBLE QUOTATION MARK (rdquo) + */ + code = 0x201d; + break; + case 149: + /* + * BULLET (bull) + */ + code = 0x2022; + break; + case 150: + /* + * EN DASH (ndash) + */ + code = 0x2013; + break; + case 151: + /* + * EM DASH (mdash) + */ + code = 0x2014; + break; + case 152: + /* + * SMALL TILDE (tilde) + */ + code = 0x02dc; + break; + case 153: + /* + * TRADE MARK SIGN (trade) + */ + code = 0x2122; + break; + case 154: + /* + * LATIN SMALL LETTER S WITH CARON + */ + code = 0x161; + break; + case 155: + /* + * SINGLE RIGHT-POINTING ANGLE QUOTATION MARK (rsaquo) + */ + code = 0x203a; + break; + case 156: + /* + * LATIN SMALL LIGATURE OE + */ + code = 0x153; + break; + case 158: + /* + * LATIN SMALL LETTER Z WITH CARON + */ + code = 0x17e; + break; + case 159: + /* + * LATIN CAPITAL LETTER Y WITH DIAERESIS + */ + code = 0x178; + break; + default: + /* + * Undefined (by convention, use the replacement character). + */ + code = UCS_REPL; + break; + } + } + return code; +} + +/* + * Function to select a character set and then set the character handling and + * LYHaveCJKCharacterSet flag. - FM + */ +void HTMLUseCharacterSet(int i) +{ + HTMLSetRawModeDefault(i); + p_entity_values = LYCharSets[i]; + HTMLSetCharacterHandling(i); /* set LYRawMode and CJK attributes */ + HTMLSetHaveCJKCharacterSet(i); + HTMLSetDisplayCharsetMatchLocale(i); + return; +} + +/* + * Initializer, calls initialization function for the CHARTRANS handling. - KW + */ +int LYCharSetsDeclared(void) +{ + UCInit(); + + return UCInitialized; +} + +#ifdef USE_CHARSET_CHOICE +void init_charset_subsets(void) +{ + int i, n; + int cur_display = 0; + int cur_assumed = 0; + + /* add them to displayed values */ + charset_subsets[UCLYhndl_for_unspec].hide_assumed = FALSE; + charset_subsets[current_char_set].hide_display = FALSE; + +#ifndef ALL_CHARSETS_IN_O_MENU_SCREEN + /*all this stuff is for supporting old menu screen... */ + for (i = 0; i < LYNumCharsets; ++i) { + if (charset_subsets[i].hide_display == FALSE) { + n = cur_display++; + if (i == current_char_set) + displayed_display_charset_idx = n; + display_charset_map[n] = i; + display_charset_choices[n] = LYchar_set_names[i]; + } + if (charset_subsets[i].hide_assumed == FALSE) { + n = cur_assumed++; + assumed_doc_charset_map[n] = i; + assumed_charset_choices[n] = LYCharSet_UC[i].MIMEname; + charset_subsets[i].assumed_idx = n; + } + display_charset_choices[cur_display] = NULL; + assumed_charset_choices[cur_assumed] = NULL; + } +#endif +} +#endif /* USE_CHARSET_CHOICE */ diff --git a/src/LYCharSets.h b/src/LYCharSets.h new file mode 100644 index 0000000..c0d1553 --- /dev/null +++ b/src/LYCharSets.h @@ -0,0 +1,154 @@ +/* + * $LynxId: LYCharSets.h,v 1.34 2012/02/10 18:43:40 tom Exp $ + */ +#ifndef LYCHARSETS_H +#define LYCHARSETS_H + +#ifndef HTUTILS_H +#include +#endif + +#include + +#ifndef UCMAP_H +#include +#endif /* !UCMAP_H */ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + extern BOOL HTPassEightBitRaw; + extern BOOL HTPassEightBitNum; + extern BOOL HTPassHighCtrlRaw; + extern BOOL HTPassHighCtrlNum; + extern BOOLEAN LYHaveCJKCharacterSet; + extern BOOLEAN DisplayCharsetMatchLocale; + + extern HTkcode kanji_code; + +/* + * currently active character set (internal handler) + */ + extern int current_char_set; +/* + * character set where it is safe to draw lines on boxes. + */ + extern int linedrawing_char_set; + +/* + * Initializer, calls initialization function for the + * CHARTRANS handling. - KW + */ + extern int LYCharSetsDeclared(void); + + extern STRING2PTR LYCharSets[]; + extern const char *SevenBitApproximations[]; + extern STRING2PTR p_entity_values; + extern const char *LYchar_set_names[]; /* Full name, not MIME */ + extern int LYlowest_eightbit[]; + extern int LYNumCharsets; + extern LYUCcharset LYCharSet_UC[]; + extern int UCGetLYhndl_byAnyName(char *value); + extern void HTMLSetCharacterHandling(int i); + extern void HTMLSetUseDefaultRawMode(int i, int modeflag); + extern void HTMLUseCharacterSet(int i); + extern UCode_t HTMLGetEntityUCValue(const char *name); + extern void Set_HTCJK(const char *inMIMEname, const char *outMIMEname); + + extern const char *HTMLGetEntityName(UCode_t code); + + UCode_t LYcp1252ToUnicode(UCode_t code); + +/* + * HTMLGetEntityName calls LYEntityNames for iso-8859-1 entity names only. + * This is an obsolete technique but widely used in the code. Note that + * unicode number in general may have several equivalent entity names because + * of synonyms. + */ + extern BOOL force_old_UCLYhndl_on_reload; + extern int forced_UCLYhdnl; + +#ifndef USE_CHARSET_CHOICE +# define ALL_CHARSETS_IN_O_MENU_SCREEN 1 +#endif + +#ifdef USE_CHARSET_CHOICE + typedef struct { + BOOL hide_display; /* if FALSE, show in "display-charset" menu */ + BOOL hide_assumed; /* if FALSE, show in "assumed-charset" menu */ +#ifndef ALL_CHARSETS_IN_O_MENU_SCREEN + int assumed_idx; /* only this field is needed */ +#endif + } charset_subset_t; + +/* each element corresponds to charset in LYCharSets */ + extern charset_subset_t charset_subsets[]; + +/* all zeros by default - i.e., all charsets allowed */ + +/* + * true if the charset choices for display charset were requested by user via + * lynx.cfg. It will remain FALSE if no "display_charset_choice" settings were + * encountered in lynx.cfg + */ + extern BOOL custom_display_charset; + +/* similar to custom_display_charset */ + extern BOOL custom_assumed_doc_charset; + +#ifndef ALL_CHARSETS_IN_O_MENU_SCREEN + +/* this stuff is initialized after reading lynx.cfg and .lynxrc */ + +/* + * These arrays map index of charset shown in menu to the index in LYCharsets[] + */ + extern int display_charset_map[]; + extern int assumed_doc_charset_map[]; + +/* these arrays are NULL terminated */ + extern const char *display_charset_choices[]; + extern const char *assumed_charset_choices[]; + + extern int displayed_display_charset_idx; + +#endif +/* this will be called after lynx.cfg and .lynxrc are read */ + extern void init_charset_subsets(void); +#endif /* USE_CHARSET_CHOICE */ + +#if !defined(NO_AUTODETECT_DISPLAY_CHARSET) +# ifdef __EMX__ +# define CAN_AUTODETECT_DISPLAY_CHARSET +# ifdef EXP_CHARTRANS_AUTOSWITCH +# define CAN_SWITCH_DISPLAY_CHARSET +# endif +# endif +#endif + +#ifdef CAN_AUTODETECT_DISPLAY_CHARSET + extern int auto_display_charset; +#endif + +#ifdef CAN_SWITCH_DISPLAY_CHARSET + enum switch_display_charset_t { + SWITCH_DISPLAY_CHARSET_MAYBE, + SWITCH_DISPLAY_CHARSET_REALLY, + SWITCH_DISPLAY_CHARSET_RESIZE + }; + extern int Switch_Display_Charset(int ord, enum switch_display_charset_t really); + extern int Find_Best_Display_Charset(int ord); + extern char *charsets_directory; + extern char *charset_switch_rules; + extern int switch_display_charsets; + extern int auto_other_display_charset; + extern int codepages[2]; + extern int real_charsets[2]; /* Non "auto-" charsets for the codepages */ +#endif + +#ifdef __cplusplus +} +#endif +#endif /* LYCHARSETS_H */ diff --git a/src/LYCharUtils.c b/src/LYCharUtils.c new file mode 100644 index 0000000..0013989 --- /dev/null +++ b/src/LYCharUtils.c @@ -0,0 +1,3419 @@ +/* + * $LynxId: LYCharUtils.c,v 1.137 2021/10/24 00:47:08 tom Exp $ + * + * Functions associated with LYCharSets.c and the Lynx version of HTML.c - FM + * ========================================================================== + */ +#include +#include + +#define Lynx_HTML_Handler +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* + * Used for nested lists. - FM + */ +int OL_CONTINUE = -29999; /* flag for whether CONTINUE is set */ +int OL_VOID = -29998; /* flag for whether a count is set */ + +static size_t count_char(const char *value, int ch) +{ + const char *found; + size_t result = 0; + + while ((*value != '\0') && (found = StrChr(value, ch)) != NULL) { + ++result; + value = (found + 1); + } + return result; +} + +/* + * This function converts any ampersands in a pre-allocated string to "&". + * If brackets is TRUE, it also converts any angle-brackets to "<" or ">". + */ +void LYEntify(char **in_out, + int brackets) +{ + char *source = *in_out; + char *target; + char *result = NULL; + size_t count_AMPs = 0; + size_t count_LTs = 0; + size_t count_GTs = 0; + +#ifdef CJK_EX + enum _state { + S_text, + S_esc, + S_dollar, + S_paren, + S_nonascii_text, + S_dollar_paren + } state = S_text; + int in_sjis = 0; +#endif + + if (non_empty(source)) { + count_AMPs = count_char(*in_out, '&'); + if (brackets) { + count_LTs = count_char(*in_out, '<'); + count_GTs = count_char(*in_out, '>'); + } + + if (count_AMPs != 0 || count_LTs != 0 || count_GTs != 0) { + + target = typecallocn(char, + (strlen(*in_out) + + (4 * count_AMPs) + + (3 * count_LTs) + + (3 * count_GTs) + 1)); + + if ((result = target) == NULL) + outofmem(__FILE__, "LYEntify"); + + for (source = *in_out; *source; source++) { +#ifdef CJK_EX + if (IS_CJK_TTY) { + switch (state) { + case S_text: + if (*source == '\033') { + state = S_esc; + *target++ = *source; + continue; + } + break; + + case S_esc: + if (*source == '$') { + state = S_dollar; + } else if (*source == '(') { + state = S_paren; + } else { + state = S_text; + } + *target++ = *source; + continue; + + case S_dollar: + if (*source == '@' || *source == 'B' || *source == 'A') { + state = S_nonascii_text; + } else if (*source == '(') { + state = S_dollar_paren; + } else { + state = S_text; + } + *target++ = *source; + continue; + + case S_dollar_paren: + if (*source == 'C') { + state = S_nonascii_text; + } else { + state = S_text; + } + *target++ = *source; + continue; + + case S_paren: + if (*source == 'B' || *source == 'J' || *source == 'T') { + state = S_text; + } else if (*source == 'I') { + state = S_nonascii_text; + } else if (*source == '\033') { + state = S_esc; + } + *target++ = *source; + continue; + + case S_nonascii_text: + if (*source == '\033') + state = S_esc; + *target++ = *source; + continue; + + default: + break; + } + if (*(source + 1) != '\0' && + (IS_EUC(UCH(*source), UCH(*(source + 1))) || + IS_SJIS(UCH(*source), UCH(*(source + 1)), in_sjis) || + IS_BIG5(UCH(*source), UCH(*(source + 1))))) { + *target++ = *source++; + *target++ = *source; + continue; + } + } +#endif + switch (*source) { + case '&': + *target++ = '&'; + *target++ = 'a'; + *target++ = 'm'; + *target++ = 'p'; + *target++ = ';'; + break; + case '<': + if (brackets) { + *target++ = '&'; + *target++ = 'l'; + *target++ = 't'; + *target++ = ';'; + break; + } + /* FALLTHRU */ + case '>': + if (brackets) { + *target++ = '&'; + *target++ = 'g'; + *target++ = 't'; + *target++ = ';'; + break; + } + /* FALLTHRU */ + default: + *target++ = *source; + break; + } + } + *target = '\0'; + FREE(*in_out); + *in_out = result; + } + } +} + +/* + * Callers to LYEntifyTitle/LYEntifyValue do not look at the 'target' param. + * Optimize things a little by avoiding the memory allocation if not needed, + * as is usually the case. + */ +static BOOL MustEntify(const char *source) +{ + BOOL result; + +#ifdef CJK_EX + if (IS_CJK_TTY && StrChr(source, '\033') != 0) { + result = TRUE; + } else +#endif + { + size_t length = strlen(source); + size_t reject = strcspn(source, "<&>"); + + result = (BOOL) (length != reject); + } + + return result; +} + +/* + * Wrappers for LYEntify() which do not assume that the source was allocated, + * e.g., output from gettext(). + */ +const char *LYEntifyTitle(char **target, const char *source) +{ + const char *result = 0; + + if (MustEntify(source)) { + StrAllocCopy(*target, source); + LYEntify(target, TRUE); + result = *target; + } else { + result = source; + } + return result; +} + +const char *LYEntifyValue(char **target, const char *source) +{ + const char *result = 0; + + if (MustEntify(source)) { + StrAllocCopy(*target, source); + LYEntify(target, FALSE); + result = *target; + } else { + result = source; + } + return result; +} + +/* + * This function trims characters <= that of a space (32), + * including HT_NON_BREAK_SPACE (1) and HT_EN_SPACE (2), + * but not ESC, from the heads of strings. - FM + */ +void LYTrimHead(char *str) +{ + const char *s = str; + + if (isEmpty(s)) + return; + + while (*s && WHITE(*s) && UCH(*s) != UCH(CH_ESC)) /* S/390 -- gil -- 1669 */ + s++; + if (s > str) { + char *ns = str; + + while (*s) { + *ns++ = *s++; + } + *ns = '\0'; + } +} + +/* + * This function trims characters <= that of a space (32), + * including HT_NON_BREAK_SPACE (1), HT_EN_SPACE (2), and + * ESC from the tails of strings. - FM + */ +void LYTrimTail(char *str) +{ + int i; + + if (isEmpty(str)) + return; + + i = (int) strlen(str) - 1; + while (i >= 0) { + if (WHITE(str[i])) + str[i] = '\0'; + else + break; + i--; + } +} + +/* + * This function should receive a pointer to the start + * of a comment. It returns a pointer to the end ('>') + * character of comment, or it's best guess if the comment + * is invalid. - FM + */ +char *LYFindEndOfComment(char *str) +{ + char *cp, *cp1; + enum comment_state { + start1, + start2, + end1, + end2 + } state; + + if (str == NULL) + /* + * We got NULL, so return NULL. - FM + */ + return NULL; + + if (StrNCmp(str, "' as terminator for comments" + ), + PARSE_FUN( + "homepage", 4|NEED_FUNCTION_ARG, homepage_fun, + "=URL\nset homepage separate from start page" + ), + PARSE_SET( + "html5_charsets", 4|TOGGLE_ARG, html5_charsets, + "toggles use of HTML5 charset replacements" + ), + PARSE_SET( + "image_links", 4|TOGGLE_ARG, clickable_images, + "toggles inclusion of links for all images" + ), + PARSE_STR( + "index", 4|NEED_LYSTRING_ARG, indexfile, + "=URL\nset the default index file to URL" + ), + PARSE_SET( + "ismap", 4|TOGGLE_ARG, LYNoISMAPifUSEMAP, + "toggles inclusion of ISMAP links when client-side\nMAPs are present" + ), +#ifdef USE_JUSTIFY_ELTS + PARSE_SET( + "justify", 4|SET_ARG, ok_justify, + "do justification of text" + ), +#endif + PARSE_INT( + "link", 4|NEED_INT_ARG, crawl_count, + "=NUMBER\nstarting count for lnk#.dat files produced by -crawl" + ), + PARSE_SET( + "list_decoded", 4|TOGGLE_ARG, dump_links_decoded, + "with -dump, forces it to decode URL-encoded links" + ), + PARSE_SET( + "list_inline", 4|TOGGLE_ARG, dump_links_inline, + "with -dump, forces it to show links inline with text" + ), + PARSE_SET( + "listonly", 4|TOGGLE_ARG, dump_links_only, + "with -dump, forces it to show only the list of links" + ), + PARSE_SET( + "localhost", 4|SET_ARG, local_host_only, + "disable URLs that point to remote hosts" + ), +#if defined(EXEC_LINKS) || defined(EXEC_SCRIPTS) + PARSE_SET( + "locexec", 4|SET_ARG, local_exec_on_local_files, + "enable local program execution from local files only" + ), +#endif /* EXEC_LINKS || EXEC_SCRIPTS */ +#if defined(USE_COLOR_STYLE) + PARSE_STR( + "lss", 2|NEED_LYSTRING_ARG, lynx_lss_file2, + "=FILENAME\nspecifies a lynx.lss file other than the default" + ), +#endif + PARSE_FUN( + "mime_header", 4|FUNCTION_ARG, mime_header_fun, + "include mime headers and force source dump" + ), + PARSE_SET( + "minimal", 4|TOGGLE_ARG, minimal_comments, + "toggles minimal versus valid comment parsing" + ), +#ifdef EXP_NESTED_TABLES + PARSE_SET( + "nested_tables", 4|TOGGLE_ARG, nested_tables, + "toggles nested-tables logic" + ), +#endif +#ifndef DISABLE_NEWS + PARSE_FUN( + "newschunksize", 4|NEED_FUNCTION_ARG, newschunksize_fun, + "=NUMBER\nnumber of articles in chunked news listings" + ), + PARSE_FUN( + "newsmaxchunk", 4|NEED_FUNCTION_ARG, newsmaxchunk_fun, + "=NUMBER\nmaximum news articles in listings before chunking" + ), +#endif +#if USE_BLAT_MAILER + PARSE_SET( + "noblat", 4|TOGGLE_ARG, mail_is_blat, + "select mail tool (`"THIS_BLAT_MAIL"' ==> `"SYSTEM_MAIL"')" + ), +#endif + PARSE_FUN( + "nobold", 4|FUNCTION_ARG, nobold_fun, + "disable bold video-attribute" + ), + PARSE_FUN( + "nobrowse", 4|FUNCTION_ARG, nobrowse_fun, + "disable directory browsing" + ), + PARSE_SET( + "nocc", 4|SET_ARG, LYNoCc, + "disable Cc: prompts for self copies of mailings" + ), + PARSE_FUN( + "nocolor", 4|FUNCTION_ARG, nocolor_fun, + "turn off color support" + ), +#if defined(EXEC_LINKS) || defined(EXEC_SCRIPTS) + PARSE_SET( + "noexec", 4|UNSET_ARG, local_exec, + "disable local program execution (DEFAULT)" + ), +#endif /* EXEC_LINKS || EXEC_SCRIPTS */ + PARSE_SET( + "nofilereferer", 4|SET_ARG, no_filereferer, + "disable transmission of Referer headers for file URLs" + ), + PARSE_SET( + "nolist", 4|SET_ARG, no_list, + "disable the link list feature in dumps" + ), + PARSE_SET( + "nolog", 4|UNSET_ARG, error_logging, + "disable mailing of error messages to document owners" + ), + PARSE_SET( + "nomargins", 4|SET_ARG, no_margins, + "disable the right/left margins in the default\nstyle-sheet" + ), + PARSE_FUN( + "nomore", 4|FUNCTION_ARG, nomore_fun, + "disable -more- string in statusline messages" + ), +#if defined(HAVE_SIGACTION) && defined(SIGWINCH) + PARSE_SET( + "nonrestarting_sigwinch", 4|SET_ARG, LYNonRestartingSIGWINCH, + "\nmake window size change handler non-restarting" + ), +#endif /* HAVE_SIGACTION */ + PARSE_SET( + "nonumbers", 4|SET_ARG, no_numbers, + "disable the link/form numbering feature in dumps" + ), + PARSE_FUN( + "nopause", 4|FUNCTION_ARG, nopause_fun, + "disable forced pauses for statusline messages" + ), + PARSE_SET( + "noprint", 4|SET_ARG, no_print, + "disable some print functions, like -restrictions=print" + ), + PARSE_SET( + "noredir", 4|SET_ARG, no_url_redirection, + "don't follow Location: redirection" + ), + PARSE_SET( + "noreferer", 4|SET_ARG, LYNoRefererHeader, + "disable transmission of Referer headers" + ), + PARSE_FUN( + "noreverse", 4|FUNCTION_ARG, noreverse_fun, + "disable reverse video-attribute" + ), +#ifdef SOCKS + PARSE_SET( + "nosocks", 2|UNSET_ARG, socks_flag, + "don't use SOCKS proxy for this session" + ), +#endif + PARSE_SET( + "nostatus", 4|SET_ARG, no_statusline, + "disable the miscellaneous information messages" + ), + PARSE_SET( + "notitle", 4|SET_ARG, no_title, + "disable the title at the top of each page" + ), + PARSE_FUN( + "nounderline", 4|FUNCTION_ARG, nounderline_fun, + "disable underline video-attribute" + ), + PARSE_FUN( + "nozap", 4|FUNCTION_ARG, nozap_fun, + "=DURATION (\"initially\" or \"full\") disable checks for 'z' key" + ), + PARSE_SET( + "number_fields", 4|SET_ARG, number_fields, + "force numbering of links as well as form input fields" + ), + PARSE_SET( + "number_links", 4|SET_ARG, number_links, + "force numbering of links" + ), +#ifdef DISP_PARTIAL + PARSE_SET( + "partial", 4|TOGGLE_ARG, display_partial_flag, + "toggles display partial pages while downloading" + ), + PARSE_INT( + "partial_thres", 4|NEED_INT_ARG, partial_threshold, + "[=NUMBER]\nnumber of lines to render before repainting display\n\ +with partial-display logic" + ), +#endif +#ifndef DISABLE_FTP + PARSE_SET( + "passive_ftp", 4|TOGGLE_ARG, ftp_passive, + "toggles passive ftp connection" + ), +#endif + PARSE_FUN( + "pauth", 4|NEED_FUNCTION_ARG, pauth_fun, + "=id:pw\nauthentication information for protected proxy server" + ), + PARSE_SET( + "popup", 4|UNSET_ARG, LYUseDefSelPop, + "toggles handling of single-choice SELECT options via\n\ +popup windows or as lists of radio buttons" + ), + PARSE_FUN( + "post_data", 2|FUNCTION_ARG, post_data_fun, + "user data for post forms, read from stdin,\n\ +terminated by '---' on a line" + ), + PARSE_SET( + "preparsed", 4|SET_ARG, LYPreparsedSource, + "show parsed " STR_HTML " with -source and in source view\n\ +to visualize how lynx behaves with invalid HTML" + ), +#ifdef USE_PRETTYSRC + PARSE_SET( + "prettysrc", 4|SET_ARG, LYpsrc, + "do syntax highlighting and hyperlink handling in source\nview" + ), +#endif + PARSE_SET( + "print", 4|UNSET_ARG, no_print, + "enable print functions (DEFAULT), opposite of -noprint" + ), + PARSE_SET( + "pseudo_inlines", 4|TOGGLE_ARG, pseudo_inline_alts, + "toggles pseudo-ALTs for inlines with no ALT string" + ), + PARSE_SET( + "raw", 4|UNSET_ARG, LYUseDefaultRawMode, + "toggles default setting of 8-bit character translations\n\ +or CJK mode for the startup character set" + ), + PARSE_SET( + "realm", 4|SET_ARG, check_realm, + "restricts access to URLs in the starting realm" + ), + PARSE_INT( + "read_timeout", 4|NEED_INT_ARG, reading_timeout, + "=N\nset the N-second read-timeout" + ), + PARSE_SET( + "reload", 4|SET_ARG, reloading, + "flushes the cache on a proxy server\n(only the first document affected)" + ), + PARSE_FUN( + "restrictions", 4|FUNCTION_ARG, restrictions_fun, + "=[options]\nuse -restrictions to see list" + ), + PARSE_SET( + "resubmit_posts", 4|TOGGLE_ARG, LYresubmit_posts, + "toggles forced resubmissions (no-cache) of forms with\n\ +method POST when the documents they returned are sought\n\ +with the PREV_DOC command or from the History List" + ), + PARSE_SET( + "rlogin", 4|UNSET_ARG, rlogin_ok, + "disable rlogins" + ), +#if defined(PDCURSES) && defined(PDC_BUILD) && PDC_BUILD >= 2401 + PARSE_FUN( + "scrsize", 4|NEED_FUNCTION_ARG, scrsize_fun, + "=width,height\nsize of window" + ), +#endif +#ifdef USE_SCROLLBAR + PARSE_SET( + "scrollbar", 4|TOGGLE_ARG, LYShowScrollbar, + "toggles showing scrollbar" + ), + PARSE_SET( + "scrollbar_arrow", 4|TOGGLE_ARG, LYsb_arrow, + "toggles showing arrows at ends of the scrollbar" + ), +#endif + PARSE_FUN( + "selective", 4|FUNCTION_ARG, selective_fun, + "require .www_browsable files to browse directories" + ), +#ifdef USE_SESSIONS + PARSE_STR( + "session", 2|NEED_LYSTRING_ARG, session_file, + "=FILENAME\nresumes from specified file on startup and\n\ +saves session to that file on exit" + ), + PARSE_STR( + "sessionin", 2|NEED_LYSTRING_ARG, sessionin_file, + "=FILENAME\nresumes session from specified file" + ), + PARSE_STR( + "sessionout", 2|NEED_LYSTRING_ARG, sessionout_file, + "=FILENAME\nsaves session to specified file" + ), +#endif /* USE_SESSIONS */ + PARSE_SET( + "short_url", 4|SET_ARG, long_url_ok, + "enables examination of beginning and end of long URL in\nstatus line" + ), + PARSE_SET( + "show_cfg", 1|SET_ARG, show_cfg, + "Show `LYNX.CFG' setting" + ), + PARSE_SET( + "show_cursor", 4|TOGGLE_ARG, LYUseDefShoCur, + "toggles hiding of the cursor in the lower right corner" + ), +#ifdef USE_READPROGRESS + PARSE_SET( + "show_rate", 4|TOGGLE_ARG, LYShowTransferRate, + "toggles display of transfer rate" + ), +#endif + PARSE_STR( + "socks5_proxy", 2|NEED_LYSTRING_ARG, socks5_proxy, + "=URL\n(via which) SOCKS5 proxy to connect (unrelated to -nosocks!)" + ), + PARSE_SET( + "soft_dquotes", 4|TOGGLE_ARG, soft_dquotes, + "toggles emulation of the old Netscape and Mosaic\n\ +bug which treated '>' as a co-terminator for\ndouble-quotes and tags" + ), + PARSE_FUN( + "source", 4|FUNCTION_ARG, source_fun, + "dump the source of the first file to stdout and exit" + ), + PARSE_SET( + "stack_dump", 4|SET_ARG, stack_dump, + "disable SIGINT cleanup handler" + ), + PARSE_SET( + "startfile_ok", 4|SET_ARG, startfile_ok, + "allow non-http startfile and homepage with -validate" + ), + PARSE_SET( + "stderr", 4|SET_ARG, dump_to_stderr, + "write warning messages to standard error when -dump\nor -source is used" + ), + PARSE_SET( + "stdin", 4|SET_ARG, startfile_stdin, + "read startfile from standard input" + ), +#ifdef SYSLOG_REQUESTED_URLS + PARSE_STR( + "syslog", 4|NEED_LYSTRING_ARG, syslog_txt, + "=text\ninformation for syslog call" + ), + PARSE_SET( + "syslog_urls", 4|SET_ARG, syslog_requested_urls, + "log requested URLs with syslog" + ), +#endif + PARSE_SET( + "tagsoup", 4|SET_ARG, DTD_recovery, + "use TagSoup rather than SortaSGML parser" + ), + PARSE_SET( + "telnet", 4|UNSET_ARG, telnet_ok, + "disable telnets" + ), + PARSE_STR( + "term", 4|NEED_STRING_ARG, terminal, + "=TERM\nset terminal type to TERM" + ), +#ifdef _WINDOWS + PARSE_INT( + "timeout", 4|INT_ARG, lynx_timeout, + "=NUMBER\nset TCP/IP timeout" + ), +#endif + PARSE_SET( + "tlog", 2|TOGGLE_ARG, LYUseTraceLog, + "toggles use of a Lynx Trace Log for the current\nsession" + ), +#ifdef TEXTFIELDS_MAY_NEED_ACTIVATION + PARSE_SET( + "tna", 4|SET_ARG, textfields_activation_option, + "turn on \"Textfields Need Activation\" mode" + ), +#endif +#ifndef NO_LYNX_TRACE + PARSE_SET( + "trace", 1|SET_ARG, WWW_TraceFlag, + "turns on Lynx trace mode" + ), + PARSE_INT( + "trace_mask", 1|INT_ARG, WWW_TraceMask, + "customize Lynx trace mode" + ), +#endif + PARSE_FUN( + "traversal", 4|FUNCTION_ARG, traversal_fun, + "traverse all http links derived from startfile" + ), + PARSE_SET( + "trim_blank_lines", 2|TOGGLE_ARG, LYtrimBlankLines, + "\ntoggle trimming of leading/trailing/collapsed-br blank lines" + ), + PARSE_SET( + "trim_input_fields", 2|SET_ARG, LYtrimInputFields, + "\ntrim input text/textarea fields in forms" + ), + PARSE_SET( + "underline_links",4|TOGGLE_ARG, LYUnderlineLinks, + "toggles use of underline/bold attribute for links" + ), + PARSE_SET( + "underscore", 4|TOGGLE_ARG, use_underscore, + "toggles use of _underline_ format in dumps" + ), + PARSE_SET( + "unique_urls", 4|TOGGLE_ARG, unique_urls, + "toggles use of unique-urls setting for -dump and -listonly options" + ), + PARSE_SET( + "update_term_title", 4|SET_ARG, update_term_title, + "enables updating the title of terminal emulators" + ), +#if defined(USE_MOUSE) + PARSE_SET( + "use_mouse", 4|SET_ARG, LYUseMouse, + "turn on mouse support" + ), +#endif + PARSE_STR( + "useragent", 4|NEED_LYSTRING_ARG, LYUserAgent, + "=Name\nset alternate Lynx User-Agent header" + ), + PARSE_SET( + "validate", 2|SET_ARG, LYValidate, + "accept only http URLs (meant for validation)\n\ +implies more restrictions than -anonymous, but\n\ +goto is allowed for http and https" + ), + PARSE_SET( + "verbose", 4|TOGGLE_ARG, verbose_img, + "toggles [LINK], [IMAGE] and [INLINE] comments\n\ +with filenames of these images" + ), + PARSE_FUN( + "version", 1|FUNCTION_ARG, version_fun, + "print Lynx version information" + ), + PARSE_SET( + "vikeys", 4|SET_ARG, vi_keys, + "enable vi-like key movement" + ), +#ifdef __DJGPP__ + PARSE_SET( + "wdebug", 4|TOGGLE_ARG, watt_debug, + "enables Waterloo tcp/ip packet debug. Prints to watt\ndebugfile" + ), +#endif /* __DJGPP__ */ + PARSE_FUN( + "width", 4|NEED_FUNCTION_ARG, width_fun, + "=NUMBER\nscreen width for formatting of dumps (default is 80)" + ), +#ifndef NO_DUMP_WITH_BACKSPACES + PARSE_SET( + "with_backspaces", 4|SET_ARG, with_backspaces, + "emit backspaces in output if -dumping or -crawling\n(like 'man' does)" + ), +#endif + PARSE_SET( + "xhtml_parsing", 4|SET_ARG, LYxhtml_parsing, + "enable XHTML 1.0 parsing" + ), + PARSE_NIL +}; +/* *INDENT-ON* */ + +static void print_help_strings(const char *name, + const char *help, + const char *value, + int option) +{ + int pad; + int c; + int first; + int field_width = 20; + + pad = field_width - (2 + option + (int) strlen(name)); + + fprintf(stdout, " %s%s", option ? "-" : "", name); + + if (*help != '=') { + pad--; + while (pad > 0) { + fputc(' ', stdout); + pad--; + } + fputc(' ', stdout); /* at least one space */ + first = 0; + } else { + first = pad; + } + + if (StrChr(help, '\n') == 0) { + fprintf(stdout, "%s", help); + } else { + while ((c = *help) != 0) { + if (c == '\n') { + if ((pad = --first) < 0) { + pad = field_width; + } else { + c = ' '; + } + fputc(c, stdout); + while (pad--) + fputc(' ', stdout); + } else { + fputc(c, stdout); + } + help++; + first--; + } + } + if (value) + printf(" (%s)", value); + fputc('\n', stdout); +} + +static void print_help_and_exit(int exit_status) +{ + Config_Type *p; + + if (pgm == NULL) + pgm = "lynx"; + + SetOutputMode(O_TEXT); + + fprintf(stdout, gettext("USAGE: %s [options] [file]\n"), pgm); + fprintf(stdout, gettext("Options are:\n")); +#ifdef VMS + print_help_strings("", + "receive the arguments from stdin (enclose\n\ +in double-quotes (\"-\") on VMS)", NULL, TRUE); +#else + print_help_strings("", "receive options and arguments from stdin", NULL, TRUE); +#endif /* VMS */ + + for (p = Arg_Table; p->name != 0; p++) { + char temp[LINESIZE], *value = temp; + ParseUnionPtr q = ParseUnionOf(p); + + switch (p->type & ARG_TYPE_MASK) { + case TOGGLE_ARG: + case SET_ARG: + strcpy(temp, *(q->set_value) ? "on" : "off"); + break; + case UNSET_ARG: + strcpy(temp, *(q->set_value) ? "off" : "on"); + break; + case INT_ARG: + sprintf(temp, "%d", *(q->int_value)); + break; + case TIME_ARG: + sprintf(temp, SECS_FMT, (double) Secs2SECS(*(q->int_value))); + break; + case STRING_ARG: + if ((value = *(q->str_value)) != 0 + && !*value) + value = 0; + break; + default: + value = 0; + break; + } + print_help_strings(p->name, p->help_string, value, TRUE); + } + + SetOutputMode(O_BINARY); + + exit_immediately(exit_status); +} + +/* + * This function performs a string comparison on two strings a and b. a is + * assumed to be an ordinary null terminated string, but b may be terminated + * by an '=', '+' or '-' character. If terminated by '=', *c will be pointed + * to the character following the '='. If terminated by '+' or '-', *c will + * be pointed to that character. (+/- added for toggle processing - BL.) + * If a and b match, it returns 1. Otherwise 0 is returned. + */ +static int arg_eqs_parse(const char *a, + char *b, + char **c) +{ + int result = -1; + + *c = NULL; + while (result < 0) { + if ((*a != *b) + || (*a == 0) + || (*b == 0)) { + if (*a == 0) { + switch (*b) { + case '\t': /* embedded blank when reading stdin */ + case ' ': + *c = LYSkipBlanks(b); + result = 1; + break; + case '=': + case ':': + *c = b + 1; + result = 1; + break; + case '-': +#if OPTNAME_ALLOW_DASHES + if (isalpha(UCH(b[1]))) { + result = 0; + break; + } +#endif + /* FALLTHRU */ + case '+': + *c = b; + result = 1; + break; + case 0: + result = 1; + break; + default: + result = 0; + break; + } + } else { +#if OPTNAME_ALLOW_DASHES + if (!(*a == '_' && *b == '-')) +#endif + result = 0; + } + } + a++; + b++; + } + return result; +} + +#define is_true(s) (*s == '1' || *s == '+' || !strcasecomp(s, "on") || !strcasecomp(s, "true")) +#define is_false(s) (*s == '0' || *s == '-' || !strcasecomp(s, "off") || !strcasecomp(s, "false")) + +/* + * Parse an option. + * argv[] points to the beginning of the unprocessed options. + * mask is used to select certain options which must be processed + * before others. + * countp (if nonnull) points to an index into argv[], which is updated + * to reflect option values which are also parsed. + */ +static BOOL parse_arg(char **argv, + unsigned mask, + int *countp) +{ + Config_Type *p; + char *arg_name; + +#if EXTENDED_STARTFILE_RECALL + static BOOLEAN no_options_further = FALSE; /* set to TRUE after '--' argument */ + static int nof_index = 0; /* set the index of -- argument */ +#endif + + arg_name = argv[0]; + CTRACE((tfp, "parse_arg(arg_name=%s, mask=%u, count=%d)\n", + arg_name, mask, countp ? *countp : -1)); + +#if EXTENDED_STARTFILE_RECALL + if (mask == (unsigned) ((countp != 0) ? 0 : 1)) { + no_options_further = FALSE; + /* want to reset nonoption when beginning scan for --stdin */ + if (nonoption != 0) { + FREE(nonoption); + } + } +#endif + + /* + * Check for a command line startfile. - FM + */ + if (*arg_name != '-' +#if EXTENDED_OPTION_LOGIC + || ((no_options_further == TRUE) + && (countp != 0) + && (nof_index < (*countp))) +#endif + ) { +#if EXTENDED_STARTFILE_RECALL + /* + * On the last pass (mask==4), check for cases where we may want to + * provide G)oto history for multiple startfiles. + */ + if (mask == 4) { + if (nonoption != 0) { + LYEnsureAbsoluteURL(&nonoption, "NONOPTION", FALSE); + HTAddGotoURL(nonoption); + FREE(nonoption); + } + StrAllocCopy(nonoption, arg_name); + } +#endif + StrAllocCopy(startfile, arg_name); + LYEscapeStartfile(&startfile); +#ifdef _WINDOWS /* 1998/01/14 (Wed) 20:11:17 */ + HTUnEscape(startfile); + { + char *q = startfile; + + while (*q++) { + if (*q == '|') + *q = ':'; + } + } +#endif + CTRACE((tfp, "parse_arg startfile:%s\n", startfile)); + return (BOOL) (countp != 0); + } +#if EXTENDED_OPTION_LOGIC + if (strcmp(arg_name, "--") == 0) { + no_options_further = TRUE; + nof_index = countp ? *countp : -1; + return TRUE; + } +#endif + + /* lose the first '-' character */ + arg_name++; + + /* + * Skip any lone "-" arguments, because we've loaded the stdin input into + * an HTList structure for special handling. - FM + */ + if (*arg_name == 0) + return TRUE; + + /* allow GNU-style options with -- prefix */ + if (*arg_name == '-') + ++arg_name; + + CTRACE((tfp, "parse_arg lookup(%s)\n", arg_name)); + + p = Arg_Table; + while (p->name != 0) { + ParseUnionPtr q = ParseUnionOf(p); + ParseFunc fun; + char *next_arg = NULL; + char *temp_ptr = NULL; + + if ((p->name[0] != *arg_name) + || (0 == arg_eqs_parse(p->name, arg_name, &next_arg))) { + p++; + continue; + } + + if (p->type & NEED_NEXT_ARG) { + if (next_arg == 0) { + next_arg = argv[1]; + if ((countp != 0) && (next_arg != 0)) + (*countp)++; + } + CTRACE((tfp, "...arg:%s\n", NONNULL(next_arg))); + } + + /* ignore option if it's not our turn */ + if (((unsigned) (p->type) & mask) == 0) { + CTRACE((tfp, "...skip (mask %u/%d)\n", mask, p->type & 7)); + return FALSE; + } + + switch (p->type & ARG_TYPE_MASK) { + case TOGGLE_ARG: /* FALLTHRU */ + case SET_ARG: /* FALLTHRU */ + case UNSET_ARG: + if (q->set_value != 0) { + if (next_arg == 0) { + switch (p->type & ARG_TYPE_MASK) { + case TOGGLE_ARG: + *(q->set_value) = (BOOL) !(*(q->set_value)); + break; + case SET_ARG: + *(q->set_value) = TRUE; + break; + case UNSET_ARG: + *(q->set_value) = FALSE; + break; + } + } else if (is_true(next_arg)) { + *(q->set_value) = TRUE; + } else if (is_false(next_arg)) { + *(q->set_value) = FALSE; + } + /* deliberately ignore anything else - BL */ + } + break; + + case FUNCTION_ARG: + fun = q->fun_value; + if (0 != fun) { + if (-1 == (*fun) (next_arg)) { + } + } + break; + + case LYSTRING_ARG: + if ((q->str_value != 0) && (next_arg != 0)) + StrAllocCopy(*(q->str_value), next_arg); + break; + + case INT_ARG: + if ((q->int_value != 0) && (next_arg != 0)) + *(q->int_value) = (int) strtol(next_arg, &temp_ptr, 0); + break; + + case TIME_ARG: + if ((q->int_value != 0) && (next_arg != 0)) { + float ival; + + if (1 == LYscanFloat(next_arg, &ival)) { + *(q->int_value) = (int) SECS2Secs(ival); + } + } + break; + + case STRING_ARG: + if ((q->str_value != 0) && (next_arg != 0)) + *(q->str_value) = next_arg; + break; + } + + Old_DTD = DTD_recovery; /* BOOL != int */ + return TRUE; + } + + if (pgm == 0) + pgm = "LYNX"; + + fprintf(stderr, gettext("%s: Invalid Option: %s\n"), pgm, argv[0]); + print_help_and_exit(-1); + return FALSE; +} + +#ifndef VMS +static void FatalProblem(int sig) +{ + /* + * Ignore further interrupts. - mhc: 11/2/91 + */ +#ifndef NOSIGHUP + (void) signal(SIGHUP, SIG_IGN); +#endif /* NOSIGHUP */ + (void) signal(SIGTERM, SIG_IGN); + (void) signal(SIGINT, SIG_IGN); +#ifndef __linux__ +#ifdef SIGBUS + (void) signal(SIGBUS, SIG_IGN); +#endif /* ! SIGBUS */ +#endif /* !__linux__ */ + (void) signal(SIGSEGV, SIG_IGN); + (void) signal(SIGILL, SIG_IGN); + + /* + * Flush all messages. - FM + */ + fflush(stderr); + fflush(stdout); + + /* + * Deal with curses, if on, and clean up. - FM + */ + if (LYOutOfMemory && LYCursesON) { + LYSleepAlert(); + } + cleanup_sig(0); +#ifndef __linux__ +#ifdef SIGBUS + signal(SIGBUS, SIG_DFL); +#endif /* SIGBUS */ +#endif /* !__linux__ */ + signal(SIGSEGV, SIG_DFL); + signal(SIGILL, SIG_DFL); + + /* + * Issue appropriate messages and abort or exit. - FM + */ + if (LYOutOfMemory == FALSE) { + fprintf(stderr, "\r\n\ +A Fatal error has occurred in %s Ver. %s\r\n", LYNX_NAME, LYNX_VERSION); + + fprintf(stderr, "\r\n\ +Please notify your system administrator to confirm a bug, and\r\n\ +if confirmed, to notify the lynx-dev list. Bug reports should\r\n\ +have concise descriptions of the command and/or URL which causes\r\n\ +the problem, the operating system name with version number, the\r\n\ +TCPIP implementation, and any other relevant information.\r\n"); + + if (!(sig == 0 && LYNoCore)) { + fprintf(stderr, "\r\n\ +Do NOT mail the core file if one was generated.\r\n"); + } + if (sig != 0) { + fprintf(stderr, "\r\n\ +Lynx now exiting with signal: %d\r\n\r\n", sig); +#ifdef WIN_EX /* 1998/08/09 (Sun) 09:58:25 */ + { + char *msg; + + switch (sig) { + case SIGABRT: + msg = "SIGABRT"; + break; + case SIGFPE: + msg = "SIGFPE"; + break; + case SIGILL: + msg = "SIGILL"; + break; + case SIGSEGV: + msg = "SIGSEGV"; + break; + default: + msg = "Not-def"; + break; + } + fprintf(stderr, "signal code = %s\n", msg); + } +#endif + } + + /* + * Exit and possibly dump core. + */ + if (LYNoCore) { + exit_immediately(EXIT_FAILURE); + } + abort(); + + } else { + LYOutOfMemory = FALSE; + printf("\r\n%s\r\n\r\n", MEMORY_EXHAUSTED_ABORT); + fflush(stdout); + + /* + * Exit without dumping core. + */ + exit_immediately(EXIT_FAILURE); + } +} +#endif /* !VMS */ diff --git a/src/LYMainLoop.c b/src/LYMainLoop.c new file mode 100644 index 0000000..b5c7ab8 --- /dev/null +++ b/src/LYMainLoop.c @@ -0,0 +1,8210 @@ +/* + * $LynxId: LYMainLoop.c,v 1.256 2024/04/11 21:53:34 tom Exp $ + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef USE_SESSIONS +#include +#endif + +#ifdef KANJI_CODE_OVERRIDE +#include +#endif + +#ifdef PREVENT_KEYBOARD_WRAPAROUND +#define HandleForwardWraparound() \ + *old_c = real_c; \ + HTInfoMsg(ALREADY_AT_END) +#define HandleReverseWraparound() \ + *old_c = real_c; \ + HTInfoMsg(ALREADY_AT_BEGIN) +#else +#define HandleForwardWraparound() \ + LYSetNewline(1) +#define HandleReverseWraparound() \ + int i; \ + i = HText_getNumOfLines() - display_lines + 2; \ + if (i >= 1 && Newline != i) { \ + LYSetNewline(i); \ + *arrowup = TRUE; \ + } +#endif + +#define LinkIsTextarea(linkNumber) \ + (links[linkNumber].type == WWW_FORM_LINK_TYPE && \ + links[linkNumber].l_form->type == F_TEXTAREA_TYPE) + +#define LinkIsTextLike(linkNumber) \ + (links[linkNumber].type == WWW_FORM_LINK_TYPE && \ + F_TEXTLIKE(links[linkNumber].l_form->type)) + +#ifdef KANJI_CODE_OVERRIDE +char *str_kcode(HTkcode code) +{ + char *p; + static char buff[8]; + + if (current_char_set == TRANSPARENT) { + p = "THRU"; + } else if (!LYRawMode) { + p = "RAW"; + } else { + switch (code) { + case NOKANJI: + p = "AUTO"; + break; + + case EUC: + p = "EUC+"; + break; + + case SJIS: + p = "SJIS"; + break; + + case JIS: + p = " JIS"; + break; + + default: + p = " ???"; + break; + } + } + + if (no_table_center) { + buff[0] = '!'; + strcpy(buff + 1, p); + } else { + strcpy(buff, p); + } + + return buff; +} +#endif + +#ifdef WIN_EX + +static char *str_sjis(char *to, char *from) +{ + if (!LYRawMode) { + strcpy(to, from); +#ifdef KANJI_CODE_OVERRIDE + } else if (last_kcode == EUC) { + EUC_TO_SJIS(from, to); + } else if (last_kcode == SJIS) { + strcpy(to, from); +#endif + } else { + TO_SJIS((unsigned char *) from, (unsigned char *) to); + } + return to; +} + +static void set_ws_title(char *str) +{ + SetConsoleTitle(str); +} + +#endif /* WIN_EX */ + +#if defined(USE_EXTERNALS) || defined(WIN_EX) +#include +#endif + +#ifdef __EMX__ +#include +#endif + +#ifdef DIRED_SUPPORT +#include +#include +#endif /* DIRED_SUPPORT */ + +#include +#include + +/* two constants: */ +HTLinkType *HTInternalLink = 0; +HTAtom *WWW_SOURCE = 0; + +#define NONINTERNAL_OR_PHYS_DIFFERENT(p,n) \ + ((track_internal_links && \ + (!curdoc.internal_link || are_phys_different(p,n))) || \ + are_different(p,n)) + +#define NO_INTERNAL_OR_DIFFERENT(c,n) \ + (track_internal_links || are_different(c,n)) + +static void exit_immediately_with_error_message(int state, int first_file); +static void status_link(const char *curlink_name, int show_more, int show_indx); +static void show_main_statusline(const LinkInfo *curlink, int for_what); +static void form_noviceline(int); +static int are_different(DocInfo *doc1, DocInfo *doc2); + +static int are_phys_different(DocInfo *doc1, DocInfo *doc2); + +#define FASTTAB + +static int sametext(char *een, + char *twee) +{ + if (een && twee) + return (strcmp(een, twee) == 0); + return TRUE; +} + +HTList *Goto_URLs = NULL; /* List of Goto URLs */ + +char *LYRequestTitle = NULL; /* newdoc.title in calls to getfile() */ +char *LYRequestReferer = NULL; /* Referer, may be set in getfile() */ + +static bstring *prev_target = NULL; + +#ifdef DISP_PARTIAL +BOOLEAN display_partial = FALSE; /* could be enabled in HText_new() */ +int NumOfLines_partial = 0; /* number of lines displayed in partial mode */ +#endif + +static int Newline = 0; +static DocInfo newdoc; +static DocInfo curdoc; +static char *traversal_host = NULL; +static char *traversal_link_to_add = NULL; +static char *owner_address = NULL; /* Holds the responsible owner's address */ +static char *ownerS_address = NULL; /* Holds owner's address during source fetch */ + +#ifdef TEXTFIELDS_MAY_NEED_ACTIVATION +static BOOL textinput_activated = FALSE; + +#else +#define textinput_activated TRUE /* a current text input is always active */ +#endif +#ifdef INACTIVE_INPUT_STYLE_VH +BOOL textinput_redrawn = FALSE; + + /*must be public since used in LYhighlight(..) */ +#endif + +#ifdef LY_FIND_LEAKS +/* + * Function for freeing allocated mainloop() variables. - FM + */ +static void free_mainloop_variables(void) +{ + LYFreeDocInfo(&newdoc); + LYFreeDocInfo(&curdoc); + +#ifdef USE_COLOR_STYLE + FREE(curdoc.style); + FREE(newdoc.style); +#endif + FREE(traversal_host); + FREE(traversal_link_to_add); + FREE(owner_address); + FREE(ownerS_address); +#ifdef DIRED_SUPPORT + clear_tags(); + reset_dired_menu(); +#endif /* DIRED_SUPPORT */ + FREE(WWW_Download_File); /* LYGetFile.c/HTFWriter.c */ + FREE(LYRequestReferer); + + return; +} +#endif /* LY_FIND_LEAKS */ + +#ifndef NO_LYNX_TRACE +static void TracelogOpenFailed(void) +{ + WWW_TraceFlag = FALSE; + if (LYCursesON) { + HTUserMsg(TRACELOG_OPEN_FAILED); + } else { + fprintf(stderr, "%s\n", TRACELOG_OPEN_FAILED); + exit_immediately(EXIT_FAILURE); + } +} + +static BOOLEAN LYReopenTracelog(BOOLEAN *trace_flag_ptr) +{ + CTRACE((tfp, "\nTurning off TRACE for fetch of log.\n")); + LYCloseTracelog(); + if ((LYTraceLogFP = LYAppendToTxtFile(LYTraceLogPath)) == NULL) { + TracelogOpenFailed(); + return FALSE; + } + if (TRACE) { + WWW_TraceFlag = FALSE; + *trace_flag_ptr = TRUE; + } + return TRUE; +} + +static void turn_trace_back_on(BOOLEAN *trace_flag_ptr) +{ + if (*trace_flag_ptr == TRUE) { + WWW_TraceFlag = TRUE; + *trace_flag_ptr = FALSE; + fprintf(tfp, "Turning TRACE back on.\n\n"); + } +} +#else +#define LYReopenTracelog(flag) TRUE +#define turn_trace_back_on(flag) /*nothing */ +#endif /* NO_LYNX_TRACE */ + +FILE *TraceFP(void) +{ +#ifndef NO_LYNX_TRACE + if (LYTraceLogFP != 0) { + return LYTraceLogFP; + } +#endif /* NO_LYNX_TRACE */ + return stderr; +} + +BOOLEAN LYOpenTraceLog(void) +{ +#ifndef NO_LYNX_TRACE + if (TRACE && LYUseTraceLog && LYTraceLogFP == NULL) { + /* + * If we can't open it for writing, give up. Otherwise, on VMS close + * it, delete it and any versions from previous sessions so they don't + * accumulate, and open it again. - FM + */ + if ((LYTraceLogFP = LYNewTxtFile(LYTraceLogPath)) == NULL) { + TracelogOpenFailed(); + return FALSE; + } +#ifdef VMS + LYCloseTracelog(); + HTSYS_remove(LYTraceLogPath); + if ((LYTraceLogFP = LYNewTxtFile(LYTraceLogPath)) == NULL) { + TracelogOpenFailed(); + return FALSE; + } +#endif /* VMS */ + fflush(stdout); + fflush(stderr); + fprintf(tfp, "\t\t%s (%s)\n\n", LYNX_TRACELOG_TITLE, LYNX_VERSION); + /* + * If TRACE is on, indicate whether the anonymous restrictions are set. + * - FM, LP, kw + * + * This is only a summary for convenience - it doesn't take the case of + * individual -restrictions= options into account. - kw + */ + if (LYValidate) { + if (LYRestricted && had_restrictions_default) { + CTRACE((tfp, + "Validate and some anonymous restrictions are set.\n")); + } else if (had_restrictions_default) { + CTRACE((tfp, + "Validate restrictions set, restriction \"default\" was given.\n")); + } else if (LYRestricted) { + CTRACE((tfp, + "Validate restrictions set, additional anonymous restrictions ignored.\n")); + } else { + CTRACE((tfp, "Validate restrictions are set.\n")); + } + /* But none of the above can actually happen, since there should + * never be a Trace Log with -validate. If it appears in a log + * file something went wrong! */ + } else if (LYRestricted) { + if (had_restrictions_all) { + CTRACE((tfp, + "Anonymous restrictions set, restriction \"all\" was given.\n")); + } else { + CTRACE((tfp, "Anonymous restrictions are set.\n")); + } + } else if (had_restrictions_all && had_restrictions_default) { + CTRACE((tfp, "Restrictions \"all\" and \"default\" were given.\n")); + } else if (had_restrictions_default) { + CTRACE((tfp, "Restriction \"default\" was given.\n")); + } else if (had_restrictions_all) { + CTRACE((tfp, "\"all\" restrictions are set.\n")); + } + } +#endif /* NO_LYNX_TRACE */ + return TRUE; +} + +void LYCloseTracelog(void) +{ +#ifndef NO_LYNX_TRACE + if (LYTraceLogFP != 0) { + fflush(stdout); + fflush(stderr); + fclose(LYTraceLogFP); + LYTraceLogFP = 0; + } +#endif /* NO_LYNX_TRACE */ +} + +void handle_LYK_TRACE_TOGGLE(void) +{ +#ifndef NO_LYNX_TRACE + WWW_TraceFlag = (BOOLEAN) !WWW_TraceFlag; + if (LYOpenTraceLog()) + HTUserMsg(WWW_TraceFlag ? TRACE_ON : TRACE_OFF); +#else + HTUserMsg(TRACE_DISABLED); +#endif /* NO_LYNX_TRACE */ +} + +void LYSetNewline(int value) +{ + Newline = value; +} + +#define LYSetNewline(value) Newline = value + +int LYGetNewline(void) +{ + return Newline; +} + +#define LYGetNewline() Newline + +void LYChgNewline(int adjust) +{ + LYSetNewline(Newline + adjust); +} + +#define LYChgNewline(adjust) Newline += (adjust) + +#ifdef USE_SOURCE_CACHE +static BOOLEAN from_source_cache = FALSE; + +/* + * Like HTreparse_document(), but also set the flag. + */ +static BOOLEAN reparse_document(void) +{ + BOOLEAN result; + + from_source_cache = TRUE; /* set for LYMainLoop_pageDisplay() */ + if ((result = HTreparse_document()) != FALSE) { + from_source_cache = TRUE; /* set for mainloop refresh */ + } else { + from_source_cache = FALSE; + } + return result; +} +#endif /* USE_SOURCE_CACHE */ + +/* + * Prefer reparsing if we can, but reload if we must - to force regeneration + * of the display. + */ +static BOOLEAN reparse_or_reload(int *cmd) +{ +#ifdef USE_SOURCE_CACHE + if (reparse_document()) { + return FALSE; + } +#endif + *cmd = LYK_RELOAD; + return TRUE; +} + +/* + * Functions for setting the current address + */ +static void set_address(DocInfo *doc, const char *address) +{ + StrAllocCopy(doc->address, address); +} + +static void copy_address(DocInfo *dst, DocInfo *src) +{ + StrAllocCopy(dst->address, src->address); +} + +static void free_address(DocInfo *doc) +{ + FREE(doc->address); +} + +static void move_address(DocInfo *dst, DocInfo *src) +{ + copy_address(dst, src); + free_address(src); +} + +#ifdef DISP_PARTIAL +/* + * This is for traversal call from within partial mode in LYUtils.c + * and HTFormat.c It simply calls HText_pageDisplay() but utilizes + * LYMainLoop.c static variables to manage proper newline position + * in case of #fragment + */ +BOOL LYMainLoop_pageDisplay(int line_num) +{ + const char *pound; + int prev_newline = LYGetNewline(); + + /* + * Override Newline with a new value if user scrolled the document while + * loading (in LYUtils.c). + */ + LYSetNewline(line_num); + +#ifdef USE_SOURCE_CACHE + /* + * reparse_document() acts on 'curdoc' which always on top of the + * history stack: no need to resolve #fragment position since + * we already know it (curdoc.line). + * So bypass here. Sorry for possible confusion... + */ + if (!from_source_cache) +#endif + /* + * If the requested URL has the #fragment, and we are not popped + * from the history stack, and have not scrolled the document yet - + * we should calculate correct newline position for the fragment. + * (This is a bit suboptimal since HTFindPoundSelector() traverse + * anchors list each time, so we have a quadratic complexity + * and may load CPU in a worst case). + */ + if (display_partial + && newdoc.line == 1 && line_num == 1 && prev_newline == 1 + && (pound = findPoundSelector(newdoc.address)) + && *pound && *(pound + 1)) { + if (HTFindPoundSelector(pound + 1)) { + /* HTFindPoundSelector will initialize www_search_result */ + LYSetNewline(www_search_result); + } else { + LYSetNewline(prev_newline); /* restore ??? */ + return NO; /* no repaint */ + } + } + + HText_pageDisplay(LYGetNewline(), prev_target->str); + return YES; +} +#endif /* DISP_PARTIAL */ + +static BOOL set_curdoc_link(int nextlink) +{ + BOOL result = FALSE; + + if (curdoc.link != nextlink + && nextlink >= 0 + && nextlink < nlinks) { + if (curdoc.link >= 0 && curdoc.link < nlinks) { + LYhighlight(FALSE, curdoc.link, prev_target->str); + result = TRUE; + } + curdoc.link = nextlink; + } + return result; +} + +/* + * Setup newdoc to jump to the given line. + * + * FIXME: prefer to also jump to the link given in a URL fragment, but the + * interface of getfile() does not provide that ability yet. + */ +static void goto_line(int nextline) +{ + int n; + int old_link = newdoc.link; + + newdoc.link = 0; + for (n = 0; n < nlinks; ++n) { + if (nextline == links[n].anchor_line_num + 1) { + CTRACE((tfp, "top_of_screen %d\n", HText_getTopOfScreen() + 1)); + CTRACE((tfp, "goto_line(%d) -> link %d -> %d\n", nextline, + old_link, n)); + newdoc.link = n; + break; + } + } +} + +#ifdef USE_MOUSE +static void set_curdoc_link_by_mouse(int nextlink) +{ + if (set_curdoc_link(nextlink)) { + LYhighlight(TRUE, nextlink, prev_target->str); + LYmsec_delay(20); + } +} +#else +#define set_curdoc_link_by_mouse(nextlink) set_curdoc_link(nextlink) +#endif + +static int do_change_link(void) +{ +#ifdef USE_MOUSE + /* Is there a mouse-clicked link waiting? */ + int mouse_tmp = get_mouse_link(); + + /* If yes, use it as the link */ + if (mouse_tmp != -1) { + if (mouse_tmp < 0 || mouse_tmp >= nlinks) { + char *msgtmp = NULL; + + HTSprintf0(&msgtmp, + gettext("Internal error: Invalid mouse link %d!"), + mouse_tmp); + HTAlert(msgtmp); + FREE(msgtmp); + return (-1); /* indicates unexpected error */ + } + set_curdoc_link_by_mouse(mouse_tmp); + } +#endif /* USE_MOUSE */ + return (0); /* indicates OK */ +} + +#ifdef DIRED_SUPPORT +#define DIRED_UNCACHE_1 if (LYAutoUncacheDirLists < 1) /*nothing*/ ;\ + else HTuncache_current_document() +#define DIRED_UNCACHE_2 if (LYAutoUncacheDirLists < 2) /*nothing*/ ;\ + else HTuncache_current_document() +#endif /* DIRED_SUPPORT */ + +static void do_check_goto_URL(bstring **user_input, + char **old_user_input, + BOOLEAN *force_load) +{ + static BOOLEAN always = TRUE; + /* *INDENT-OFF* */ + static struct { + const char *name; + BOOLEAN *flag; + } table[] = { + { STR_FILE_URL, &no_file_url }, + { STR_FILE_URL, &no_goto_file }, + { STR_LYNXEXEC, &no_goto_lynxexec }, + { STR_LYNXPROG, &no_goto_lynxprog }, + { STR_LYNXCGI, &no_goto_lynxcgi }, + { STR_CSO_URL, &no_goto_cso }, + { STR_FINGER_URL, &no_goto_finger }, + { STR_FTP_URL, &no_goto_ftp }, + { STR_GOPHER_URL, &no_goto_gopher }, + { STR_HTTP_URL, &no_goto_http }, + { STR_HTTPS_URL, &no_goto_https }, + { STR_MAILTO_URL, &no_goto_mailto }, + { STR_RLOGIN_URL, &no_goto_rlogin }, + { STR_TELNET_URL, &no_goto_telnet }, + { STR_TN3270_URL, &no_goto_tn3270 }, + { STR_WAIS_URL, &no_goto_wais }, +#ifndef DISABLE_BIBP + { STR_BIBP_URL, &no_goto_bibp }, +#endif +#ifndef DISABLE_NEWS + { STR_NEWS_URL, &no_goto_news }, + { STR_NNTP_URL, &no_goto_nntp }, + { STR_SNEWS_URL, &no_goto_snews }, +#endif +#ifdef EXEC_LINKS + { STR_LYNXEXEC, &local_exec_on_local_files }, + { STR_LYNXPROG, &local_exec_on_local_files }, +#endif /* EXEC_LINKS */ + { STR_LYNXCFG, &no_goto_configinfo }, + { STR_LYNXCFLAGS, &no_goto_configinfo }, + { STR_LYNXCOOKIE, &always }, +#ifdef USE_CACHEJAR + { STR_LYNXCACHE, &always }, +#endif + { STR_LYNXDIRED, &always }, + { STR_LYNXDOWNLOAD, &always }, + { STR_LYNXOPTIONS, &always }, + { STR_LYNXPRINT, &always }, + }; + /* *INDENT-ON* */ + + unsigned n; + BOOLEAN found = FALSE; + + /* allow going to anchors */ + if ((*user_input)->str[0] == '#') { + if ((*user_input)->str[1] && + HTFindPoundSelector((*user_input)->str + 1)) { + /* HTFindPoundSelector will initialize www_search_result, + so we do nothing else. */ + HTAddGotoURL((*user_input)->str); + trimPoundSelector(curdoc.address); + StrAllocCat(curdoc.address, (*user_input)->str); + } + } else { + /* + * If it's not a URL then make it one. + */ + StrAllocCopy(*old_user_input, (*user_input)->str); + LYEnsureAbsoluteURL(old_user_input, "", TRUE); + BStrCopy0((*user_input), *old_user_input); + FREE(*old_user_input); + + for (n = 0; n < TABLESIZE(table); n++) { + if (*(table[n].flag) + && !StrNCmp((*user_input)->str, + table[n].name, + strlen(table[n].name))) { + found = TRUE; + HTUserMsg2(GOTO_XXXX_DISALLOWED, table[n].name); + break; + } + } + if (found) { + ; + } else if (LYValidate && + !isHTTP_URL((*user_input)->str) && + !isHTTPS_URL((*user_input)->str)) { + HTUserMsg(GOTO_NON_HTTP_DISALLOWED); + + } else { + set_address(&newdoc, (*user_input)->str); + newdoc.isHEAD = FALSE; + /* + * Might be an anchor in the same doc from a POST form. If so, + * don't free the content. -- FM + */ + if (are_different(&curdoc, &newdoc)) { + /* + * Make a name for this new URL. + */ + StrAllocCopy(newdoc.title, + gettext("A URL specified by the user")); + LYFreePostData(&newdoc); + FREE(newdoc.bookmark); + newdoc.safe = FALSE; + newdoc.internal_link = FALSE; + *force_load = TRUE; +#ifdef DIRED_SUPPORT + if (lynx_edit_mode) { + DIRED_UNCACHE_2; + } +#endif /* DIRED_SUPPORT */ + } + LYUserSpecifiedURL = TRUE; + HTAddGotoURL(newdoc.address); + } + } +} + +/* returns FALSE if user cancelled input or URL was invalid, TRUE otherwise */ +static BOOL do_check_recall(int ch, + bstring **user_input, + char **old_user_input, + int URLTotal, + int *URLNum, + RecallType recall, + BOOLEAN *FirstURLRecall) +{ + char *cp; + BOOL ret = FALSE; + + if (*old_user_input == 0) + StrAllocCopy(*old_user_input, ""); + + for (;;) { +#ifdef WIN_EX /* 1998/10/11 (Sun) 10:41:05 */ + int len = (int) strlen((*user_input)->str); + + if (len >= 3) { + if (len < MAX_LINE - 1 + && LYIsHtmlSep((*user_input)->str[len - 3]) + && LYIsDosDrive((*user_input)->str + len - 2)) + LYAddPathSep0((*user_input)->str); + + } else if (len == 2 && (*user_input)->str[1] == ':') { + if (LYIsDosDrive((*user_input)->str)) { + LYAddPathSep0((*user_input)->str); + } else { + HTUserMsg2(WWW_ILLEGAL_URL_MESSAGE, (*user_input)->str); + BStrCopy0((*user_input), *old_user_input); + FREE(*old_user_input); + ret = FALSE; + break; + } + } +#endif + /* + * Get rid of leading spaces (and any other spaces). + */ + LYTrimAllStartfile((*user_input)->str); + if (isBEmpty(*user_input) && + !(recall && (ch == UPARROW_KEY || ch == DNARROW_KEY))) { + BStrCopy0((*user_input), *old_user_input); + FREE(*old_user_input); + HTInfoMsg(CANCELLED); + ret = FALSE; + break; + } + if (recall && ch == UPARROW_KEY) { + if (*FirstURLRecall) { + /* + * Use last URL in the list. - FM + */ + *FirstURLRecall = FALSE; + *URLNum = 0; + } else { + /* + * Go back to the previous URL in the list. - FM + */ + *URLNum += 1; + } + if (*URLNum >= URLTotal) + /* + * Roll around to the last URL in the list. - FM + */ + *URLNum = 0; + if ((cp = (char *) HTList_objectAt(Goto_URLs, + *URLNum)) != NULL) { + BStrCopy0((*user_input), cp); + if (goto_buffer + && **old_user_input + && !strcmp(*old_user_input, (*user_input)->str)) { + _statusline(EDIT_CURRENT_GOTO); + } else if ((goto_buffer && URLTotal == 2) || + (!goto_buffer && URLTotal == 1)) { + _statusline(EDIT_THE_PREV_GOTO); + } else { + _statusline(EDIT_A_PREV_GOTO); + } + if ((ch = LYgetBString(user_input, FALSE, 0, recall)) < 0) { + /* + * User cancelled the Goto via ^G. Restore + * user_input and break. - FM + */ + BStrCopy0((*user_input), *old_user_input); + FREE(*old_user_input); + HTInfoMsg(CANCELLED); + ret = FALSE; + break; + } + continue; + } + } else if (recall && ch == DNARROW_KEY) { + if (*FirstURLRecall) { + /* + * Use the first URL in the list. - FM + */ + *FirstURLRecall = FALSE; + *URLNum = URLTotal - 1; + } else { + /* + * Advance to the next URL in the list. - FM + */ + *URLNum -= 1; + } + if (*URLNum < 0) + /* + * Roll around to the first URL in the list. - FM + */ + *URLNum = URLTotal - 1; + if ((cp = (char *) HTList_objectAt(Goto_URLs, *URLNum)) != NULL) { + BStrCopy0((*user_input), cp); + if (goto_buffer && **old_user_input && + !strcmp(*old_user_input, (*user_input)->str)) { + _statusline(EDIT_CURRENT_GOTO); + } else if ((goto_buffer && URLTotal == 2) || + (!goto_buffer && URLTotal == 1)) { + _statusline(EDIT_THE_PREV_GOTO); + } else { + _statusline(EDIT_A_PREV_GOTO); + } + if ((ch = LYgetBString(user_input, FALSE, 0, recall)) < 0) { + /* + * User cancelled the Goto via ^G. Restore + * user_input and break. - FM + */ + BStrCopy0((*user_input), *old_user_input); + FREE(*old_user_input); + HTInfoMsg(CANCELLED); + ret = FALSE; + break; + } + continue; + } + } else { + ret = TRUE; + break; + } + } + return ret; +} + +static void do_cleanup_after_delete(void) +{ + HTuncache_current_document(); + move_address(&newdoc, &curdoc); + newdoc.line = curdoc.line; + if (curdoc.link == nlinks - 1) { + /* + * We deleted the last link on the page. - FM + */ + newdoc.link = curdoc.link - 1; + } else { + newdoc.link = curdoc.link; + } +} + +static int find_link_near_col(int col, + int delta) +{ + int i; + + for (i = curdoc.link; delta > 0 ? (i < nlinks) : (i >= 0); i += delta) { + if ((links[i].ly - links[curdoc.link].ly) * delta > 0) { + int cy = links[i].ly, best = -1, dist = 1000000; + + while ((delta > 0 ? (i < nlinks) : (i >= 0)) && cy == links[i].ly) { + int cx = links[i].lx; + const char *text = LYGetHiliteStr(i, 0); + + if (text != NULL) + cx += (int) strlen(text) / 2; + cx -= col; + if (cx < 0) + cx = -cx; + if (cx < dist) { + dist = cx; + best = i; + } + i += delta; + } + return (best); + } + } + return (-1); +} + +/* + * This is a special feature to traverse every http link derived from startfile + * and check for errors or create crawl output files. Only URL's that begin + * with "traversal_host" are searched - this keeps the search from crossing to + * other servers (a feature, not a bug!). + */ +static int DoTraversal(int c, + BOOLEAN *crawl_ok) +{ + BOOLEAN rlink_rejected = FALSE; + BOOLEAN rlink_exists; + BOOLEAN rlink_allowed; + + rlink_exists = (BOOL) (nlinks > 0 && + links[curdoc.link].type != WWW_FORM_LINK_TYPE && + links[curdoc.link].lname != NULL); + + if (rlink_exists) { + rlink_rejected = lookup_reject(links[curdoc.link].lname); + if (!rlink_rejected && + traversal_host && + links[curdoc.link].lname) { + if (!isLYNXIMGMAP(links[curdoc.link].lname)) { + rlink_allowed = (BOOL) !StrNCmp(traversal_host, + links[curdoc.link].lname, + strlen(traversal_host)); + } else { + rlink_allowed = (BOOL) !StrNCmp(traversal_host, + links[curdoc.link].lname + LEN_LYNXIMGMAP, + strlen(traversal_host)); + } + } else { + rlink_allowed = FALSE; + } + } else { + rlink_allowed = FALSE; + } + if (rlink_exists && rlink_allowed) { + if (lookup_link(links[curdoc.link].lname)) { + if (more_links || + (curdoc.link > -1 && curdoc.link < nlinks - 1)) { + c = DNARROW_KEY; + } else { + if (STREQ(curdoc.title, "Entry into main screen") || + (nhist <= 0)) { + if (!dump_output_immediately) { + cleanup(); + exit_immediately(EXIT_FAILURE); + } + c = -1; + } else { + c = LTARROW_KEY; + } + } + } else { + StrAllocCopy(traversal_link_to_add, + links[curdoc.link].lname); + if (!isLYNXIMGMAP(traversal_link_to_add)) + *crawl_ok = TRUE; + c = RTARROW_KEY; + } + } else { /* no good right link, so only down and left arrow ok */ + if (rlink_exists /* && !rlink_rejected */ ) + /* uncomment in previous line to avoid duplicates - kw */ + add_to_reject_list(links[curdoc.link].lname); + if (more_links || + (curdoc.link > -1 && curdoc.link < nlinks - 1)) { + c = DNARROW_KEY; + } else { + /* + * curdoc.title doesn't always work, so bail out if the history + * list is empty. + */ + if (STREQ(curdoc.title, "Entry into main screen") || + (nhist <= 0)) { + if (!dump_output_immediately) { + cleanup(); + exit_immediately(EXIT_FAILURE); + } + c = -1; + } else { + c = LTARROW_KEY; + } + } + } + CTRACE((tfp, "DoTraversal(%d:%d) -> %s\n", + nlinks > 0 ? curdoc.link : 0, + nlinks, + LYKeycodeToString(c, FALSE))); + return c; +} + +static BOOLEAN check_history(void) +{ + const char *base; + + if (!curdoc.post_data) + /* + * Normal case - List Page is not associated with post data. - kw + */ + return TRUE; + + if (nhist > 0 + && !LYresubmit_posts + && HDOC(nhist - 1).post_data + && BINEQ(curdoc.post_data, HDOC(nhist - 1).post_data) + && (base = HText_getContentBase()) != 0) { + char *text = !isLYNXIMGMAP(HDOC(nhist - 1).address) + ? HDOC(nhist - 1).address + : HDOC(nhist - 1).address + LEN_LYNXIMGMAP; + + if (!StrNCmp(base, text, strlen(base))) { + /* + * Normal case - as best as we can check, the document at the top + * of the history stack seems to be the document the List Page is + * about (or a LYNXIMGMAP derived from it), and LYresubmit_posts is + * not set, so don't prompt here. If we actually have to repeat a + * POST because, against expectations, the underlying document + * isn't cached any more, HTAccess will prompt for confirmation, + * unless we had LYK_NOCACHE -kw + */ + return TRUE; + } + } + return FALSE; +} + +static int handle_LYK_ACTIVATE(int *c, + int cmd GCC_UNUSED, + BOOLEAN *try_internal GCC_UNUSED, + BOOLEAN *refresh_screen, + BOOLEAN *force_load, + int real_cmd) +{ + if (do_change_link() == -1) { + LYforce_no_cache = FALSE; + reloading = FALSE; + return 1; /* mouse stuff was confused, ignore - kw */ + } + if (nlinks > 0) { + if (links[curdoc.link].type == WWW_FORM_LINK_TYPE) { +#ifdef TEXTFIELDS_MAY_NEED_ACTIVATION + if (real_cmd == LYK_ACTIVATE && textfields_need_activation && + F_TEXTLIKE(links[curdoc.link].l_form->type)) { + + textinput_activated = TRUE; + show_main_statusline(&links[curdoc.link], FOR_INPUT); + textfields_need_activation = textfields_activation_option; + + return 0; + } +#endif + /* + * Don't try to submit forms with bad actions. - FM + */ + if (links[curdoc.link].l_form->type == F_SUBMIT_TYPE || + links[curdoc.link].l_form->type == F_IMAGE_SUBMIT_TYPE || + links[curdoc.link].l_form->type == F_TEXT_SUBMIT_TYPE) { + /* + * Do nothing if it's disabled. - FM + */ + if (links[curdoc.link].l_form->disabled == YES) { + HTOutputFormat = WWW_PRESENT; + LYforce_no_cache = FALSE; + reloading = FALSE; + return 0; + } + /* + * Make sure we have an action. - FM + */ + if (isEmpty(links[curdoc.link].l_form->submit_action)) { + HTUserMsg(NO_FORM_ACTION); + HTOutputFormat = WWW_PRESENT; + LYforce_no_cache = FALSE; + reloading = FALSE; + return 0; + } + /* + * Check for no_mail if the form action is a mailto URL. - FM + */ + if (links[curdoc.link].l_form->submit_method + == URL_MAIL_METHOD && no_mail) { + HTAlert(FORM_MAILTO_DISALLOWED); + HTOutputFormat = WWW_PRESENT; + LYforce_no_cache = FALSE; + reloading = FALSE; + return 0; + } + /* + * Make sure this isn't a spoof in an account with restrictions + * on file URLs. - FM + */ + if (no_file_url && + isFILE_URL(links[curdoc.link].l_form->submit_action)) { + HTAlert(FILE_ACTIONS_DISALLOWED); + HTOutputFormat = WWW_PRESENT; + LYforce_no_cache = FALSE; + reloading = FALSE; + return 0; + } + /* + * Make sure this isn't a spoof attempt via an internal URL. - + * FM + */ + if (isLYNXCOOKIE(links[curdoc.link].l_form->submit_action) || + isLYNXCACHE(links[curdoc.link].l_form->submit_action) || +#ifdef DIRED_SUPPORT +#ifdef OK_PERMIT + (isLYNXDIRED(links[curdoc.link].l_form->submit_action) && + (no_dired_support || + strncasecomp((links[curdoc.link].l_form->submit_action + + 10), + "//PERMIT_LOCATION", 17) || + !LYIsUIPage(curdoc.address, UIP_PERMIT_OPTIONS))) || +#else + isLYNXDIRED(links[curdoc.link].l_form->submit_action) || +#endif /* OK_PERMIT */ +#endif /* DIRED_SUPPORT */ + isLYNXDOWNLOAD(links[curdoc.link].l_form->submit_action) || + isLYNXHIST(links[curdoc.link].l_form->submit_action) || + isLYNXEDITMAP(links[curdoc.link].l_form->submit_action) || + isLYNXKEYMAP(links[curdoc.link].l_form->submit_action) || + isLYNXIMGMAP(links[curdoc.link].l_form->submit_action) || + isLYNXPRINT(links[curdoc.link].l_form->submit_action) || + isLYNXEXEC(links[curdoc.link].l_form->submit_action) || + isLYNXPROG(links[curdoc.link].l_form->submit_action)) { + + HTAlert(SPECIAL_ACTION_DISALLOWED); + CTRACE((tfp, "LYMainLoop: Rejected '%s'\n", + links[curdoc.link].l_form->submit_action)); + HTOutputFormat = WWW_PRESENT; + LYforce_no_cache = FALSE; + reloading = FALSE; + return 0; + } +#ifdef NOTDEFINED /* We're disabling form inputs instead of using this. - FM */ + /* + * Check for enctype and let user know we don't yet support + * multipart/form-data - FM + */ + if (links[curdoc.link].l_form->submit_enctype) { + if (!strcmp(links[curdoc.link].l_form->submit_enctype, + "multipart/form-data")) { + HTAlert(gettext("Enctype multipart/form-data not yet supported! Cannot submit.")); + HTOutputFormat = WWW_PRESENT; + LYforce_no_cache = FALSE; + reloading = FALSE; + return 0; + } + } +#endif /* NOTDEFINED */ + if (check_realm) { + LYPermitURL = TRUE; + } + if (no_filereferer == TRUE && isFILE_URL(curdoc.address)) { + LYNoRefererForThis = TRUE; + } + if (links[curdoc.link].l_form->submit_method != URL_MAIL_METHOD) { + StrAllocCopy(newdoc.title, + LYGetHiliteStr(curdoc.link, 0)); + } + } + + /* + * Normally we don't get here for text input fields, but it can + * happen as a result of mouse positioning. In that case the + * statusline will not have updated info, so update it now. - kw + */ + if (F_TEXTLIKE(links[curdoc.link].l_form->type)) { + show_formlink_statusline(links[curdoc.link].l_form, + (real_cmd == LYK_NOCACHE || + real_cmd == LYK_DOWNLOAD || + real_cmd == LYK_HEAD || + (real_cmd == LYK_MOUSE_SUBMIT && + !textinput_activated)) ? + FOR_PANEL : FOR_INPUT); + if (user_mode == NOVICE_MODE && + textinput_activated && + (real_cmd == LYK_ACTIVATE || + real_cmd == LYK_MOUSE_SUBMIT)) { + form_noviceline(FormIsReadonly(links[curdoc.link].l_form)); + } + } + + *c = change_form_link(curdoc.link, + &newdoc, refresh_screen, + FALSE, + (real_cmd == LYK_MOUSE_SUBMIT || + real_cmd == LYK_NOCACHE || + real_cmd == LYK_DOWNLOAD || + real_cmd == LYK_HEAD)); + if (*c != LKC_DONE || *refresh_screen) { + /* + * Cannot have been a submit field for which newdoc was filled + * in. - kw + */ + if ((links[curdoc.link].l_form->type == F_SUBMIT_TYPE || + links[curdoc.link].l_form->type == F_IMAGE_SUBMIT_TYPE || + links[curdoc.link].l_form->type == F_TEXT_SUBMIT_TYPE) && + links[curdoc.link].l_form->submit_method + != URL_MAIL_METHOD) { + /* + * Try to undo change of newdoc.title done above. + */ + if (HText_getTitle()) { + StrAllocCopy(newdoc.title, HText_getTitle()); + } else if (curdoc.title) { + StrAllocCopy(newdoc.title, curdoc.title); + } + } + } else { + if (HTOutputFormat == WWW_DOWNLOAD && + newdoc.post_data != NULL && + newdoc.safe == FALSE) { + + if ((HText_POSTReplyLoaded(&newdoc) == TRUE) && + HTConfirm(CONFIRM_POST_RESUBMISSION) == FALSE) { + HTInfoMsg(CANCELLED); + HTOutputFormat = WWW_PRESENT; + LYforce_no_cache = FALSE; + copy_address(&newdoc, &curdoc); + StrAllocCopy(newdoc.title, curdoc.title); + BStrCopy(newdoc.post_data, curdoc.post_data); + StrAllocCopy(newdoc.post_content_type, + curdoc.post_content_type); + StrAllocCopy(newdoc.bookmark, curdoc.bookmark); + newdoc.isHEAD = curdoc.isHEAD; + newdoc.safe = curdoc.safe; + newdoc.internal_link = curdoc.internal_link; + return 0; + } + } + /* + * Moved here from earlier to only apply when it should. + * Anyway, why should realm checking be overridden for form + * submissions, this seems to be an unnecessary loophole?? But + * that's the way it was, maybe there is some reason. However, + * at least make sure this doesn't weaken restrictions implied + * by -validate! + * - kw 1999-05-25 + */ + if (check_realm && !LYValidate) { + LYPermitURL = TRUE; + } + } + if (*c == LKC_DONE) { + *c = DO_NOTHING; + } else if (*c == 23) { + *c = DO_NOTHING; + *refresh_screen = TRUE; + } else { + /* Avoid getting stuck with repeatedly calling + * handle_LYK_ACTIVATE(), instead of calling change_form_link() + * directly from mainloop(), for text input fields. - kw + */ + switch (LKC_TO_C(*c)) { + case '\n': + case '\r': + default: + if ((real_cmd == LYK_ACTIVATE || + real_cmd == LYK_MOUSE_SUBMIT) && + F_TEXTLIKE(links[curdoc.link].l_form->type) && + textinput_activated) { + return 3; + } + break; + } + } + return 2; + } else { + /* + * Not a forms link. + * + * Make sure this isn't a spoof in an account with restrictions on + * file URLs. - FM + */ + if (no_file_url && isFILE_URL(links[curdoc.link].lname)) { + if (!isFILE_URL(curdoc.address) && + !((isLYNXEDITMAP(curdoc.address) || + isLYNXKEYMAP(curdoc.address) || + isLYNXCOOKIE(curdoc.address) || + isLYNXCACHE(curdoc.address)) && + !StrNCmp(links[curdoc.link].lname, + helpfilepath, + strlen(helpfilepath)))) { + HTAlert(FILE_SERVED_LINKS_DISALLOWED); + reloading = FALSE; + return 0; + } else if (curdoc.bookmark != NULL) { + HTAlert(FILE_BOOKMARKS_DISALLOWED); + reloading = FALSE; + return 0; + } + } + /* + * Make sure this isn't a spoof attempt via an internal URL in a + * non-internal document. - FM + */ + if ((isLYNXCOOKIE(links[curdoc.link].lname) && + (strcmp(NonNull(curdoc.title), COOKIE_JAR_TITLE) || + !isLYNXCOOKIE(curdoc.address))) || +#ifdef USE_CACHEJAR + (isLYNXCACHE(links[curdoc.link].lname) && + (strcmp(NonNull(curdoc.title), CACHE_JAR_TITLE) || + !isLYNXCACHE(curdoc.address))) || +#endif +#ifdef DIRED_SUPPORT + (isLYNXDIRED(links[curdoc.link].lname) && + !LYIsUIPage(curdoc.address, UIP_DIRED_MENU) && + !LYIsUIPage(curdoc.address, UIP_PERMIT_OPTIONS) && +#ifdef OK_INSTALL + !LYIsUIPage(curdoc.address, UIP_INSTALL) && +#endif /* OK_INSTALL */ + !LYIsUIPage(curdoc.address, UIP_UPLOAD_OPTIONS)) || +#endif /* DIRED_SUPPORT */ + (isLYNXDOWNLOAD(links[curdoc.link].lname) && + !LYIsUIPage(curdoc.address, UIP_DOWNLOAD_OPTIONS)) || + (isLYNXHIST(links[curdoc.link].lname) && + !LYIsUIPage(curdoc.address, UIP_HISTORY) && + !LYIsUIPage(curdoc.address, UIP_LIST_PAGE) && + !LYIsUIPage(curdoc.address, UIP_ADDRLIST_PAGE)) || + (isLYNXPRINT(links[curdoc.link].lname) && + !LYIsUIPage(curdoc.address, UIP_PRINT_OPTIONS))) { + HTAlert(SPECIAL_VIA_EXTERNAL_DISALLOWED); + HTOutputFormat = WWW_PRESENT; + LYforce_no_cache = FALSE; + reloading = FALSE; + return 0; + } +#ifdef USE_EXTERNALS + if (run_external(links[curdoc.link].lname, TRUE)) { + *refresh_screen = TRUE; + return 0; + } +#endif /* USE_EXTERNALS */ + + /* + * Follow a normal link or anchor. + */ + set_address(&newdoc, links[curdoc.link].lname); + StrAllocCopy(newdoc.title, LYGetHiliteStr(curdoc.link, 0)); + /* + * For internal links, retain POST content if present. If we are + * on the List Page, prevent pushing it on the history stack. + * Otherwise set try_internal to signal that the top of the loop + * should attempt to reposition directly, without calling getfile. + * - kw + */ + if (track_internal_links) { + /* + * Might be an internal link anchor in the same doc. If so, take + * the try_internal shortcut if we didn't fall through from + * LYK_NOCACHE. - kw + */ + newdoc.internal_link = + (links[curdoc.link].type == WWW_INTERN_LINK_TYPE); + if (newdoc.internal_link) { + /* + * Special case of List Page document with an internal link + * indication, which may really stand for an internal link + * within the document the List Page is about. - kw + */ + if (LYIsListpageTitle(NonNull(curdoc.title)) && + (LYIsUIPage(curdoc.address, UIP_LIST_PAGE) || + LYIsUIPage(curdoc.address, UIP_ADDRLIST_PAGE))) { + if (check_history()) { + LYinternal_flag = TRUE; + } else { + HTLastConfirmCancelled(); /* reset flag */ + if (!confirm_post_resub(newdoc.address, + newdoc.title, + ((LYresubmit_posts && + HText_POSTReplyLoaded(&newdoc)) + ? 1 + : 2), + 2)) { + if (HTLastConfirmCancelled() || + (LYresubmit_posts && + cmd != LYK_NOCACHE && + !HText_POSTReplyLoaded(&newdoc))) { + /* cancel the whole thing */ + LYforce_no_cache = FALSE; + reloading = FALSE; + copy_address(&newdoc, &curdoc); + StrAllocCopy(newdoc.title, curdoc.title); + newdoc.internal_link = curdoc.internal_link; + HTInfoMsg(CANCELLED); + return 1; + } else if (LYresubmit_posts && + cmd != LYK_NOCACHE) { + /* If LYresubmit_posts is set, and the + answer was No, and the key wasn't + NOCACHE, and we have a cached copy, + then use it. - kw */ + LYforce_no_cache = FALSE; + } else { + /* if No, but not ^C or ^G, drop + * the post data. Maybe the link + * wasn't meant to be internal after + * all, here we can recover from that + * assumption. - kw */ + LYFreePostData(&newdoc); + newdoc.internal_link = FALSE; + HTAlert(DISCARDING_POST_DATA); + } + } + } + /* + * Don't push the List Page if we follow an internal link + * given by it. - kw + */ + free_address(&curdoc); + } else if (cmd != LYK_NOCACHE) { + *try_internal = TRUE; + } + if (!(LYresubmit_posts && newdoc.post_data)) + LYinternal_flag = TRUE; + /* We still set force_load so that history pushing + * etc. will be done. - kw + */ + *force_load = TRUE; + return 1; + } else { + /* + * Free POST content if not an internal link. - kw + */ + LYFreePostData(&newdoc); + } + } + /* + * Might be an anchor in the same doc from a POST form. If so, + * don't free the content. -- FM + */ + if (are_different(&curdoc, &newdoc)) { + LYFreePostData(&newdoc); + FREE(newdoc.bookmark); + if (isLYNXMESSAGES(newdoc.address)) + LYforce_no_cache = TRUE; + } + if (!no_jump && lynxjumpfile && curdoc.address && + !strcmp(lynxjumpfile, curdoc.address)) { + LYJumpFileURL = TRUE; + LYUserSpecifiedURL = TRUE; + } else if ((curdoc.title && + (LYIsUIPage(curdoc.address, UIP_HISTORY) || + !strcmp(curdoc.title, HISTORY_PAGE_TITLE))) || + curdoc.bookmark != NULL || + (lynxjumpfile && + curdoc.address && + !strcmp(lynxjumpfile, curdoc.address))) { + LYUserSpecifiedURL = TRUE; + } else if (no_filereferer == TRUE && + curdoc.address != NULL && + isFILE_URL(curdoc.address)) { + LYNoRefererForThis = TRUE; + } + newdoc.link = 0; + *force_load = TRUE; /* force MainLoop to reload */ +#ifdef USE_PRETTYSRC + psrc_view = FALSE; /* we get here if link is not internal */ +#endif + +#if defined(DIRED_SUPPORT) && !defined(__DJGPP__) + if (lynx_edit_mode) { + DIRED_UNCACHE_2; + /* + * Unescaping any slash chars in the URL, but avoid double + * unescaping and too-early unescaping of other chars. - KW + */ + HTUnEscapeSome(newdoc.address, "/"); + /* avoid stripping final slash for root dir - kw */ + if (strcasecomp(newdoc.address, "file://localhost/")) + strip_trailing_slash(newdoc.address); + } +#endif /* DIRED_SUPPORT && !__DJGPP__ */ + if (isLYNXCOOKIE(curdoc.address) + || isLYNXCACHE(curdoc.address)) { + HTuncache_current_document(); + } + } + } + return 0; +} +/* + * If the given form link does not point to the requested type, search for + * the first link belonging to the form which does. If there are none, + * return null. + */ +#define SameFormAction(form,submit) \ + ((submit) \ + ? (F_SUBMITLIKE((form)->type)) \ + : ((form)->type == F_RESET_TYPE)) + +static FormInfo *FindFormAction(FormInfo * given, int submit) +{ + FormInfo *result = NULL; + FormInfo *fi; + int i; + + if (given == NULL) { + HTAlert(LINK_NOT_IN_FORM); + } else if (SameFormAction(given, submit)) { + result = given; + } else { + for (i = 0; i < nlinks; i++) { + if ((fi = links[i].l_form) != 0 && + fi->number == given->number && + (SameFormAction(fi, submit))) { + result = fi; + break; + } + } + } + return result; +} + +static FormInfo *MakeFormAction(FormInfo * given, int submit) +{ + FormInfo *result = 0; + + if (given != 0) { + result = typecalloc(FormInfo); + + if (result == NULL) + outofmem(__FILE__, "MakeFormAction"); + + *result = *given; + if (submit) { + if (result->submit_action == 0) { + PerFormInfo *pfi = HText_PerFormInfo(result->number); + + *result = pfi->data; + } + result->type = F_SUBMIT_TYPE; + } else { + result->type = F_RESET_TYPE; + } + result->number = given->number; + } + return result; +} + +static void handle_LYK_SUBMIT(int cur, DocInfo *doc, BOOLEAN *refresh_screen) +{ + FormInfo *form = FindFormAction(links[cur].l_form, 1); + FormInfo *make = NULL; + char *save_submit_action = NULL; + + if (form == 0) { + make = MakeFormAction(links[cur].l_form, 1); + form = make; + } + + if (form != 0) { + StrAllocCopy(save_submit_action, form->submit_action); + form->submit_action = HTPrompt(EDIT_SUBMIT_URL, form->submit_action); + + if (isEmpty(form->submit_action) || + (!isLYNXCGI(form->submit_action) && + StrNCmp(form->submit_action, "http", 4))) { + HTUserMsg(FORM_ACTION_NOT_HTTP_URL); + } else { + HTInfoMsg(SUBMITTING_FORM); + HText_SubmitForm(form, doc, form->name, form->value); + *refresh_screen = TRUE; + } + + StrAllocCopy(form->submit_action, save_submit_action); + FREE(make); + } +} + +static void handle_LYK_RESET(int cur, BOOLEAN *refresh_screen) +{ + FormInfo *form = FindFormAction(links[cur].l_form, 0); + FormInfo *make = NULL; + + if (form == 0) { + make = MakeFormAction(links[cur].l_form, 0); + form = make; + } + + if (form != 0) { + HTInfoMsg(RESETTING_FORM); + HText_ResetForm(form); + *refresh_screen = TRUE; + FREE(make); + } +} + +#ifdef USE_ADDRLIST_PAGE +static BOOLEAN handle_LYK_ADDRLIST(int *cmd) +{ + /* + * Don't do if already viewing list addresses page. + */ + if (LYIsUIPage(curdoc.address, UIP_ADDRLIST_PAGE)) { + /* + * Already viewing list page, so get out. + */ + *cmd = LYK_PREV_DOC; + return TRUE; + } + + /* + * Print address list page to file. + */ + if (showlist(&newdoc, FALSE) < 0) + return FALSE; + StrAllocCopy(newdoc.title, ADDRLIST_PAGE_TITLE); + /* + * showlist will set newdoc's other fields. It may leave post_data intact + * so the list can be used to follow internal links in the current document + * even if it is a POST response. - kw + */ + + if (LYValidate || check_realm) { + LYPermitURL = TRUE; + StrAllocCopy(lynxlistfile, newdoc.address); + } + return FALSE; +} +#endif /* USE_ADDRLIST_PAGE */ + +static void handle_LYK_ADD_BOOKMARK(BOOLEAN *refresh_screen, + int *old_c, + int real_c) +{ + int c; + + if (LYValidate) { + if (*old_c != real_c) { + *old_c = real_c; + HTUserMsg(BOOKMARKS_DISABLED); + } + return; + } + + if (!LYIsUIPage(curdoc.address, UIP_HISTORY) && + !LYIsUIPage(curdoc.address, UIP_SHOWINFO) && + !LYIsUIPage(curdoc.address, UIP_PRINT_OPTIONS) && +#ifdef DIRED_SUPPORT + !LYIsUIPage(curdoc.address, UIP_DIRED_MENU) && + !LYIsUIPage(curdoc.address, UIP_PERMIT_OPTIONS) && + !LYIsUIPage(curdoc.address, UIP_UPLOAD_OPTIONS) && +#endif /* DIRED_SUPPORT */ + !LYIsUIPage(curdoc.address, UIP_DOWNLOAD_OPTIONS) && + !isLYNXCOOKIE(curdoc.address) && + !isLYNXCACHE(curdoc.address) && + !LYIsUIPage(curdoc.address, UIP_OPTIONS_MENU) && + ((nlinks <= 0) || + (links[curdoc.link].lname != NULL && + !isLYNXHIST(links[curdoc.link].lname) && + !isLYNXPRINT(links[curdoc.link].lname) && + !isLYNXDIRED(links[curdoc.link].lname) && + !isLYNXDOWNLOAD(links[curdoc.link].lname) && + !isLYNXCOOKIE(links[curdoc.link].lname) && + !isLYNXCACHE(links[curdoc.link].lname) && + !isLYNXPRINT(links[curdoc.link].lname)))) { + if (nlinks > 0) { + if (curdoc.post_data == NULL && + curdoc.bookmark == NULL && + !LYIsUIPage(curdoc.address, UIP_LIST_PAGE) && + !LYIsUIPage(curdoc.address, UIP_ADDRLIST_PAGE) && + !LYIsUIPage(curdoc.address, UIP_VLINKS)) { + /* + * The document doesn't have POST content, and is not a + * bookmark file, nor is the list or visited links page, so we + * can save either that or the link. - FM + */ + _statusline(BOOK_D_L_OR_CANCEL); + if ((c = LYgetch_single()) == 'D') { + save_bookmark_link(curdoc.address, curdoc.title); + *refresh_screen = TRUE; /* MultiBookmark support */ + goto check_add_bookmark_to_self; + } + } else { + if (LYMultiBookmarks == MBM_OFF && + curdoc.bookmark != NULL && + strstr(curdoc.address, + (*bookmark_page == '.' + ? (bookmark_page + 1) + : bookmark_page)) != NULL) { + /* + * If multiple bookmarks are disabled, offer the L)ink or + * C)ancel, but with wording which indicates that the link + * already exists in this bookmark file. - FM + */ + _statusline(MULTIBOOKMARKS_SELF); + } else if (curdoc.post_data != NULL && + links[curdoc.link].type == WWW_INTERN_LINK_TYPE) { + /* + * Internal link, and document has POST content. + */ + HTUserMsg(NOBOOK_POST_FORM); + return; + } else { + /* + * Only offer the link in a document with POST content, or + * if the current document is a bookmark file and multiple + * bookmarks are enabled. - FM + */ + _statusline(BOOK_L_OR_CANCEL); + } + c = LYgetch_single(); + } + if (c == 'L') { + if (curdoc.post_data != NULL && + links[curdoc.link].type == WWW_INTERN_LINK_TYPE) { + /* + * Internal link, and document has POST content. + */ + HTUserMsg(NOBOOK_POST_FORM); + return; + } + /* + * User does want to save the link. - FM + */ + if (links[curdoc.link].type != WWW_FORM_LINK_TYPE) { + save_bookmark_link(links[curdoc.link].lname, + LYGetHiliteStr(curdoc.link, 0)); + *refresh_screen = TRUE; /* MultiBookmark support */ + } else { + HTUserMsg(NOBOOK_FORM_FIELD); + return; + } + } else { + return; + } + } else if (curdoc.post_data != NULL) { + /* + * No links, and document has POST content. - FM + */ + HTUserMsg(NOBOOK_POST_FORM); + return; + } else if (curdoc.bookmark != NULL) { + /* + * It's a bookmark file from which all of the links were deleted. + * - FM + */ + HTUserMsg(BOOKMARKS_NOLINKS); + return; + } else { + _statusline(BOOK_D_OR_CANCEL); + if (LYgetch_single() == 'D') { + save_bookmark_link(curdoc.address, curdoc.title); + *refresh_screen = TRUE; /* MultiBookmark support */ + } else { + return; + } + } + check_add_bookmark_to_self: + if (curdoc.bookmark && BookmarkPage && + !strcmp(curdoc.bookmark, BookmarkPage)) { + HTuncache_current_document(); + move_address(&newdoc, &curdoc); + StrAllocCopy(newdoc.bookmark, curdoc.bookmark); + newdoc.line = curdoc.line; + newdoc.link = curdoc.link; + newdoc.internal_link = FALSE; + } + } else { + if (*old_c != real_c) { + *old_c = real_c; + HTUserMsg(NOBOOK_HSML); + } + } +} + +static void handle_LYK_CLEAR_AUTH(int *old_c, + int real_c) +{ + if (*old_c != real_c) { + *old_c = real_c; + if (HTConfirm(CLEAR_ALL_AUTH_INFO)) { + FREE(authentication_info[0]); + FREE(authentication_info[1]); + FREE(proxyauth_info[0]); + FREE(proxyauth_info[1]); + HTClearHTTPAuthInfo(); +#ifndef DISABLE_NEWS + HTClearNNTPAuthInfo(); +#endif +#ifndef DISABLE_FTP + HTClearFTPPassword(); +#endif + HTUserMsg(AUTH_INFO_CLEARED); + } else { + HTUserMsg(CANCELLED); + } + } +} + +static int handle_LYK_COMMAND(bstring **user_input) +{ + LYKeymapCode ch; + Kcmd *mp; + char *src, *tmp; + + BStrCopy0((*user_input), ""); + _statusline(": "); + if (LYgetBString(user_input, FALSE, 0, RECALL_CMD) >= 0) { + src = LYSkipBlanks((*user_input)->str); + tmp = LYSkipNonBlanks(src); + *tmp = 0; + ch = ((mp = LYStringToKcmd(src)) != 0) ? mp->code : LYK_UNKNOWN; + CTRACE((tfp, "LYK_COMMAND(%s.%s) = %d\n", src, tmp, (int) ch)); + if (ch == 0) { + return *src ? -1 : 0; + } + /* FIXME: reuse the rest of the buffer for parameters */ + return ch; + } + return 0; +} + +static void handle_LYK_COMMENT(BOOLEAN *refresh_screen, + char **owner_address_p, + int *old_c, + int real_c) +{ + int c; + + if (!*owner_address_p && + strncasecomp(curdoc.address, "http", 4)) { + if (*old_c != real_c) { + *old_c = real_c; + HTUserMsg(NO_OWNER); + } + } else if (no_mail) { + if (*old_c != real_c) { + *old_c = real_c; + HTUserMsg(MAIL_DISALLOWED); + } + } else { + if (HTConfirmDefault(CONFIRM_COMMENT, NO)) { + if (!*owner_address_p) { + /* + * No owner defined, so make a guess and and offer it to the + * user. - FM + */ + char *address = NULL; + char *temp = HTParse(curdoc.address, "", PARSE_PATH); + char *cp; + + if (temp != NULL) { + HTUnEscape(temp); + if (LYIsTilde(*temp) && strlen(temp) > 1) { + /* + * It's a ~user URL so guess user@host. - FM + */ + if ((cp = StrChr((temp + 1), '/')) != NULL) + *cp = '\0'; + StrAllocCopy(address, STR_MAILTO_URL); + StrAllocCat(address, (temp + 1)); + StrAllocCat(address, "@"); + } + FREE(temp); + } + if (address == NULL) + /* + * Wasn't a ~user URL so guess WebMaster@host. - FM + */ + StrAllocCopy(address, "mailto:WebMaster@"); + temp = HTParse(curdoc.address, "", PARSE_HOST); + StrAllocCat(address, temp); + HTSprintf0(&temp, NO_OWNER_USE, address); + c = HTConfirmDefault(temp, NO); + FREE(temp); + if (c == YES) { + StrAllocCopy(*owner_address_p, address); + FREE(address); + } else { + FREE(address); + return; + } + } + if (is_url(*owner_address_p) != MAILTO_URL_TYPE) { + /* + * The address is a URL. Just follow the link. + */ + set_address(&newdoc, *owner_address_p); + newdoc.internal_link = FALSE; + } else { + /* + * The owner_address is a mailto: URL. + */ + const char *kp = HText_getRevTitle(); + const char *id = HText_getMessageID(); + char *tmptitle = NULL; + + if (!kp && HTMainAnchor) { + kp = HTAnchor_subject(HTMainAnchor); + if (non_empty(kp)) { + if (strncasecomp(kp, "Re: ", 4)) { + StrAllocCopy(tmptitle, "Re: "); + StrAllocCat(tmptitle, kp); + kp = tmptitle; + } + } + } + + if (StrChr(*owner_address_p, ':') != NULL) + /* + * Send a reply. The address is after the colon. + */ + reply_by_mail(StrChr(*owner_address_p, ':') + 1, + curdoc.address, + NonNull(kp), id); + else + reply_by_mail(*owner_address_p, curdoc.address, + NonNull(kp), id); + + FREE(tmptitle); + *refresh_screen = TRUE; /* to force a showpage */ + } + } + } +} + +#ifdef USE_CACHEJAR +static BOOLEAN handle_LYK_CACHE_JAR(int *cmd) +{ + /* + * Don't do this if already viewing cache jar. + */ + if (!isLYNXCACHE(curdoc.address)) { + set_address(&newdoc, STR_LYNXCACHE "/"); + LYFreePostData(&newdoc); + FREE(newdoc.bookmark); + newdoc.isHEAD = FALSE; + newdoc.safe = FALSE; + newdoc.internal_link = FALSE; + LYforce_no_cache = TRUE; + if (LYValidate || check_realm) { + LYPermitURL = TRUE; + } + } else { + /* + * If already in the cache jar, get out. + */ + *cmd = LYK_PREV_DOC; + return TRUE; + } + return FALSE; +} +#endif /* USE_CACHEJAR */ + +static BOOLEAN handle_LYK_COOKIE_JAR(int *cmd) +{ + /* + * Don't do if already viewing the cookie jar. + */ + if (!isLYNXCOOKIE(curdoc.address)) { + set_address(&newdoc, "LYNXCOOKIE:/"); + LYFreePostData(&newdoc); + FREE(newdoc.bookmark); + newdoc.isHEAD = FALSE; + newdoc.safe = FALSE; + newdoc.internal_link = FALSE; + LYforce_no_cache = TRUE; + if (LYValidate || check_realm) { + LYPermitURL = TRUE; + } + } else { + /* + * If already in the cookie jar, get out. + */ + *cmd = LYK_PREV_DOC; + return TRUE; + } + return FALSE; +} + +#if defined(DIRED_SUPPORT) +static void handle_LYK_CREATE(void) +{ + if (lynx_edit_mode && !no_dired_support) { + if (local_create(&curdoc) > 0) { + DIRED_UNCACHE_1; + move_address(&newdoc, &curdoc); + LYFreePostData(&newdoc); + FREE(newdoc.bookmark); + newdoc.isHEAD = FALSE; + newdoc.safe = FALSE; + newdoc.line = curdoc.line; + newdoc.link = curdoc.link > -1 ? curdoc.link : 0; + LYclear(); + } + } +} +#endif /* DIRED_SUPPORT */ + +static void handle_LYK_DEL_BOOKMARK(BOOLEAN *refresh_screen, + int *old_c, + int real_c) +{ + if (curdoc.bookmark != NULL) { + if (HTConfirmDefault(CONFIRM_BOOKMARK_DELETE, NO) != YES) + return; + remove_bookmark_link(links[curdoc.link].anchor_number - 1, + curdoc.bookmark); + } else { /* behave like REFRESH for backward compatibility */ + *refresh_screen = TRUE; + if (*old_c != real_c) { + *old_c = real_c; + lynx_force_repaint(); + } + return; + } + do_cleanup_after_delete(); +} + +#if defined(DIRED_SUPPORT) || defined(VMS) +static void handle_LYK_DIRED_MENU(BOOLEAN *refresh_screen, + int *old_c GCC_UNUSED, + int real_c GCC_UNUSED) +{ +#ifdef VMS + char *cp, *temp = 0; + const char *test = HTGetProgramPath(ppCSWING); + + /* + * Check if the CSwing Directory/File Manager is available. Will be + * disabled if CSWING path is NULL, zero-length, or "none" (case + * insensitive), if no_file_url was set via the file_url restriction, if + * no_goto_file was set for the anonymous account, or if HTDirAccess was + * set to HT_DIR_FORBID or HT_DIR_SELECTIVE via the -nobrowse or -selective + * switches. - FM + */ + if (isEmpty(test) || + !strcasecomp(test, "none") || + no_file_url || no_goto_file || + HTDirAccess == HT_DIR_FORBID || + HTDirAccess == HT_DIR_SELECTIVE) { + if (*old_c != real_c) { + *old_c = real_c; + HTUserMsg(DFM_NOT_AVAILABLE); + } + return; + } + + /* + * If we are viewing a local directory listing or a local file which is not + * temporary, invoke CSwing with the URL's directory converted to VMS path + * specs and passed as the argument, so we start up CSwing positioned on + * that node of the directory tree. Otherwise, pass the current default + * directory as the argument. - FM + */ + if (LYisLocalFile(curdoc.address) && + strncasecomp(curdoc.address, + lynx_temp_space, strlen(lynx_temp_space))) { + /* + * We are viewing a local directory or a local file which is not + * temporary. - FM + */ + struct stat stat_info; + + cp = HTParse(curdoc.address, "", PARSE_PATH | PARSE_PUNCTUATION); + HTUnEscape(cp); + if (HTStat(cp, &stat_info) == -1) { + CTRACE((tfp, "mainloop: Can't stat %s\n", cp)); + FREE(cp); + HTSprintf0(&temp, "%s []", HTGetProgramPath(ppCSWING)); + *refresh_screen = TRUE; /* redisplay */ + } else { + char *VMSdir = NULL; + + if (S_ISDIR(stat_info.st_mode)) { + /* + * We're viewing a local directory. Make that the CSwing + * argument. - FM + */ + LYAddPathSep(&cp); + StrAllocCopy(VMSdir, HTVMS_name("", cp)); + FREE(cp); + } else { + /* + * We're viewing a local file. Make its directory the CSwing + * argument. - FM + */ + StrAllocCopy(VMSdir, HTVMS_name("", cp)); + FREE(cp); + if ((cp = strrchr(VMSdir, ']')) != NULL) { + *(cp + 1) = '\0'; + } else if ((cp = strrchr(VMSdir, ':')) != NULL) { + *(cp + 1) = '\0'; + } + } + HTSprintf0(&temp, "%s %s", HTGetProgramPath(ppCSWING), VMSdir); + FREE(VMSdir); + /* + * Uncache the current document in case we change, move, or delete + * it during the CSwing session. - FM + */ + /* could use DIRED_UNCACHE_1 but it's currently only defined + for dired - kw */ + HTuncache_current_document(); + move_address(&newdoc, &curdoc); + StrAllocCopy(newdoc.title, NonNull(curdoc.title)); + StrAllocCopy(newdoc.bookmark, curdoc.bookmark); + newdoc.line = curdoc.line; + newdoc.link = curdoc.link; + } + } else { + /* + * We're not viewing a local directory or file. Pass CSwing the + * current default directory as an argument and don't uncache the + * current document. - FM + */ + HTSprintf0(&temp, "%s []", HTGetProgramPath(ppCSWING)); + *refresh_screen = TRUE; /* redisplay */ + } + stop_curses(); + LYSystem(temp); + start_curses(); + FREE(temp); +#else + /* + * Don't do if not allowed or already viewing the menu. + */ + if (lynx_edit_mode && !no_dired_support && + !LYIsUIPage(curdoc.address, UIP_DIRED_MENU) && + strcmp(NonNull(curdoc.title), DIRED_MENU_TITLE)) { + dired_options(&curdoc, &newdoc.address); + *refresh_screen = TRUE; /* redisplay */ + } +#endif /* VMS */ +} +#endif /* defined(DIRED_SUPPORT) || defined(VMS) */ + +static int handle_LYK_DOWNLOAD(int *cmd, + int *old_c, + int real_c) +{ + + /* + * Don't do if both download and disk_save are restricted. + */ + if (LYValidate || + (no_download && !override_no_download && no_disk_save)) { + if (*old_c != real_c) { + *old_c = real_c; + HTUserMsg(DOWNLOAD_DISABLED); + } + return 0; + } + + /* + * Don't do if already viewing download options page. + */ + if (LYIsUIPage(curdoc.address, UIP_DOWNLOAD_OPTIONS)) + return 0; + + if (do_change_link() == -1) + return 1; /* mouse stuff was confused, ignore - kw */ + if (nlinks > 0) { + if (links[curdoc.link].type == WWW_FORM_LINK_TYPE) { + if (links[curdoc.link].l_form->type == F_SUBMIT_TYPE || + links[curdoc.link].l_form->type == F_IMAGE_SUBMIT_TYPE || + links[curdoc.link].l_form->type == F_TEXT_SUBMIT_TYPE) { + if (links[curdoc.link].l_form->submit_method == + URL_MAIL_METHOD) { + if (*old_c != real_c) { + *old_c = real_c; + HTUserMsg(NO_DOWNLOAD_MAILTO_ACTION); + } + return 0; + } + if (isEmpty(links[curdoc.link].l_form->submit_action) || + isLYNXOPTIONS(links[curdoc.link].l_form->submit_action)) { + if (*old_c != real_c) { + *old_c = real_c; + HTUserMsg(NO_DOWNLOAD_SPECIAL); + } + return 0; + } + HTOutputFormat = WWW_DOWNLOAD; + LYforce_no_cache = TRUE; + *cmd = LYK_ACTIVATE; + return 2; + } + if (*old_c != real_c) { + *old_c = real_c; + HTUserMsg(NO_DOWNLOAD_INPUT); + } + + } else if (isLYNXCOOKIE(curdoc.address)) { + if (*old_c != real_c) { + *old_c = real_c; + HTUserMsg(NO_DOWNLOAD_COOKIES); + } + } else if (LYIsUIPage(curdoc.address, UIP_PRINT_OPTIONS)) { + if (*old_c != real_c) { + *old_c = real_c; + HTUserMsg(NO_DOWNLOAD_PRINT_OP); + } +#ifdef DIRED_SUPPORT + } else if (LYIsUIPage(curdoc.address, UIP_UPLOAD_OPTIONS)) { + if (*old_c != real_c) { + *old_c = real_c; + HTUserMsg(NO_DOWNLOAD_UPLOAD_OP); + } + + } else if (LYIsUIPage(curdoc.address, UIP_PERMIT_OPTIONS)) { + if (*old_c != real_c) { + *old_c = real_c; + HTUserMsg(NO_DOWNLOAD_PERMIT_OP); + } + + } else if (lynx_edit_mode && !no_dired_support && + !LYstrstr(links[curdoc.link].lname, "/SugFile=")) { + /* + * Don't bother making a /tmp copy of the local file. + */ + static DocInfo temp; + + copy_address(&temp, &newdoc); + set_address(&newdoc, links[curdoc.link].lname); + if (LYdownload_options(&newdoc.address, + links[curdoc.link].lname) < 0) + copy_address(&newdoc, &temp); + else + newdoc.internal_link = FALSE; + LYFreeDocInfo(&temp); +#endif /* DIRED_SUPPORT */ + + } else if (LYIsUIPage(curdoc.address, UIP_HISTORY) && + isLYNXHIST(links[curdoc.link].lname)) { + int number = atoi(links[curdoc.link].lname + LEN_LYNXHIST); + + if (number >= nhist || number < 0) { + HTUserMsg(NO_DOWNLOAD_SPECIAL); + return 0; + } + if ((HDOC(number).post_data != NULL && + HDOC(number).safe != TRUE) && + HTConfirm(CONFIRM_POST_RESUBMISSION) == FALSE) { + HTInfoMsg(CANCELLED); + return 0; + } + /* + * OK, we download from history page, restore URL from stack. + */ + copy_address(&newdoc, &HDOC(number)); + StrAllocCopy(newdoc.title, LYGetHiliteStr(curdoc.link, 0)); + StrAllocCopy(newdoc.bookmark, HDOC(number).bookmark); + LYFreePostData(&newdoc); + if (HDOC(number).post_data) + BStrCopy(newdoc.post_data, + HDOC(number).post_data); + if (HDOC(number).post_content_type) + StrAllocCopy(newdoc.post_content_type, + HDOC(number).post_content_type); + newdoc.isHEAD = HDOC(number).isHEAD; + newdoc.safe = HDOC(number).safe; + newdoc.internal_link = FALSE; + newdoc.link = (user_mode == NOVICE_MODE) ? 1 : 0; + HTOutputFormat = WWW_DOWNLOAD; + LYUserSpecifiedURL = TRUE; + /* + * Force the document to be reloaded. + */ + LYforce_no_cache = TRUE; + + } else if (!StrNCmp(links[curdoc.link].lname, "data:", 5)) { + if (*old_c != real_c) { + *old_c = real_c; + HTAlert(UNSUPPORTED_DATA_URL); + } + + } else if (isLYNXCOOKIE(links[curdoc.link].lname) || + isLYNXCACHE(links[curdoc.link].lname) || + isLYNXDIRED(links[curdoc.link].lname) || + isLYNXDOWNLOAD(links[curdoc.link].lname) || + isLYNXPRINT(links[curdoc.link].lname) || + isLYNXOPTIONS(links[curdoc.link].lname) || + isLYNXHIST(links[curdoc.link].lname) || + /* handled above if valid - kw */ +/* @@@ should next two be downloadable? - kw */ + isLYNXHIST(links[curdoc.link].lname) || + isLYNXCFLAGS(links[curdoc.link].lname) || + isLYNXEXEC(links[curdoc.link].lname) || + isLYNXPROG(links[curdoc.link].lname)) { + HTUserMsg(NO_DOWNLOAD_SPECIAL); + + } else if (isMAILTO_URL(links[curdoc.link].lname)) { + HTUserMsg(NO_DOWNLOAD_MAILTO_LINK); + + /* + * From here on we could have a remote host, so check if that's + * allowed. + * + * We copy all these checks from getfile() to LYK_DOWNLOAD here + * because LYNXDOWNLOAD:// will NOT be pushing the previous + * document into the history stack so preserve getfile() from + * returning a wrong status (NULLFILE). + */ + } else if (local_host_only && + !(LYisLocalHost(links[curdoc.link].lname) || + LYisLocalAlias(links[curdoc.link].lname))) { + HTUserMsg(ACCESS_ONLY_LOCALHOST); + } else { /* Not a forms, options or history link */ + /* + * Follow a normal link or anchor. Note that if it's an anchor + * within the same document, entire document will be downloaded. + */ + set_address(&newdoc, links[curdoc.link].lname); + StrAllocCopy(newdoc.title, LYGetHiliteStr(curdoc.link, 0)); + /* + * Might be an internal link in the same doc from a POST form. If + * so, don't free the content. - kw + */ + if (track_internal_links) { + if (links[curdoc.link].type != WWW_INTERN_LINK_TYPE) { + LYFreePostData(&newdoc); + FREE(newdoc.bookmark); + newdoc.isHEAD = FALSE; + newdoc.safe = FALSE; + } + } else { + /* + * Might be an anchor in the same doc from a POST form. If so, + * don't free the content. -- FM + */ + if (are_different(&curdoc, &newdoc)) { + LYFreePostData(&newdoc); + FREE(newdoc.bookmark); + newdoc.isHEAD = FALSE; + newdoc.safe = FALSE; + } + } + newdoc.internal_link = FALSE; + newdoc.link = (user_mode == NOVICE_MODE) ? 1 : 0; + HTOutputFormat = WWW_DOWNLOAD; + /* + * Force the document to be reloaded. + */ + LYforce_no_cache = TRUE; + } + } else if (*old_c != real_c) { + *old_c = real_c; + HTUserMsg(NO_DOWNLOAD_CHOICE); + } + return 0; +} + +static void handle_LYK_DOWN_xxx(int *old_c, + int real_c, + int scroll_by) +{ + int i; + + if (more_text) { + LYChgNewline(scroll_by); + if (nlinks > 0 && curdoc.link > -1 && + links[curdoc.link].ly > scroll_by) { + newdoc.link = curdoc.link; + for (i = 0; links[i].ly <= scroll_by; i++) + --newdoc.link; + } + } else if (*old_c != real_c) { + HandleForwardWraparound(); + } +} + +static void handle_LYK_DOWN_HALF(int *old_c, + int real_c) +{ + handle_LYK_DOWN_xxx(old_c, real_c, display_lines / 2); +} + +static void handle_LYK_DOWN_LINK(int *follow_col, + int *old_c, + int real_c) +{ + if (curdoc.link < (nlinks - 1)) { /* more links? */ + int newlink; + + if (*follow_col == -1) { + const char *text = LYGetHiliteStr(curdoc.link, 0); + + *follow_col = links[curdoc.link].lx; + + if (text != NULL) + *follow_col += (int) strlen(text) / 2; + } + + newlink = find_link_near_col(*follow_col, 1); + if (newlink > -1) { + set_curdoc_link(newlink); + } else if (more_text) { /* next page */ + LYChgNewline(display_lines); + } else if (*old_c != real_c) { + *old_c = real_c; + HTUserMsg(NO_LINKS_BELOW); + return; + } + } else if (more_text) { /* next page */ + LYChgNewline(display_lines); + } else if (*old_c != real_c) { + HandleForwardWraparound(); + } +} + +static void handle_LYK_DOWN_TWO(int *old_c, + int real_c) +{ + handle_LYK_DOWN_xxx(old_c, real_c, 2); +} + +static int handle_LYK_DWIMEDIT(int *cmd, + int *old_c, + int real_c) +{ +#ifdef TEXTAREA_AUTOEXTEDIT + /* + * If we're in a forms TEXTAREA, invoke the editor on *its* contents, + * rather than attempting to edit the html source document. KED + */ + if (nlinks > 0 && + LinkIsTextarea(curdoc.link)) { + *cmd = LYK_EDITTEXTAREA; + return 2; + } + + /* + * If we're in a forms TEXT type, tell user the request is bogus (though in + * reality, without this trap, if the document with the TEXT field is + * local, the editor *would* be invoked on the source .html file; eg, the + * o(ptions) form tempfile). + * + * [This is done to avoid possible user confusion, due to auto invocation + * of the editor on the TEXTAREA's contents via the above if() statement.] + */ + if (nlinks > 0 && + links[curdoc.link].type == WWW_FORM_LINK_TYPE && + links[curdoc.link].l_form->type == F_TEXT_TYPE) { + HTUserMsg(CANNOT_EDIT_FIELD); + return 1; + } + + if (no_editor) { + if (*old_c != real_c) { + *old_c = real_c; + HTUserMsg(ANYEDIT_DISABLED); + } + return 1; + } +#endif /* TEXTAREA_AUTOEXTEDIT */ + return 0; +} + +static int handle_LYK_ECGOTO(int *ch, + bstring **user_input, + char **old_user_input, + int *old_c, + int real_c) +{ + if (no_goto && !LYValidate) { + /* + * Go to not allowed. - FM + */ + if (*old_c != real_c) { + *old_c = real_c; + HTUserMsg(GOTO_DISALLOWED); + } + return 0; + } +#ifdef DIRED_SUPPORT + if (LYIsUIPage(curdoc.address, UIP_DIRED_MENU) || + LYIsUIPage(curdoc.address, UIP_PERMIT_OPTIONS) || + LYIsUIPage(curdoc.address, UIP_UPLOAD_OPTIONS)) { + /* + * Disallow editing of File Management URLs. - FM + */ + if (*old_c != real_c) { + *old_c = real_c; + HTUserMsg(EDIT_FM_MENU_URLS_DISALLOWED); + } + return 0; + } +#endif /* DIRED_SUPPORT */ + + /* + * Save the current user_input string, and load the current + * document's address. + */ + StrAllocCopy(*old_user_input, (*user_input)->str); + BStrCopy0((*user_input), curdoc.address); + + /* + * Warn the user if the current document has POST data associated with it. + * - FM + */ + if (curdoc.post_data) + HTAlert(CURRENT_DOC_HAS_POST_DATA); + + /* + * Offer the current document's URL for editing. - FM + */ + _statusline(EDIT_CURDOC_URL); + if (((*ch = LYgetBString(user_input, FALSE, 0, RECALL_URL)) >= 0) && + !isBEmpty(*user_input) && + strcmp((*user_input)->str, curdoc.address)) { + LYTrimAllStartfile((*user_input)->str); + if (!isBEmpty(*user_input)) { + return 2; + } + } + /* + * User cancelled via ^G, a full deletion, or not modifying the URL. - FM + */ + HTInfoMsg(CANCELLED); + BStrCopy0((*user_input), *old_user_input); + FREE(*old_user_input); + return 0; +} + +static void handle_LYK_EDIT(int *old_c, + int real_c) +{ +#ifdef DIRED_SUPPORT + char *cp; + char *tp = NULL; + struct stat dir_info; +#endif /* DIRED_SUPPORT */ + + if (no_editor) { + if (*old_c != real_c) { + *old_c = real_c; + HTUserMsg(EDIT_DISABLED); + } + } +#ifdef DIRED_SUPPORT + /* + * Allow the user to edit the link rather than curdoc in edit mode. + */ + else if (lynx_edit_mode && + non_empty(editor) && !no_dired_support) { + if (nlinks > 0) { + cp = links[curdoc.link].lname; + if (is_url(cp) == FILE_URL_TYPE) { + cp = HTfullURL_toFile(cp); + StrAllocCopy(tp, cp); + FREE(cp); + + if (stat(tp, &dir_info) == -1) { + HTAlert(NO_STATUS); + } else { + if (S_ISREG(dir_info.st_mode)) { + StrAllocCopy(tp, links[curdoc.link].lname); + HTUnEscapeSome(tp, "/"); + if (edit_current_file(tp, curdoc.link, -1)) { + DIRED_UNCACHE_1; + move_address(&newdoc, &curdoc); +#ifdef NO_SEEK_OLD_POSITION + /* + * Go to top of file. + */ + newdoc.line = 1; + newdoc.link = 0; +#else + /* + * Seek old position, which probably changed. + */ + newdoc.line = curdoc.line; + newdoc.link = curdoc.link; +#endif /* NO_SEEK_OLD_POSITION */ + LYclear(); /* clear the screen */ + } + } + } + FREE(tp); + } + } + } +#endif /* DIRED_SUPPORT */ + else if (non_empty(editor)) { + if (edit_current_file(newdoc.address, curdoc.link, LYGetNewline())) { + HTuncache_current_document(); + LYforce_no_cache = TRUE; /*force reload of document */ + free_address(&curdoc); /* so it doesn't get pushed */ +#ifdef NO_SEEK_OLD_POSITION + /* + * Go to top of file. + */ + newdoc.line = 1; + newdoc.link = 0; +#else + /* + * Seek old position, which probably changed. + */ + newdoc.line = curdoc.line; + newdoc.link = curdoc.link; +#endif /* NO_SEEK_OLD_POSITION */ + LYclear(); /* clear the screen */ + } + + } else { + if (*old_c != real_c) { + *old_c = real_c; + HTUserMsg(NO_EDITOR); + } + } +} + +static void handle_LYK_DWIMHELP(const char **cshelpfile) +{ + /* + * Currently a help file different from the main 'helpfile' is shown only + * if current link is a text input form field. - kw + */ + if (curdoc.link >= 0 && curdoc.link < nlinks && + !FormIsReadonly(links[curdoc.link].l_form) && + LinkIsTextLike(curdoc.link)) { + *cshelpfile = STR_LYNXEDITMAP; + } +} + +static void handle_LYK_EDITMAP(int *old_c, + int real_c) +{ + if (*old_c != real_c) { + *old_c = real_c; + set_address(&newdoc, STR_LYNXEDITMAP); + StrAllocCopy(newdoc.title, CURRENT_EDITMAP_TITLE); + LYFreePostData(&newdoc); + FREE(newdoc.bookmark); + newdoc.isHEAD = FALSE; + newdoc.safe = FALSE; + newdoc.internal_link = FALSE; +#if defined(DIRED_SUPPORT) && defined(OK_OVERRIDE) + /* + * Remember whether we are in dired menu so we can display the right + * keymap. + */ + if (!no_dired_support) { + prev_lynx_edit_mode = lynx_edit_mode; + } +#endif /* DIRED_SUPPORT && OK_OVERRIDE */ + LYforce_no_cache = TRUE; + } +} + +static void handle_LYK_EDIT_TEXTAREA(BOOLEAN *refresh_screen, + int *old_c, + int real_c) +{ + if (no_editor) { + if (*old_c != real_c) { + *old_c = real_c; + HTUserMsg(ANYEDIT_DISABLED); + } + } else if (isEmpty(editor)) { + if (*old_c != real_c) { + *old_c = real_c; + HTUserMsg(NO_EDITOR); + } + } else if (LinkIsTextarea(curdoc.link)) { + /* + * if the current link is in a form TEXTAREA, it requires handling + * for the possible multiple lines. + */ + + /* stop screen */ + stop_curses(); + + (void) HText_EditTextArea(&links[curdoc.link]); + + /* + * TODO: + * Move cursor "n" lines from the current line to position it on the + * 1st trailing blank line in the now edited TEXTAREA. If the target + * line/ anchor requires us to scroll up/down, position the target in + * the approximate center of the screen. + */ + + /* curdoc.link += n; */ + /* works, except for page crossing, */ + /* damnit; why is nothing ever easy */ + + /* start screen */ + start_curses(); + *refresh_screen = TRUE; + + } else if (LinkIsTextLike(curdoc.link)) { + /* + * other text fields are single-line + */ + stop_curses(); + HText_EditTextField(&links[curdoc.link]); + start_curses(); + *refresh_screen = TRUE; + } else { + + HTInfoMsg(NOT_IN_TEXTAREA_NOEDIT); + } +} + +static int handle_LYK_ELGOTO(int *ch, + bstring **user_input, + char **old_user_input, + int *old_c, + int real_c) +{ + if (no_goto && !LYValidate) { + /* + * Go to not allowed. - FM + */ + if (*old_c != real_c) { + *old_c = real_c; + HTUserMsg(GOTO_DISALLOWED); + } + return 0; + } + if (!(nlinks > 0 && curdoc.link > -1) || + (links[curdoc.link].type == WWW_FORM_LINK_TYPE && + links[curdoc.link].l_form->type != F_SUBMIT_TYPE && + links[curdoc.link].l_form->type != F_IMAGE_SUBMIT_TYPE && + links[curdoc.link].l_form->type != F_TEXT_SUBMIT_TYPE)) { + /* + * No links on page, or not a normal link or form submit button. - FM + */ + if (*old_c != real_c) { + *old_c = real_c; + HTUserMsg(NOT_ON_SUBMIT_OR_LINK); + } + return 0; + } + if ((links[curdoc.link].type == WWW_FORM_LINK_TYPE) && + (isEmpty(links[curdoc.link].l_form->submit_action))) { + /* + * Form submit button with no ACTION defined. - FM + */ + if (*old_c != real_c) { + *old_c = real_c; + HTUserMsg(NO_FORM_ACTION); + } + return 0; + } +#ifdef DIRED_SUPPORT + if (isLYNXDIRED(links[curdoc.link].lname) || + LYIsUIPage(curdoc.address, UIP_DIRED_MENU) || + LYIsUIPage(curdoc.address, UIP_PERMIT_OPTIONS) || + LYIsUIPage(curdoc.address, UIP_UPLOAD_OPTIONS)) { + /* + * Disallow editing of File Management URLs. - FM + */ + if (*old_c != real_c) { + *old_c = real_c; + HTUserMsg(EDIT_FM_MENU_URLS_DISALLOWED); + } + return 0; + } +#endif /* DIRED_SUPPORT */ + + /* + * Save the current user_input string, and load the current link's + * address. - FM + */ + StrAllocCopy(*old_user_input, (*user_input)->str); + BStrCopy0((*user_input), + ((links[curdoc.link].type == WWW_FORM_LINK_TYPE) + ? links[curdoc.link].l_form->submit_action + : links[curdoc.link].lname)); + /* + * Offer the current link's URL for editing. - FM + */ + _statusline(EDIT_CURLINK_URL); + if (((*ch = LYgetBString(user_input, FALSE, 0, RECALL_URL)) >= 0) && + !isBEmpty(*user_input) && + strcmp((*user_input)->str, + ((links[curdoc.link].type == WWW_FORM_LINK_TYPE) + ? links[curdoc.link].l_form->submit_action + : links[curdoc.link].lname))) { + LYTrimAllStartfile((*user_input)->str); + if (!isBEmpty(*user_input)) { + return 2; + } + } + /* + * User cancelled via ^G, a full deletion, or not modifying the URL. - FM + */ + HTInfoMsg(CANCELLED); + BStrCopy0((*user_input), *old_user_input); + FREE(*old_user_input); + return 0; +} + +#ifdef USE_EXTERNALS +static void handle_LYK_EXTERN_LINK(BOOLEAN *refresh_screen) +{ + if ((nlinks > 0) && (links[curdoc.link].lname != NULL)) { + run_external(links[curdoc.link].lname, FALSE); + *refresh_screen = TRUE; + } +} + +static void handle_LYK_EXTERN_PAGE(BOOLEAN *refresh_screen) +{ + if (curdoc.address != NULL) { + run_external(curdoc.address, FALSE); + *refresh_screen = TRUE; + } +} +#endif + +static BOOLEAN handle_LYK_FASTBACKW_LINK(int *cmd, + int *old_c, + int real_c) +{ + int samepage = 0, nextlink = curdoc.link; + int res; + BOOLEAN code = FALSE; + + if (nlinks > 1) { + + /* + * If in textarea, move to first link or textarea group before it if + * there is one on this screen. - kw + */ + if (LinkIsTextarea(curdoc.link)) { + int thisgroup = links[curdoc.link].l_form->number; + char *thisname = links[curdoc.link].l_form->name; + + if (curdoc.link > 0 && + !(LinkIsTextarea(0) && + links[0].l_form->number == thisgroup && + sametext(links[0].l_form->name, thisname))) { + do + nextlink--; + while + (LinkIsTextarea(nextlink) && + links[nextlink].l_form->number == thisgroup && + sametext(links[nextlink].l_form->name, thisname)); + samepage = 1; + + } else if (!more_text && LYGetNewline() == 1 && + (LinkIsTextarea(0) && + links[0].l_form->number == thisgroup && + sametext(links[0].l_form->name, thisname)) && + !(LinkIsTextarea(nlinks - 1) && + links[nlinks - 1].l_form->number == thisgroup && + sametext(links[nlinks - 1].l_form->name, thisname))) { + nextlink = nlinks - 1; + samepage = 1; + + } else if (!more_text && LYGetNewline() == 1 && curdoc.link > 0) { + nextlink = 0; + samepage = 1; + } + } else if (curdoc.link > 0) { + nextlink--; + samepage = 1; + } else if (!more_text && LYGetNewline() == 1) { + nextlink = nlinks - 1; + samepage = 1; + } + } + + if (samepage) { + /* + * If the link as determined so far is part of a group of textarea + * fields, try to use the first of them that's on the screen instead. + * - kw + */ + if (nextlink > 0 && + LinkIsTextarea(nextlink)) { + int thisgroup = links[nextlink].l_form->number; + char *thisname = links[nextlink].l_form->name; + + if (LinkIsTextarea(0) && + links[0].l_form->number == thisgroup && + sametext(links[0].l_form->name, thisname)) { + nextlink = 0; + } else + while + (nextlink > 1 && + LinkIsTextarea(nextlink - 1) && + links[nextlink - 1].l_form->number == thisgroup && + sametext(links[nextlink - 1].l_form->name, thisname)) { + nextlink--; + } + } + set_curdoc_link(nextlink); + + } else if (LYGetNewline() > 1 && /* need a previous page */ + (res = HTGetLinkOrFieldStart(curdoc.link, + &Newline, &newdoc.link, + -1, TRUE)) != NO) { + if (res == LINK_DO_ARROWUP) { + /* + * It says we should use the normal PREV_LINK mechanism, so we'll + * do that. - kw + */ + if (nlinks > 0) + curdoc.link = 0; + *cmd = LYK_PREV_LINK; + code = TRUE; + } else { + LYChgNewline(1); /* our line counting starts with 1 not 0 */ + } + } else if (*old_c != real_c) { + *old_c = real_c; + HTInfoMsg(NO_LINKS_ABOVE); + } + return code; +} + +static void handle_LYK_FASTFORW_LINK(int *old_c, + int real_c) +{ + int samepage = 0, nextlink = curdoc.link; + + if (nlinks > 1) { + + /* + * If in textarea, move to first link or field after it if there is one + * on this screen. - kw + */ + if (LinkIsTextarea(curdoc.link)) { + int thisgroup = links[curdoc.link].l_form->number; + char *thisname = links[curdoc.link].l_form->name; + + if (curdoc.link < nlinks - 1 && + !(LinkIsTextarea(nlinks - 1) && + links[nlinks - 1].l_form->number == thisgroup && + sametext(links[nlinks - 1].l_form->name, thisname))) { + do + nextlink++; + while + (LinkIsTextarea(nextlink) && + links[nextlink].l_form->number == thisgroup && + sametext(links[nextlink].l_form->name, thisname)); + samepage = 1; + } else if (!more_text && LYGetNewline() == 1 && curdoc.link > 0) { + nextlink = 0; + samepage = 1; + } + } else if (curdoc.link < nlinks - 1) { + nextlink++; + samepage = 1; + } else if (!more_text && LYGetNewline() == 1 && curdoc.link > 0) { + nextlink = 0; + samepage = 1; + } + } + + if (samepage) { + set_curdoc_link(nextlink); + } else if (!more_text && LYGetNewline() == 1 && curdoc.link == nlinks - 1) { + /* + * At the bottom of list and there is only one page. Move to the top + * link on the page. + */ + set_curdoc_link(0); + + } else if (more_text && /* need a later page */ + HTGetLinkOrFieldStart(curdoc.link, + &Newline, &newdoc.link, + 1, TRUE) != NO) { + LYChgNewline(1); /* our line counting starts with 1 not 0 */ + /* nothing more to do here */ + + } else if (*old_c != real_c) { + *old_c = real_c; + HTInfoMsg(NO_LINKS_BELOW); + } + return; +} + +static void handle_LYK_FIRST_LINK(void) +{ + int i = curdoc.link; + + for (;;) { + if (--i < 0 + || links[i].ly != links[curdoc.link].ly) { + set_curdoc_link(i + 1); + break; + } + } +} + +static BOOLEAN handle_LYK_GOTO(int *ch, + bstring **user_input, + char **old_user_input, + RecallType * recall, + int *URLTotal, + int *URLNum, + BOOLEAN *FirstURLRecall, + int *old_c, + int real_c) +{ + + if (no_goto && !LYValidate) { + if (*old_c != real_c) { + *old_c = real_c; + HTUserMsg(GOTO_DISALLOWED); + } + return FALSE; + } + + StrAllocCopy(*old_user_input, (*user_input)->str); + if (!goto_buffer) + BStrCopy0((*user_input), ""); + + *URLTotal = (Goto_URLs ? HTList_count(Goto_URLs) : 0); + if (goto_buffer && !isBEmpty(*user_input)) { + *recall = ((*URLTotal > 1) ? RECALL_URL : NORECALL); + *URLNum = 0; + *FirstURLRecall = FALSE; + } else { + *recall = ((*URLTotal >= 1) ? RECALL_URL : NORECALL); + *URLNum = *URLTotal; + *FirstURLRecall = TRUE; + } + + /* + * Ask the user. + */ + _statusline(URL_TO_OPEN); + if ((*ch = LYgetBString(user_input, FALSE, 0, *recall)) < 0) { + /* + * User cancelled the Goto via ^G. Restore user_input and + * break. - FM + */ + BStrCopy0((*user_input), *old_user_input); + FREE(*old_user_input); + HTInfoMsg(CANCELLED); + return FALSE; + } + return TRUE; +} + +static void handle_LYK_GROW_TEXTAREA(BOOLEAN *refresh_screen) +{ + /* + * See if the current link is in a form TEXTAREA. + */ + if (LinkIsTextarea(curdoc.link)) { + + HText_ExpandTextarea(&links[curdoc.link], TEXTAREA_EXPAND_SIZE); + + *refresh_screen = TRUE; + + } else { + + HTInfoMsg(NOT_IN_TEXTAREA); + } +} + +static BOOLEAN handle_LYK_HEAD(int *cmd) +{ + int c; + + if (nlinks > 0 && + (links[curdoc.link].type != WWW_FORM_LINK_TYPE || + links[curdoc.link].l_form->type == F_SUBMIT_TYPE || + links[curdoc.link].l_form->type == F_IMAGE_SUBMIT_TYPE || + links[curdoc.link].l_form->type == F_TEXT_SUBMIT_TYPE)) { + /* + * We have links, and the current link is a normal link or a form's + * submit button. - FM + */ + _statusline(HEAD_D_L_OR_CANCEL); + c = LYgetch_single(); + if (c == 'D') { + char *scheme = !isLYNXIMGMAP(curdoc.address) + ? curdoc.address + : curdoc.address + LEN_LYNXIMGMAP; + + if (LYCanDoHEAD(scheme) != TRUE) { + HTUserMsg(DOC_NOT_HTTP_URL); + } else { + /* + * Check if this is a reply from a POST, and if so, seek + * confirmation if the safe element is not set. - FM + */ + if ((curdoc.post_data != NULL && + curdoc.safe != TRUE) && + HTConfirm(CONFIRM_POST_DOC_HEAD) == FALSE) { + HTInfoMsg(CANCELLED); + } else { + HEAD_request = TRUE; + LYforce_no_cache = TRUE; + StrAllocCopy(newdoc.title, curdoc.title); + if (HTLoadedDocumentIsHEAD()) { + HText_setNoCache(HTMainText); + free_address(&curdoc); + } else { + StrAllocCat(newdoc.title, " - HEAD"); + } + } + } + } else if (c == 'L') { + if (links[curdoc.link].type != WWW_FORM_LINK_TYPE && + StrNCmp(links[curdoc.link].lname, "http", 4) && + StrNCmp(links[curdoc.link].lname, "LYNXIMGMAP:http", 15) && + LYCanDoHEAD(links[curdoc.link].lname) != TRUE && + (links[curdoc.link].type != WWW_INTERN_LINK_TYPE || + !curdoc.address || + StrNCmp(curdoc.address, "http", 4))) { + HTUserMsg(LINK_NOT_HTTP_URL); + } else if (links[curdoc.link].type == WWW_FORM_LINK_TYPE && + FormIsReadonly(links[curdoc.link].l_form)) { + HTUserMsg(FORM_ACTION_DISABLED); + } else if (links[curdoc.link].type == WWW_FORM_LINK_TYPE && + links[curdoc.link].l_form->submit_action != 0 && + !isLYNXCGI(links[curdoc.link].l_form->submit_action) && + StrNCmp(links[curdoc.link].l_form->submit_action, + "http", 4)) { + HTUserMsg(FORM_ACTION_NOT_HTTP_URL); + } else if (links[curdoc.link].type == WWW_FORM_LINK_TYPE && + links[curdoc.link].l_form->submit_method == + URL_POST_METHOD && + HTConfirm(CONFIRM_POST_LINK_HEAD) == FALSE) { + HTInfoMsg(CANCELLED); + } else { + HEAD_request = TRUE; + LYforce_no_cache = TRUE; + *cmd = LYK_ACTIVATE; + return TRUE; + } + } + } else { + /* + * We can offer only this document for a HEAD request. Check if this + * is a reply from a POST, and if so, seek confirmation if the safe + * element is not set. - FM + */ + if ((curdoc.post_data != NULL && + curdoc.safe != TRUE) && + HTConfirm(CONFIRM_POST_DOC_HEAD) == FALSE) { + HTInfoMsg(CANCELLED); + } else { + if (nlinks > 0) { + /* + * The current link is a non-submittable form link, so prompt + * the user to make it clear that the HEAD request would be for + * the current document, not the form link. - FM + */ + _statusline(HEAD_D_OR_CANCEL); + c = LYgetch_single(); + } else { + /* + * No links, so we can just assume that the user wants a HEAD + * request for the current document. - FM + */ + c = 'D'; + } + if (c == 'D') { + char *scheme = !isLYNXIMGMAP(curdoc.address) + ? curdoc.address + : curdoc.address + LEN_LYNXIMGMAP; + + /* + * The user didn't cancel, so check if a HEAD request is + * appropriate for the current document. - FM + */ + if (LYCanDoHEAD(scheme) != TRUE) { + HTUserMsg(DOC_NOT_HTTP_URL); + } else { + HEAD_request = TRUE; + LYforce_no_cache = TRUE; + StrAllocCopy(newdoc.title, curdoc.title); + if (HTLoadedDocumentIsHEAD()) { + HText_setNoCache(HTMainText); + free_address(&curdoc); + } else { + StrAllocCat(newdoc.title, " - HEAD"); + } + } + } + } + } + return FALSE; +} + +static void handle_LYK_HELP(const char **cshelpfile) +{ + char *my_value = NULL; + + if (*cshelpfile == NULL) + *cshelpfile = helpfile; + StrAllocCopy(my_value, *cshelpfile); + LYEnsureAbsoluteURL(&my_value, *cshelpfile, FALSE); + if (!STREQ(curdoc.address, my_value)) { + /* + * Set the filename. + */ + set_address(&newdoc, my_value); + /* + * Make a name for this help file. + */ + StrAllocCopy(newdoc.title, gettext("Help Screen")); + LYFreePostData(&newdoc); + FREE(newdoc.bookmark); + newdoc.isHEAD = FALSE; + newdoc.safe = FALSE; + newdoc.internal_link = FALSE; + } + FREE(my_value); + *cshelpfile = NULL; /* reset pointer - kw */ +} + +static void handle_LYK_HISTORICAL(void) +{ +#ifdef USE_SOURCE_CACHE + if (!HTcan_reparse_document()) { +#endif + /* + * Check if this is a reply from a POST, and if so, seek confirmation + * of reload if the safe element is not set. - FM + */ + if ((curdoc.post_data != NULL && + curdoc.safe != TRUE) && + confirm_post_resub(curdoc.address, NULL, 0, 0) == FALSE) { + HTInfoMsg(WILL_NOT_RELOAD_DOC); + } else { + HText_setNoCache(HTMainText); + move_address(&newdoc, &curdoc); + newdoc.line = curdoc.line; + newdoc.link = curdoc.link; + } +#ifdef USE_SOURCE_CACHE + } /* end if no bypass */ +#endif + historical_comments = (BOOLEAN) !historical_comments; + if (minimal_comments) { + HTAlert(historical_comments ? + HISTORICAL_ON_MINIMAL_OFF : HISTORICAL_OFF_MINIMAL_ON); + } else { + HTAlert(historical_comments ? + HISTORICAL_ON_VALID_OFF : HISTORICAL_OFF_VALID_ON); + } +#ifdef USE_SOURCE_CACHE + (void) reparse_document(); +#endif + return; +} + +static BOOLEAN handle_LYK_HISTORY(int ForcePush) +{ + if (curdoc.title && !LYIsUIPage(curdoc.address, UIP_HISTORY)) { + /* + * Don't do this if already viewing history page. + * + * Push the current file so that the history list contains the current + * file for printing purposes. Pop the file afterwards to prevent + * multiple copies. + */ + if (TRACE && !LYUseTraceLog && LYCursesON) { + LYHideCursor(); /* make sure cursor is down */ +#ifdef USE_SLANG + LYaddstr("\n"); +#endif /* USE_SLANG */ + LYrefresh(); + } + LYpush(&curdoc, ForcePush); + + /* + * Print history options to file. + */ + if (showhistory(&newdoc.address) < 0) { + LYpop(&curdoc); + return TRUE; + } + LYRegisterUIPage(newdoc.address, UIP_HISTORY); + StrAllocCopy(newdoc.title, HISTORY_PAGE_TITLE); + LYFreePostData(&newdoc); + FREE(newdoc.bookmark); + newdoc.isHEAD = FALSE; + newdoc.safe = FALSE; + newdoc.internal_link = FALSE; + newdoc.link = 1; /*@@@ bypass "recent statusline messages" link */ + free_address(&curdoc); /* so it doesn't get pushed */ + + if (LYValidate || check_realm) { + LYPermitURL = TRUE; + } + return TRUE; + } /* end if StrNCmp */ + return FALSE; +} + +static BOOLEAN handle_LYK_IMAGE_TOGGLE(int *cmd) +{ + clickable_images = (BOOLEAN) !clickable_images; + + HTUserMsg(clickable_images ? + CLICKABLE_IMAGES_ON : CLICKABLE_IMAGES_OFF); + return reparse_or_reload(cmd); +} + +static void handle_LYK_INDEX(int *old_c, + int real_c) +{ + /* + * Make sure we are not in the index already. + */ + if (!STREQ(curdoc.address, indexfile)) { + + if (indexfile[0] == '\0') { /* no defined index */ + if (*old_c != real_c) { + *old_c = real_c; + HTUserMsg(NO_INDEX_FILE); + } + + } else { +#ifdef KANJI_CODE_OVERRIDE + if (HTCJK == JAPANESE) { + last_kcode = NOKANJI; /* AUTO */ + } +#endif +#ifdef USE_PROGRAM_DIR + if (is_url(indexfile) == 0) { + char *tmp = NULL; + + HTSprintf0(&tmp, "%s\\%s", program_dir, indexfile); + FREE(indexfile); + LYLocalFileToURL(&indexfile, tmp); + FREE(tmp); + } +#endif + set_address(&newdoc, indexfile); + StrAllocCopy(newdoc.title, gettext("System Index")); /* name it */ + LYFreePostData(&newdoc); + FREE(newdoc.bookmark); + newdoc.isHEAD = FALSE; + newdoc.safe = FALSE; + newdoc.internal_link = FALSE; + } /* end else */ + } /* end if */ +} + +static void handle_LYK_INDEX_SEARCH(BOOLEAN *force_load, + int ForcePush, + int *old_c, + int real_c) +{ + if (is_www_index) { + /* + * Perform a database search. + * + * do_www_search will try to go out and get the document. If it + * returns TRUE, a new document was returned and is named in the + * newdoc.address. + */ + newdoc.isHEAD = FALSE; + newdoc.safe = FALSE; + if (do_www_search(&newdoc) == NORMAL) { + /* + * Yah, the search succeeded. + */ + if (TRACE && !LYUseTraceLog && LYCursesON) { + /* + * Make sure cursor is down. + */ + LYHideCursor(); +#ifdef USE_SLANG + LYaddstr("\n"); +#endif /* USE_SLANG */ + LYrefresh(); + } + LYpush(&curdoc, ForcePush); + /* + * Make the curdoc.address the newdoc.address so that getfile + * doesn't try to get the newdoc.address. Since we have already + * gotten it. + */ + copy_address(&curdoc, &newdoc); + BStrCopy(newdoc.post_data, curdoc.post_data); + StrAllocCopy(newdoc.post_content_type, curdoc.post_content_type); + newdoc.internal_link = FALSE; + curdoc.line = -1; + LYSetNewline(0); + } else if (use_this_url_instead != NULL) { + /* + * Got back a redirecting URL. Check it out. + */ + HTUserMsg2(WWW_USING_MESSAGE, use_this_url_instead); + + /* + * Make a name for this URL. + */ + StrAllocCopy(newdoc.title, + "A URL specified by redirection"); + set_address(&newdoc, use_this_url_instead); + LYFreePostData(&newdoc); + FREE(newdoc.bookmark); + newdoc.isHEAD = FALSE; + newdoc.safe = FALSE; + newdoc.internal_link = FALSE; + FREE(use_this_url_instead); + *force_load = TRUE; + } else { + /* + * Yuk, the search failed. Restore the old file. + */ + copy_address(&newdoc, &curdoc); + BStrCopy(newdoc.post_data, curdoc.post_data); + StrAllocCopy(newdoc.post_content_type, + curdoc.post_content_type); + StrAllocCopy(newdoc.bookmark, curdoc.bookmark); + newdoc.isHEAD = curdoc.isHEAD; + newdoc.safe = curdoc.safe; + newdoc.internal_link = curdoc.internal_link; + } + } else if (*old_c != real_c) { + *old_c = real_c; + HTUserMsg(NOT_ISINDEX); + } +} + +static BOOLEAN handle_LYK_INFO(int *cmd) +{ + /* + * Don't do if already viewing info page. + */ + if (!LYIsUIPage(curdoc.address, UIP_SHOWINFO)) { + if (do_change_link() != -1 + && LYShowInfo(&curdoc, &newdoc, owner_address) >= 0) { + LYRegisterUIPage(newdoc.address, UIP_SHOWINFO); + StrAllocCopy(newdoc.title, SHOWINFO_TITLE); + LYFreePostData(&newdoc); + FREE(newdoc.bookmark); + newdoc.isHEAD = FALSE; + newdoc.safe = FALSE; + newdoc.internal_link = FALSE; + LYforce_no_cache = TRUE; + if (LYValidate || check_realm) + LYPermitURL = TRUE; + } + } else { + /* + * If already in info page, get out. + */ + *cmd = LYK_PREV_DOC; + return TRUE; + } + return FALSE; +} + +static BOOLEAN handle_LYK_INLINE_TOGGLE(int *cmd) +{ + pseudo_inline_alts = (BOOLEAN) !pseudo_inline_alts; + + HTUserMsg(pseudo_inline_alts ? + PSEUDO_INLINE_ALTS_ON : PSEUDO_INLINE_ALTS_OFF); + return reparse_or_reload(cmd); +} + +static void handle_LYK_INSERT_FILE(BOOLEAN *refresh_screen, + int *old_c, + int real_c) +{ + /* + * See if the current link is in a form TEXTAREA. + */ + if (LinkIsTextarea(curdoc.link)) { + + /* + * Reject attempts to use this for gaining access to local files when + * such access is restricted: if no_file_url was set via the file_url + * restriction, if no_goto_file was set for the anonymous account, or + * if HTDirAccess was set to HT_DIR_FORBID or HT_DIR_SELECTIVE via the + * -nobrowse or -selective switches, it is assumed that inserting files + * or checking for existence of files needs to be denied. - kw + */ + if (no_file_url || no_goto_file || + HTDirAccess == HT_DIR_FORBID || + HTDirAccess == HT_DIR_SELECTIVE) { + if (*old_c != real_c) { + *old_c = real_c; + if (no_goto_file) + HTUserMsg2(GOTO_XXXX_DISALLOWED, STR_FILE_URL); + else + HTUserMsg(NOAUTH_TO_ACCESS_FILES); + HTInfoMsg(FILE_INSERT_CANCELLED); + } + return; + } + + (void) HText_InsertFile(&links[curdoc.link]); + + /* + * TODO: + * Move cursor "n" lines from the current line to position it on the + * 1st line following the text that was inserted. If the target + * line/anchor requires us to scroll up/down, position the target in + * the approximate center of the screen. + * + * [Current behavior leaves cursor on the same line relative to the + * start of the TEXTAREA that it was on before the insertion. This is + * the same behavior that occurs with (my) editor, so this TODO will + * stay unimplemented.] + */ + + *refresh_screen = TRUE; + + } else { + + HTInfoMsg(NOT_IN_TEXTAREA); + } +} + +#if defined(DIRED_SUPPORT) && defined(OK_INSTALL) +static void handle_LYK_INSTALL(void) +{ + if (lynx_edit_mode && nlinks > 0 && !no_dired_support) + local_install(NULL, links[curdoc.link].lname, &newdoc.address); +} +#endif + +static const char *hexy = "0123456789ABCDEF"; + +#define HEX(n) hexy[(n) & 0xf] +/* + * URL-encode a parameter which can then be appended to a URI. + * RFC-3986 lists reserved characters, which should be encoded. + */ +static char *urlencode(char *str) +{ + char *result = NULL; + char *ptr; + int ch; + + if (str != NULL) { + result = malloc(strlen(str) * 3 + 1); + ptr = result; + + if (result == NULL) + outofmem(__FILE__, "urlencode"); + + while ((ch = UCH(*str++)) != 0) { + if (ch == ' ') { + *ptr = '+'; + ptr++; + } else if (ch > 127 || + StrChr(":/?#[]@!$&'()*+,;=", ch) != 0) { + *ptr++ = '%'; + *ptr++ = HEX(ch >> 4); + *ptr++ = HEX(ch); + } else { + *ptr++ = (char) ch; + } + } + *ptr = '\0'; + } + + return result; +} + +/* + * Fill in "%s" marker(s) in the url_template by prompting the user for the + * values. + */ +static BOOLEAN check_JUMP_param(char **url_template) +{ + int param = 1; + char *subs; + char *result = *url_template; + char *encoded = NULL; + int code = TRUE; + bstring *input = NULL; + + CTRACE((tfp, "check_JUMP_param: %s\n", NONNULL(result))); + + while (result != NULL && (subs = strstr(result, "%s")) != 0) { + char prompt[MAX_LINE]; + RecallType recall = NORECALL; + + CTRACE((tfp, "Prompt for query param%d: %s\n", param, result)); + + sprintf(prompt, gettext("Query parameter %d: "), param++); + statusline(prompt); + BStrCopy0(input, ""); + + if (encoded) + FREE(encoded); + + if (LYgetBString(&input, FALSE, 0, recall) < 0) { + /* + * cancelled via ^G + */ + HTInfoMsg(CANCELLED); + code = FALSE; + break; + } else if ((encoded = urlencode(input->str)) != NULL && *encoded != '\0') { + int subs_at = (int) (subs - result); + int fill_in = (int) strlen(encoded) - 2; + size_t have = strlen(result); + size_t want = strlen(encoded) + have - 1; + int n; + char *update = realloc(result, want + 1); + + if (update == 0) { + HTInfoMsg(NOT_ENOUGH_MEMORY); + code = FALSE; + break; + } + + CTRACE((tfp, " reply: %s\n", input->str)); + CTRACE((tfp, " coded: %s\n", encoded)); + + result = update; + result[want] = '\0'; + for (n = (int) want; (n - fill_in) >= subs_at; --n) { + result[n] = result[n - fill_in]; + } + for (n = subs_at; encoded[n - subs_at] != '\0'; ++n) { + result[n] = encoded[n - subs_at]; + } + CTRACE((tfp, " subst: %s\n", result)); + } else { + HTInfoMsg(CANCELLED); + code = FALSE; + break; + } + } + BStrFree(input); + FREE(encoded); + *url_template = result; + return (BOOLEAN) code; +} + +static void fill_JUMP_Params(char **addressp) +{ + if (LYJumpFileURL) { + check_JUMP_param(addressp); + } +} + +static BOOLEAN handle_LYK_JUMP(int c, + bstring **user_input, + char **old_user_input GCC_UNUSED, + RecallType * recall GCC_UNUSED, + BOOLEAN *FirstURLRecall GCC_UNUSED, + int *URLNum GCC_UNUSED, + int *URLTotal GCC_UNUSED, + int *ch GCC_UNUSED, + int *old_c, + int real_c) +{ + char *ret; + + if (no_jump || JThead == NULL) { + if (*old_c != real_c) { + *old_c = real_c; + if (no_jump) + HTUserMsg(JUMP_DISALLOWED); + else + HTUserMsg(NO_JUMPFILE); + } + } else { + LYJumpFileURL = TRUE; + if ((ret = LYJump(c)) != NULL) { +#ifdef PERMIT_GOTO_FROM_JUMP + if (!strncasecomp(ret, "Go ", 3)) { + LYJumpFileURL = FALSE; + StrAllocCopy(*old_user_input, (*user_input)->str); + *URLTotal = (Goto_URLs ? HTList_count(Goto_URLs) : 0); + *recall = ((*URLTotal >= 1) ? RECALL_URL : NORECALL); + *URLNum = *URLTotal; + *FirstURLRecall = TRUE; + if (!strcasecomp(ret, "Go :")) { + if (recall) { + *ch = UPARROW_KEY; + return TRUE; + } + FREE(*old_user_input); + HTUserMsg(NO_RANDOM_URLS_YET); + return FALSE; + } + ret = HTParse((ret + 3), startfile, PARSE_ALL); + BStrCopy0((*user_input), ret); + FREE(ret); + return TRUE; + } +#endif /* PERMIT_GOTO_FROM_JUMP */ + ret = HTParse(ret, startfile, PARSE_ALL); + if (!LYTrimStartfile(ret)) { + LYRemoveBlanks((*user_input)->str); + } + if (check_JUMP_param(&ret)) { + set_address(&newdoc, ret); + StrAllocCopy(lynxjumpfile, ret); + LYFreePostData(&newdoc); + FREE(newdoc.bookmark); + newdoc.isHEAD = FALSE; + newdoc.safe = FALSE; + newdoc.internal_link = FALSE; + LYUserSpecifiedURL = TRUE; + } + FREE(ret); + } else { + LYJumpFileURL = FALSE; + } + } + return FALSE; +} + +static void handle_LYK_KEYMAP(BOOLEAN *vi_keys_flag, + BOOLEAN *emacs_keys_flag, + int *old_c, + int real_c) +{ + if (*old_c != real_c) { + *old_c = real_c; + set_address(&newdoc, STR_LYNXKEYMAP); + StrAllocCopy(newdoc.title, CURRENT_KEYMAP_TITLE); + LYFreePostData(&newdoc); + FREE(newdoc.bookmark); + newdoc.isHEAD = FALSE; + newdoc.safe = FALSE; + newdoc.internal_link = FALSE; + /* + * If vi_keys changed, the keymap did too, so force no cache, and reset + * the flag. - FM + */ + if (*vi_keys_flag != vi_keys || + *emacs_keys_flag != emacs_keys) { + LYforce_no_cache = TRUE; + *vi_keys_flag = vi_keys; + *emacs_keys_flag = emacs_keys; + } +#if defined(DIRED_SUPPORT) && defined(OK_OVERRIDE) + /* + * Remember whether we are in dired menu so we can display the right + * keymap. + */ + if (!no_dired_support) { + prev_lynx_edit_mode = lynx_edit_mode; + } +#endif /* DIRED_SUPPORT && OK_OVERRIDE */ + LYforce_no_cache = TRUE; + } +} + +static void handle_LYK_LAST_LINK(void) +{ + int i = curdoc.link; + + for (;;) { + if (++i >= nlinks + || links[i].ly != links[curdoc.link].ly) { + set_curdoc_link(i - 1); + break; + } + } +} + +static void handle_LYK_LEFT_LINK(void) +{ + if (curdoc.link > 0 && + links[curdoc.link].ly == links[curdoc.link - 1].ly) { + set_curdoc_link(curdoc.link - 1); + } +} + +static BOOLEAN handle_LYK_LIST(int *cmd) +{ + /* + * Don't do if already viewing list page. + */ + if (!strcmp(NonNull(curdoc.title), LIST_PAGE_TITLE) && + LYIsUIPage(curdoc.address, UIP_LIST_PAGE)) { + /* + * Already viewing list page, so get out. + */ + *cmd = LYK_PREV_DOC; + return TRUE; + } + + /* + * Print list page to file. + */ + if (showlist(&newdoc, TRUE) < 0) + return FALSE; + StrAllocCopy(newdoc.title, LIST_PAGE_TITLE); + /* + * showlist will set newdoc's other fields. It may leave post_data intact + * so the list can be used to follow internal links in the current document + * even if it is a POST response. - kw + */ + + if (LYValidate || check_realm) { + LYPermitURL = TRUE; + StrAllocCopy(lynxlistfile, newdoc.address); + } + return FALSE; +} + +static void handle_LYK_MAIN_MENU(int *old_c, + int real_c) +{ + /* + * If its already the homepage then don't reload it. + */ + if (!STREQ(curdoc.address, homepage)) { + + if (HTConfirmDefault(CONFIRM_MAIN_SCREEN, NO) == YES) { + set_address(&newdoc, homepage); + StrAllocCopy(newdoc.title, gettext("Entry into main screen")); + LYFreePostData(&newdoc); + FREE(newdoc.bookmark); + newdoc.isHEAD = FALSE; + newdoc.safe = FALSE; + newdoc.internal_link = FALSE; + LYhighlight(FALSE, curdoc.link, prev_target->str); +#ifdef DIRED_SUPPORT + if (lynx_edit_mode) { + DIRED_UNCACHE_2; + } +#endif /* DIRED_SUPPORT */ + } + } else { + if (*old_c != real_c) { + *old_c = real_c; + HTUserMsg(IN_MAIN_SCREEN); + } + } +} + +static void handle_LYK_MINIMAL(void) +{ + if (!historical_comments) { +#ifdef USE_SOURCE_CACHE + if (!HTcan_reparse_document()) { +#endif + /* + * Check if this is a reply from a POST, and if so, seek + * confirmation of reload if the safe element is not set. - FM + */ + if ((curdoc.post_data != NULL && + curdoc.safe != TRUE) && + confirm_post_resub(curdoc.address, NULL, 0, 0) == FALSE) { + HTInfoMsg(WILL_NOT_RELOAD_DOC); + } else { + HText_setNoCache(HTMainText); + move_address(&newdoc, &curdoc); + newdoc.line = curdoc.line; + newdoc.link = curdoc.link; + } +#ifdef USE_SOURCE_CACHE + } /* end if no bypass */ +#endif + } + minimal_comments = (BOOLEAN) !minimal_comments; + if (!historical_comments) { + HTAlert(minimal_comments ? + MINIMAL_ON_IN_EFFECT : MINIMAL_OFF_VALID_ON); + } else { + HTAlert(minimal_comments ? + MINIMAL_ON_BUT_HISTORICAL : MINIMAL_OFF_HISTORICAL_ON); + } +#ifdef USE_SOURCE_CACHE + (void) reparse_document(); +#endif + return; +} + +#if defined(DIRED_SUPPORT) +static void handle_LYK_MODIFY(BOOLEAN *refresh_screen) +{ + if (lynx_edit_mode && nlinks > 0 && !no_dired_support) { + int ret; + + ret = local_modify(&curdoc, &newdoc.address); + if (ret == PERMIT_FORM_RESULT) { /* Permit form thrown up */ + *refresh_screen = TRUE; + } else if (ret) { + DIRED_UNCACHE_1; + move_address(&newdoc, &curdoc); + LYFreePostData(&newdoc); + FREE(newdoc.bookmark); + newdoc.isHEAD = FALSE; + newdoc.safe = FALSE; + newdoc.internal_link = FALSE; + newdoc.line = curdoc.line; + newdoc.link = curdoc.link; + LYclear(); + } + } +} +#endif /* DIRED_SUPPORT */ + +#ifdef EXP_NESTED_TABLES +static BOOLEAN handle_LYK_NESTED_TABLES(int *cmd) +{ + nested_tables = (BOOLEAN) !nested_tables; + HTUserMsg(nested_tables ? NESTED_TABLES_ON : NESTED_TABLES_OFF); + return reparse_or_reload(cmd); +} +#endif + +static BOOLEAN handle_LYK_OPTIONS(int *cmd, + BOOLEAN *refresh_screen) +{ +#ifndef NO_OPTION_MENU + if (!LYUseFormsOptions) { + BOOLEAN LYUseDefaultRawMode_flag = LYUseDefaultRawMode; + BOOLEAN LYSelectPopups_flag = LYSelectPopups; + BOOLEAN verbose_img_flag = verbose_img; + BOOLEAN keypad_mode_flag = (BOOL) keypad_mode; + BOOLEAN show_dotfiles_flag = show_dotfiles; + BOOLEAN user_mode_flag = (BOOL) user_mode; + int CurrentAssumeCharSet_flag = UCLYhndl_for_unspec; + int CurrentCharSet_flag = current_char_set; + int HTfileSortMethod_flag = HTfileSortMethod; + char *CurrentUserAgent = NULL; + char *CurrentNegoLanguage = NULL; + char *CurrentNegoCharset = NULL; + + StrAllocCopy(CurrentUserAgent, NonNull(LYUserAgent)); + StrAllocCopy(CurrentNegoLanguage, NonNull(language)); + StrAllocCopy(CurrentNegoCharset, NonNull(pref_charset)); + + LYoptions(); /** do the old-style options stuff **/ + + if (keypad_mode_flag != keypad_mode || + (user_mode_flag != user_mode && + (user_mode_flag == NOVICE_MODE || + user_mode == NOVICE_MODE)) || + (((HTfileSortMethod_flag != HTfileSortMethod) || + (show_dotfiles_flag != show_dotfiles)) && + (isFILE_URL(curdoc.address) || + isFTP_URL(curdoc.address))) || + CurrentCharSet_flag != current_char_set || + CurrentAssumeCharSet_flag != UCLYhndl_for_unspec || + verbose_img_flag != verbose_img || + LYUseDefaultRawMode_flag != LYUseDefaultRawMode || + LYSelectPopups_flag != LYSelectPopups || + ((strcmp(CurrentUserAgent, NonNull(LYUserAgent)) || + strcmp(CurrentNegoLanguage, NonNull(language)) || + strcmp(CurrentNegoCharset, NonNull(pref_charset))) && + (!StrNCmp(curdoc.address, "http", 4) || + isLYNXCGI(curdoc.address)))) { + + BOOLEAN canreparse_post = FALSE; + + /* + * Check if this is a reply from a POST, and if so, seek + * confirmation of reload if the safe element is not set. - FM + */ + if ((curdoc.post_data != NULL && + curdoc.safe != TRUE) && +#ifdef USE_SOURCE_CACHE + (!(canreparse_post = HTcan_reparse_document())) && +#endif + confirm_post_resub(curdoc.address, curdoc.title, + 2, 1) == FALSE) { + HTInfoMsg(WILL_NOT_RELOAD_DOC); + } else { + copy_address(&newdoc, &curdoc); + if (((strcmp(CurrentUserAgent, NonNull(LYUserAgent)) || + strcmp(CurrentNegoLanguage, NonNull(language)) || + strcmp(CurrentNegoCharset, NonNull(pref_charset))) && + (StrNCmp(curdoc.address, "http", 4) == 0 || + isLYNXCGI(curdoc.address)))) { + /* + * An option has changed which may influence content + * negotiation, and the resource is from a http or https or + * lynxcgi URL (the only protocols which currently do + * anything with this information). Set reloading = TRUE + * so that proxy caches will be flushed, which is necessary + * until the time when all proxies understand HTTP 1.1 + * Vary: and all Servers properly use it... Treat like + * case LYK_RELOAD (see comments there). - KW + */ + reloading = TRUE; + } + if (HTisDocumentSource()) { + srcmode_for_next_retrieval(1); + } +#ifdef USE_SOURCE_CACHE + if (reloading == FALSE) { + /* one more attempt to be smart enough: */ + if (reparse_document()) { + FREE(CurrentUserAgent); + FREE(CurrentNegoLanguage); + FREE(CurrentNegoCharset); + return FALSE; + } + } +#endif + if (canreparse_post && + confirm_post_resub(curdoc.address, curdoc.title, + 2, 1) == FALSE) { + if (HTisDocumentSource()) { + srcmode_for_next_retrieval(0); + } + FREE(CurrentUserAgent); + FREE(CurrentNegoLanguage); + FREE(CurrentNegoCharset); + return FALSE; + } + + HEAD_request = HTLoadedDocumentIsHEAD(); + HText_setNoCache(HTMainText); + newdoc.line = curdoc.line; + newdoc.link = curdoc.link; + LYforce_no_cache = TRUE; + free_address(&curdoc); /* So it doesn't get pushed. */ + } + } + FREE(CurrentUserAgent); + FREE(CurrentNegoLanguage); + FREE(CurrentNegoCharset); + *refresh_screen = TRUE; /* to repaint screen */ + return FALSE; + } /* end if !LYUseFormsOptions */ +#else + (void) refresh_screen; +#endif /* !NO_OPTION_MENU */ +#ifndef NO_OPTION_FORMS + /* + * Generally stolen from LYK_COOKIE_JAR. Options menu handling is + * done in postoptions(), called from getfile() currently. + * + * postoptions() is also responsible for reloading the document + * before the 'options menu' but only when (a few) important + * options were changed. + * + * It is critical that post_data is freed here since the + * submission of changed options is done via the same protocol as + * LYNXOPTIONS: + */ + /* + * Don't do if already viewing options page. + */ + if (!LYIsUIPage(curdoc.address, UIP_OPTIONS_MENU)) { + + set_address(&newdoc, LYNXOPTIONS_PAGE("/")); + LYFreePostData(&newdoc); + FREE(newdoc.bookmark); + newdoc.isHEAD = FALSE; + newdoc.safe = FALSE; + newdoc.internal_link = FALSE; + LYforce_no_cache = TRUE; + /* change to 'if (check_realm && !LYValidate)' and + make change near top of getfile to forbid + using forms options menu with -validate: - kw */ + if (LYValidate || check_realm) { + LYPermitURL = TRUE; + } + } else { + /* + * If already in the options menu, get out. + */ + *cmd = LYK_PREV_DOC; + return TRUE; + } +#else + (void) cmd; +#endif /* !NO_OPTION_FORMS */ + return FALSE; +} + +static void handle_NEXT_DOC(void) +{ + if (LYhist_next(&curdoc, &newdoc)) { + free_address(&curdoc); /* avoid push */ + return; + } + HTUserMsg(gettext("No next document present")); +} + +static void handle_LYK_NEXT_LINK(int c, + int *old_c, + int real_c) +{ + if (curdoc.link < nlinks - 1) { /* next link */ + LYhighlight(FALSE, curdoc.link, prev_target->str); +#ifdef FASTTAB + /* + * Move to different textarea if TAB in textarea. + */ + if (LinkIsTextarea(curdoc.link) && + c == '\t') { + int thisgroup = links[curdoc.link].l_form->number; + char *thisname = links[curdoc.link].l_form->name; + + do + curdoc.link++; + while ((curdoc.link < nlinks - 1) && + LinkIsTextarea(curdoc.link) && + links[curdoc.link].l_form->number == thisgroup && + sametext(links[curdoc.link].l_form->name, thisname)); + } else { + curdoc.link++; + } +#else + curdoc.link++; +#endif /* FASTTAB */ + /* + * At the bottom of list and there is only one page. Move to the top + * link on the page. + */ + } else if (!more_text && LYGetNewline() == 1 && curdoc.link == nlinks - 1) { + set_curdoc_link(0); + + } else if (more_text) { /* next page */ + LYChgNewline(display_lines); + } else if (*old_c != real_c) { + HandleForwardWraparound(); + } +} + +static void handle_LYK_NEXT_PAGE(int *old_c, + int real_c) +{ + if (more_text) { + LYChgNewline(display_lines); + } else if (curdoc.link < nlinks - 1) { + set_curdoc_link(nlinks - 1); + } else if (*old_c != real_c) { + *old_c = real_c; + HTInfoMsg(ALREADY_AT_END); + } +} + +static BOOLEAN handle_LYK_NOCACHE(int *old_c, + int real_c) +{ + if (nlinks > 0) { + if (links[curdoc.link].type == WWW_FORM_LINK_TYPE && + links[curdoc.link].l_form->type != F_SUBMIT_TYPE && + links[curdoc.link].l_form->type != F_IMAGE_SUBMIT_TYPE && + links[curdoc.link].l_form->type != F_TEXT_SUBMIT_TYPE) { + if (*old_c != real_c) { + *old_c = real_c; + HTUserMsg(NOT_ON_SUBMIT_OR_LINK); + } + return FALSE; + } else { + LYforce_no_cache = TRUE; + reloading = TRUE; + } + } + return TRUE; +} + +static void handle_LYK_PREV_LINK(int *arrowup, + int *old_c, + int real_c) +{ + if (curdoc.link > 0) { /* previous link */ + set_curdoc_link(curdoc.link - 1); + + } else if (!more_text && + curdoc.link == 0 && LYGetNewline() == 1) { /* at the top of list */ + /* + * If there is only one page of data and the user goes off the top, + * just move the cursor to last link on the page. + */ + set_curdoc_link(nlinks - 1); + + } else if (curdoc.line > 1) { /* previous page */ + /* + * Go back to the previous page. + */ + int scrollamount = (LYGetNewline() > display_lines + ? display_lines + : LYGetNewline() - 1); + + LYChgNewline(-scrollamount); + if (scrollamount < display_lines && + nlinks > 0 && curdoc.link == 0 && + links[0].ly - 1 + scrollamount <= display_lines) { + newdoc.link = HText_LinksInLines(HTMainText, + 1, + scrollamount) - 1; + } else { + *arrowup = TRUE; + } + + } else if (*old_c != real_c) { + HandleReverseWraparound(); + } +} + +#define nhist_1 (nhist - 1) /* workaround for indent */ + +static int handle_PREV_DOC(int *cmd, + int *old_c, + int real_c) +{ + if (nhist > 0) { /* if there is anything to go back to */ + /* + * Check if the previous document is a reply from a POST, and if so, + * seek confirmation of resubmission if the safe element is not set and + * the document is not still in the cache or LYresubmit_posts is set. + * If not confirmed and it is not the startfile, pop it so we go to the + * yet previous document, until we're OK or reach the startfile. If we + * reach the startfile and its not OK or we don't get confirmation, + * cancel. - FM + */ + DocAddress WWWDoc; + HTParentAnchor *tmpanchor; + BOOLEAN conf = FALSE, first = TRUE; + + HTLastConfirmCancelled(); /* reset flag */ + while (nhist > 0) { + conf = FALSE; + if (HDOC(nhist_1).post_data == NULL) { + break; + } + WWWDoc.address = HDOC(nhist_1).address; + WWWDoc.post_data = HDOC(nhist_1).post_data; + WWWDoc.post_content_type = + HDOC(nhist_1).post_content_type; + WWWDoc.bookmark = HDOC(nhist_1).bookmark; + WWWDoc.isHEAD = HDOC(nhist_1).isHEAD; + WWWDoc.safe = HDOC(nhist_1).safe; + tmpanchor = HTAnchor_findAddress(&WWWDoc); + if (HTAnchor_safe(tmpanchor)) { + break; + } + if ((HTAnchor_document(tmpanchor) == NULL && + (isLYNXIMGMAP(WWWDoc.address) || + (conf = confirm_post_resub(WWWDoc.address, + HDOC(nhist_1).title, + 0, 0)) + == FALSE)) || + ((LYresubmit_posts && !conf && + (NONINTERNAL_OR_PHYS_DIFFERENT((DocInfo *) &history[(nhist_1)], + &curdoc) || + NONINTERNAL_OR_PHYS_DIFFERENT((DocInfo *) &history[(nhist_1)], + &newdoc))) && + !confirm_post_resub(WWWDoc.address, + HDOC(nhist_1).title, + 2, 2))) { + if (HTLastConfirmCancelled()) { + if (!first && curdoc.internal_link) + free_address(&curdoc); + *cmd = LYK_DO_NOTHING; + return 2; + } + if (nhist == 1) { + HTInfoMsg(CANCELLED); + *old_c = 0; + *cmd = LYK_DO_NOTHING; + return 2; + } else { + HTUserMsg2(WWW_SKIP_MESSAGE, WWWDoc.address); + do { /* Should be LYhist_prev when _next supports */ + LYpop(&curdoc); /* skipping of forms */ + } while (nhist > 1 + && !are_different((DocInfo *) &history[nhist_1], + &curdoc)); + first = FALSE; /* have popped at least one */ + continue; + } + } else { + /* + * Break from loop; if user just confirmed to load again + * because document wasn't in cache, set LYforce_no_cache to + * avoid unnecessary repeat question down the road. - kw + */ + if (conf) + LYforce_no_cache = TRUE; + break; + } + } + + if (!first) + curdoc.internal_link = FALSE; + + /* + * Set newdoc.address to empty to pop a file. + */ + LYhist_prev_register(&curdoc); /* Why not call _prev instead of zeroing address? */ + free_address(&newdoc); +#ifdef DIRED_SUPPORT + if (lynx_edit_mode) { + DIRED_UNCACHE_2; + } +#endif /* DIRED_SUPPORT */ + } else if (child_lynx == TRUE) { + return (1); /* exit on left arrow in main screen */ + + } else if (*old_c != real_c) { + *old_c = real_c; + HTUserMsg(ALREADY_AT_FIRST); + } + return 0; +} + +static void handle_LYK_PREV_PAGE(int *old_c, + int real_c) +{ + if (LYGetNewline() > 1) { + LYChgNewline(-display_lines); + } else if (curdoc.link > 0) { + set_curdoc_link(0); + } else if (*old_c != real_c) { + *old_c = real_c; + HTInfoMsg(ALREADY_AT_BEGIN); + } +} + +static void handle_LYK_PRINT(BOOLEAN *ForcePush, + int *old_c, + int real_c) +{ + if (LYValidate) { + if (*old_c != real_c) { + *old_c = real_c; + HTUserMsg(PRINT_DISABLED); + } + return; + } + + /* + * Don't do if already viewing print options page. + */ + if (!LYIsUIPage(curdoc.address, UIP_PRINT_OPTIONS) + && print_options(&newdoc.address, + curdoc.address, HText_getNumOfLines()) >= 0) { + LYRegisterUIPage(newdoc.address, UIP_PRINT_OPTIONS); + StrAllocCopy(newdoc.title, PRINT_OPTIONS_TITLE); + LYFreePostData(&newdoc); + FREE(newdoc.bookmark); + newdoc.isHEAD = FALSE; + newdoc.safe = FALSE; + *ForcePush = TRUE; /* see LYpush() and print_options() */ + if (check_realm) + LYPermitURL = TRUE; + } +} + +static BOOLEAN handle_LYK_QUIT(void) +{ + int c; + + if (LYQuitDefaultYes == TRUE) { + c = HTConfirmDefault(REALLY_QUIT, YES); + } else { + c = HTConfirmDefault(REALLY_QUIT, NO); + } + if (LYQuitDefaultYes == TRUE) { + if (c != NO) { + return (TRUE); + } else { + HTInfoMsg(NO_CANCEL); + } + } else if (c == YES) { + return (TRUE); + } else { + HTInfoMsg(NO_CANCEL); + } + return FALSE; +} + +static BOOLEAN handle_LYK_RAW_TOGGLE(int *cmd) +{ + if (HTLoadedDocumentCharset()) { + HTUserMsg(gettext("charset for this document specified explicitly, sorry...")); + return FALSE; + } else { + LYUseDefaultRawMode = (BOOL) !LYUseDefaultRawMode; + HTUserMsg(LYRawMode ? RAWMODE_OFF : RAWMODE_ON); + HTMLSetCharacterHandling(current_char_set); + return reparse_or_reload(cmd); + } +} + +static void handle_LYK_RELOAD(int real_cmd) +{ + /* + * Check if this is a reply from a POST, and if so, + * seek confirmation if the safe element is not set. - FM + */ + if ((curdoc.post_data != NULL && + curdoc.safe != TRUE) && + HTConfirm(CONFIRM_POST_RESUBMISSION) == FALSE) { + HTInfoMsg(CANCELLED); + return; + } + + /* + * Check to see if should reload source, or load html + */ + + if (HTisDocumentSource()) { + if ((forced_UCLYhdnl = HTMainText_Get_UCLYhndl()) >= 0) + force_old_UCLYhndl_on_reload = TRUE; + srcmode_for_next_retrieval(1); + } + + HEAD_request = HTLoadedDocumentIsHEAD(); + HText_setNoCache(HTMainText); + /* + * Do assume the reloaded document will be the same. - FM + * + * (I don't remember all the reasons why we couldn't assume this. As the + * problems show up, we'll try to fix them, or add warnings. - FM) + */ + newdoc.line = curdoc.line; + newdoc.link = curdoc.link; + free_address(&curdoc); /* so it doesn't get pushed */ +#ifdef VMS + lynx_force_repaint(); +#endif /* VMS */ + /* + * Reload should force a cache refresh on a proxy. -- Ari L. + * + * + * -- but only if this was really a reload requested by the user, not if we + * jumped here to handle reloading for INLINE_TOGGLE, IMAGE_TOGGLE, + * RAW_TOGGLE, etc. - KW + */ + if (real_cmd == LYK_RELOAD) + reloading = REAL_RELOAD; + + return; +} + +#ifdef DIRED_SUPPORT +static void handle_LYK_REMOVE(BOOLEAN *refresh_screen) +{ + if (lynx_edit_mode && nlinks > 0 && !no_dired_support) { + int linkno = curdoc.link; /* may be changed in local_remove - kw */ + + local_remove(&curdoc); + if (LYAutoUncacheDirLists >= 1) + do_cleanup_after_delete(); + else if (curdoc.link != linkno) + *refresh_screen = TRUE; + } +} +#endif /* DIRED_SUPPORT */ + +static void handle_LYK_RIGHT_LINK(void) +{ + if (curdoc.link < nlinks - 1 && + links[curdoc.link].ly == links[curdoc.link + 1].ly) { + set_curdoc_link(curdoc.link + 1); + } +} + +static void handle_LYK_SHELL(BOOLEAN *refresh_screen, + int *old_c, + int real_c) +{ + if (!no_shell) { + stop_curses(); + printf("%s\r\n", SPAWNING_MSG); +#if defined(__CYGWIN__) + /* handling "exec $SHELL" does not work if $SHELL is null */ + if (LYGetEnv("SHELL") == NULL) { + Cygwin_Shell(); + } else +#endif + { + static char *shell = NULL; + + if (shell == 0) + StrAllocCopy(shell, LYSysShell()); + LYSystem(shell); + } + start_curses(); + *refresh_screen = TRUE; /* for an HText_pageDisplay() */ + } else { + if (*old_c != real_c) { + *old_c = real_c; + HTUserMsg(SPAWNING_DISABLED); + } + } +} + +static void handle_LYK_SOFT_DQUOTES(void) +{ +#ifdef USE_SOURCE_CACHE + if (!HTcan_reparse_document()) { +#endif + /* + * Check if this is a reply from a POST, and if so, seek confirmation + * of reload if the safe element is not set. - FM + */ + if ((curdoc.post_data != NULL && + curdoc.safe != TRUE) && + confirm_post_resub(curdoc.address, NULL, 1, 1) == FALSE) { + HTInfoMsg(WILL_NOT_RELOAD_DOC); + } else { + HText_setNoCache(HTMainText); + move_address(&newdoc, &curdoc); + newdoc.line = curdoc.line; + newdoc.link = curdoc.link; + } +#ifdef USE_SOURCE_CACHE + } /* end if no bypass */ +#endif + soft_dquotes = (BOOLEAN) !soft_dquotes; + HTUserMsg(soft_dquotes ? + SOFT_DOUBLE_QUOTE_ON : SOFT_DOUBLE_QUOTE_OFF); +#ifdef USE_SOURCE_CACHE + (void) reparse_document(); +#endif + return; +} + +#define GetAnchorNumber(link) \ + ((nlinks > 0 && link >= 0) \ + ? links[link].anchor_number \ + : -1) +#define GetAnchorLineNo(link) \ + ((nlinks > 0 && link >= 0) \ + ? links[link].anchor_line_num \ + : -1) + +/* + * Adjust the top-of-screen line number for the new document if the redisplayed + * screen would not show the given link-number. + */ +#ifdef USE_SOURCE_CACHE +static int wrap_reparse_document(void) +{ + int result; + int anchor_number = GetAnchorNumber(curdoc.link); + int old_line_num = HText_getAbsLineNumber(HTMainText, anchor_number); + int old_from_top = old_line_num - LYGetNewline() + 1; + + /* get the offset for the current anchor */ + int old_offset = ((nlinks > 0 && curdoc.link >= 0) + ? links[curdoc.link].sgml_offset + : -1); + + CTRACE((tfp, "original anchor %d, topline %d, link %d, offset %d\n", + anchor_number, old_line_num, curdoc.link, old_offset)); + + /* reparse the document (producing a new anchor list) */ + result = reparse_document(); + + /* readjust top-line and link-number */ + if (result && old_offset >= 0) { + int new_anchor = HText_closestAnchor(HTMainText, old_offset); + int new_lineno = HText_getAbsLineNumber(HTMainText, new_anchor); + int top_lineno; + + CTRACE((tfp, "old anchor %d -> new anchor %d\n", anchor_number, new_anchor)); + + if (new_lineno - old_from_top < 0) + old_from_top = new_lineno; + + /* Newline and newdoc.line are 1-based, + * but 0-based lines are simpler to work with. + */ + top_lineno = HText_getPreferredTopLine(HTMainText, new_lineno - + old_from_top) + 1; + CTRACE((tfp, "preferred top %d\n", top_lineno)); + + if (top_lineno != LYGetNewline()) { + LYSetNewline(top_lineno); + newdoc.link = HText_anchorRelativeTo(HTMainText, top_lineno - 1, new_anchor); + curdoc.link = newdoc.link; + CTRACE((tfp, + "adjusted anchor %d, topline %d, link %d, offset %d\n", + new_anchor, + top_lineno, + curdoc.link, + HText_locateAnchor(HTMainText, new_anchor))); + } else { + newdoc.link = curdoc.link; + } + } + return result; +} +#endif /* USE_SOURCE_CACHE */ + +static void handle_LYK_SOURCE(char **ownerS_address_p) +{ +#ifdef USE_SOURCE_CACHE + BOOLEAN canreparse_post = FALSE; +#endif + + /* + * Check if this is a reply from a POST, and if so, + * seek confirmation if the safe element is not set. - FM + */ + if ((curdoc.post_data != NULL && + curdoc.safe != TRUE) && +#ifdef USE_SOURCE_CACHE + (!(canreparse_post = HTcan_reparse_document())) && +#endif + (curdoc.isHEAD ? HTConfirm(CONFIRM_POST_RESUBMISSION) : + confirm_post_resub(curdoc.address, curdoc.title, 1, 1)) == FALSE) { + HTInfoMsg(CANCELLED); + return; + } + + if (HTisDocumentSource()) { + srcmode_for_next_retrieval(-1); + } else { + if (HText_getOwner()) + StrAllocCopy(*ownerS_address_p, HText_getOwner()); + LYUCPushAssumed(HTMainAnchor); + srcmode_for_next_retrieval(1); + } + +#ifdef USE_SOURCE_CACHE + if (wrap_reparse_document()) { + /* + * These normally get cleaned up after getfile() returns; + * since we're not calling getfile(), we have to clean them + * up ourselves. -dsb + */ + HTOutputFormat = WWW_PRESENT; +#ifdef USE_PRETTYSRC + if (psrc_view) + HTMark_asSource(); + psrc_view = FALSE; +#endif + FREE(*ownerS_address_p); /* not used with source_cache */ + LYUCPopAssumed(); /* probably a right place here */ + HTMLSetCharacterHandling(current_char_set); /* restore now */ + + return; + } else if (canreparse_post) { + srcmode_for_next_retrieval(0); + LYUCPopAssumed(); /* probably a right place here */ + return; + } +#endif + + if (curdoc.title) + StrAllocCopy(newdoc.title, curdoc.title); + + free_address(&curdoc); /* so it doesn't get pushed */ + LYforce_no_cache = TRUE; +} + +static void handle_LYK_SWITCH_DTD(void) +{ +#ifdef USE_SOURCE_CACHE + BOOLEAN canreparse = FALSE; + + if (!(canreparse = HTcan_reparse_document())) { +#endif + /* + * Check if this is a reply from a POST, and if so, + * seek confirmation of reload if the safe element + * is not set. - FM, kw + */ + if ((curdoc.post_data != NULL && + curdoc.safe != TRUE) && + confirm_post_resub(curdoc.address, NULL, 1, 1) == FALSE) { + HTInfoMsg(WILL_NOT_RELOAD_DOC); + } else { + /* + * If currently viewing preparsed source, switching to the other + * DTD parsing may show source differences, so stay in source view + * - kw + */ + + /* NOTE: this conditional can be considered incorrect - + current behaviour - when viewing source and + LYPreparsedSource==TRUE, pressing ^V will toggle parser mode + AND switch back from the source view to presentation view.-HV + */ + if (HTisDocumentSource() && LYPreparsedSource) { + srcmode_for_next_retrieval(1); + } + HText_setNoCache(HTMainText); + move_address(&newdoc, &curdoc); + newdoc.line = curdoc.line; + newdoc.link = curdoc.link; + } +#ifdef USE_SOURCE_CACHE + } /* end if no bypass */ +#endif + Old_DTD = !Old_DTD; + HTSwitchDTD(!Old_DTD); + HTUserMsg(Old_DTD ? USING_DTD_0 : USING_DTD_1); +#ifdef USE_SOURCE_CACHE + if (canreparse) { + if (HTisDocumentSource() && LYPreparsedSource) { + srcmode_for_next_retrieval(1); + } + if (!reparse_document()) { + srcmode_for_next_retrieval(0); + } + } +#endif + return; +} + +#ifdef DIRED_SUPPORT +static void handle_LYK_TAG_LINK(void) +{ + if (lynx_edit_mode && nlinks > 0 && !no_dired_support) { + if (!strcmp(LYGetHiliteStr(curdoc.link, 0), "..")) + return; /* Never tag the parent directory */ + if (dir_list_style == MIXED_STYLE) { + if (!strcmp(LYGetHiliteStr(curdoc.link, 0), "../")) + return; + } else if (!StrNCmp(LYGetHiliteStr(curdoc.link, 0), "Up to ", 6)) + return; + { + /* + * HTList-based management of tag list, see LYLocal.c - KW + */ + HTList *t1 = tagged; + char *tagname = NULL; + BOOLEAN found = FALSE; + + while ((tagname = (char *) HTList_nextObject(t1)) != NULL) { + if (!strcmp(links[curdoc.link].lname, tagname)) { + found = TRUE; + HTList_removeObject(tagged, tagname); + FREE(tagname); + tagflag(FALSE, curdoc.link); + break; + } + } + if (!found) { + if (tagged == NULL) + tagged = HTList_new(); + tagname = NULL; + StrAllocCopy(tagname, links[curdoc.link].lname); + HTList_addObject(tagged, tagname); + tagflag(TRUE, curdoc.link); + } + } + if (curdoc.link < nlinks - 1) { + set_curdoc_link(curdoc.link + 1); + } else if (!more_text && LYGetNewline() == 1 && curdoc.link == nlinks + - 1) { + set_curdoc_link(0); + } else if (more_text) { /* next page */ + LYChgNewline(display_lines); + } + } +} +#endif /* DIRED_SUPPORT */ + +static void handle_LYK_TOGGLE_HELP(void) +{ + if (user_mode == NOVICE_MODE) { + toggle_novice_line(); + noviceline(more_text); + } +} + +static void handle_LYK_TOOLBAR(BOOLEAN *try_internal, + BOOLEAN *force_load, + int *old_c, + int real_c) +{ + char *cp; + char *toolbar = NULL; + + if (!HText_hasToolbar(HTMainText)) { + if (*old_c != real_c) { + *old_c = real_c; + HTUserMsg(NO_TOOLBAR); + } + } else if (*old_c != real_c) { + *old_c = real_c; + cp = trimPoundSelector(curdoc.address); + HTSprintf0(&toolbar, "%s#%s", curdoc.address, LYToolbarName); + restorePoundSelector(cp); + set_address(&newdoc, toolbar); + FREE(toolbar); + *try_internal = TRUE; + *force_load = TRUE; /* force MainLoop to reload */ + } +} + +static void handle_LYK_TRACE_LOG(BOOLEAN *trace_flag_ptr) +{ +#ifndef NO_LYNX_TRACE + /* + * Check whether we've started a TRACE log in this session. - FM + */ + if (LYTraceLogFP == NULL) { + HTUserMsg(NO_TRACELOG_STARTED); + return; + } + + /* + * Don't do if already viewing the TRACE log. - FM + */ + if (LYIsUIPage(curdoc.address, UIP_TRACELOG)) + return; + + /* + * If TRACE mode is on, turn it off during this fetch of the TRACE log, so + * we don't enter stuff about this fetch, and set a flag for turning it + * back on when we return to this loop. Note that we'll miss any messages + * about memory exhaustion if it should occur. It seems unlikely that + * anything else bad might happen, but if it does, we'll miss messages + * about that too. We also fflush(), close, and open it again, to make + * sure all stderr messages thus far will be in the log. - FM + */ + if (!LYReopenTracelog(trace_flag_ptr)) + return; + + LYLocalFileToURL(&(newdoc.address), LYTraceLogPath); + LYRegisterUIPage(newdoc.address, UIP_TRACELOG); + StrAllocCopy(newdoc.title, LYNX_TRACELOG_TITLE); + LYFreePostData(&newdoc); + FREE(newdoc.bookmark); + newdoc.isHEAD = FALSE; + newdoc.safe = FALSE; + newdoc.internal_link = FALSE; + if (LYValidate || check_realm) { + LYPermitURL = TRUE; + } + LYforce_no_cache = TRUE; +#else + HTUserMsg(TRACE_DISABLED); +#endif /* NO_LYNX_TRACE */ +} + +#ifdef DIRED_SUPPORT +static void handle_LYK_UPLOAD(void) +{ + /* + * Don't do if already viewing upload options page. + */ + if (LYIsUIPage(curdoc.address, UIP_UPLOAD_OPTIONS)) + return; + + if (lynx_edit_mode && !no_dired_support) { + LYUpload_options(&(newdoc.address), curdoc.address); + StrAllocCopy(newdoc.title, UPLOAD_OPTIONS_TITLE); + LYFreePostData(&newdoc); + FREE(newdoc.bookmark); + newdoc.isHEAD = FALSE; + newdoc.safe = FALSE; + newdoc.internal_link = FALSE; + /* + * Uncache the current listing so that it will be updated to included + * the uploaded file if placed in the current directory. - FM + */ + DIRED_UNCACHE_1; + } +} +#endif /* DIRED_SUPPORT */ + +static void handle_LYK_UP_xxx(int *arrowup, + int *old_c, + int real_c, + int scroll_by) +{ + if (LYGetNewline() > 1) { + if (LYGetNewline() - scroll_by < 1) + scroll_by = LYGetNewline() - 1; + LYChgNewline(-scroll_by); + if (nlinks > 0 && curdoc.link > -1) { + if (links[curdoc.link].ly + scroll_by <= display_lines) { + newdoc.link = curdoc.link + + HText_LinksInLines(HTMainText, + LYGetNewline(), + scroll_by); + } else { + *arrowup = TRUE; + } + } + } else if (*old_c != real_c) { + HandleReverseWraparound(); + } +} + +static void handle_LYK_UP_HALF(int *arrowup, + int *old_c, + int real_c) +{ + handle_LYK_UP_xxx(arrowup, old_c, real_c, display_lines / 2); +} + +static void handle_LYK_UP_LINK(int *follow_col, + int *arrowup, + int *old_c, + int real_c) +{ + if (curdoc.link > 0 && + (links[0].ly != links[curdoc.link].ly || + !HText_LinksInLines(HTMainText, 1, LYGetNewline() - 1))) { + /* more links before this on screen, and first of them on + a different line or no previous links before this screen? */ + int newlink; + + if (*follow_col == -1) { + const char *text = LYGetHiliteStr(curdoc.link, 0); + + *follow_col = links[curdoc.link].lx; + + if (text != NULL) + *follow_col += (int) strlen(text) / 2; + } + + newlink = find_link_near_col(*follow_col, -1); + if (newlink > -1) { + set_curdoc_link(newlink); + } else if (*old_c != real_c) { + *old_c = real_c; + HTUserMsg(NO_LINKS_ABOVE); + } + + } else if (curdoc.line > 1 && LYGetNewline() > 1) { /* previous page */ + int scrollamount = (LYGetNewline() > display_lines + ? display_lines + : LYGetNewline() - 1); + + LYChgNewline(-scrollamount); + if (scrollamount < display_lines && + nlinks > 0 && curdoc.link > -1 && + links[0].ly - 1 + scrollamount <= display_lines) { + newdoc.link = HText_LinksInLines(HTMainText, + 1, + scrollamount) - 1; + } else { + *arrowup = TRUE; + } + + } else if (*old_c != real_c) { + HandleReverseWraparound(); + } +} + +static void handle_LYK_UP_TWO(int *arrowup, + int *old_c, + int real_c) +{ + handle_LYK_UP_xxx(arrowup, old_c, real_c, 2); +} + +static void handle_LYK_VIEW_BOOKMARK(BOOLEAN *refresh_screen, + int *old_c, + int real_c) +{ + const char *cp; + + if (LYValidate) { + if (*old_c != real_c) { + *old_c = real_c; + HTUserMsg(BOOKMARKS_DISABLED); + } + return; + } + + /* + * See if a bookmark exists. If it does replace newdoc.address with its + * name. + */ + if ((cp = get_bookmark_filename(&newdoc.address)) != NULL) { + if (*cp == '\0' || !strcmp(cp, " ") || + !strcmp(curdoc.address, newdoc.address)) { + if (LYMultiBookmarks != MBM_OFF) + *refresh_screen = TRUE; + return; + } +#ifdef KANJI_CODE_OVERRIDE + if (HTCJK == JAPANESE) { + last_kcode = NOKANJI; /* AUTO */ + } +#endif + LYforce_no_cache = TRUE; /*force the document to be reloaded */ + StrAllocCopy(newdoc.title, BOOKMARK_TITLE); + StrAllocCopy(newdoc.bookmark, BookmarkPage); + LYFreePostData(&newdoc); + newdoc.isHEAD = FALSE; + newdoc.safe = FALSE; + newdoc.internal_link = FALSE; + } else { + if (*old_c != real_c) { + *old_c = real_c; + LYMBM_statusline(BOOKMARKS_NOT_OPEN); + LYSleepAlert(); + if (LYMultiBookmarks != MBM_OFF) { + *refresh_screen = TRUE; + } + } + } +} + +static BOOLEAN handle_LYK_VLINKS(int *cmd, + BOOLEAN *newdoc_link_is_absolute) +{ + int c; + + if (LYIsUIPage(curdoc.address, UIP_VLINKS)) { + /* + * Already viewing visited links page, so get out. + */ + *cmd = LYK_PREV_DOC; + return TRUE; + } + + /* + * Print visited links page to file. + */ + c = LYShowVisitedLinks(&newdoc.address); + if (c < 0) { + HTUserMsg(VISITED_LINKS_EMPTY); + return FALSE; + } + StrAllocCopy(newdoc.title, VISITED_LINKS_TITLE); + LYFreePostData(&newdoc); + FREE(newdoc.bookmark); + newdoc.isHEAD = FALSE; + newdoc.safe = FALSE; + newdoc.internal_link = FALSE; + if (c > 0) { + /* Select a correct link. */ + *newdoc_link_is_absolute = TRUE; + newdoc.link = c - 1; + } + if (LYValidate || check_realm) { + LYPermitURL = TRUE; + StrAllocCopy(lynxlinksfile, newdoc.address); + } + return FALSE; +} + +void handle_LYK_WHEREIS(int cmd, + BOOLEAN *refresh_screen) +{ + BOOLEAN have_target_onscreen = (BOOLEAN) (!isBEmpty(prev_target) && + HText_pageHasPrevTarget()); + BOOL found; + int oldcur = curdoc.link; /* temporarily remember */ + char *remember_old_target = NULL; + + if (have_target_onscreen) + StrAllocCopy(remember_old_target, prev_target->str); + else + StrAllocCopy(remember_old_target, ""); + + if (cmd == LYK_WHEREIS) { + /* + * Reset prev_target to force prompting for a new search string and to + * turn off highlighting if no search string is entered by the user. + */ + BStrCopy0(prev_target, ""); + } + found = textsearch(&curdoc, &prev_target, + (cmd == LYK_WHEREIS) + ? 0 + : ((cmd == LYK_NEXT) + ? 1 + : -1)); + + /* + * Force a redraw to ensure highlighting of hits even when found on the + * same page, or clearing of highlighting if the default search string was + * erased without replacement. - FM + */ + /* + * Well let's try to avoid it at least in a few cases + * where it is not needed. - kw + */ + if (www_search_result >= 0 && www_search_result != curdoc.line) { + *refresh_screen = TRUE; /* doesn't really matter */ + } else if (!found) { + *refresh_screen = have_target_onscreen; + } else if (!have_target_onscreen && found) { + *refresh_screen = TRUE; + } else if (www_search_result == curdoc.line && + curdoc.link == oldcur && + curdoc.link >= 0 && nlinks > 0 && + links[curdoc.link].ly >= (display_lines / 3)) { + *refresh_screen = TRUE; + } else if ((LYcase_sensitive && 0 != strcmp(prev_target->str, + remember_old_target)) || + (!LYcase_sensitive && 0 != strcasecomp8(prev_target->str, + remember_old_target))) { + *refresh_screen = TRUE; + } + FREE(remember_old_target); +} + +/* + * Get a number from the user and follow that link number. + */ +static void handle_LYK_digit(int c, + BOOLEAN *force_load, + int *old_c, + int real_c, + BOOLEAN *try_internal GCC_UNUSED) +{ + int lindx = ((nlinks > 0) ? curdoc.link : 0); + int number; + char *temp = NULL; + + /* pass cur line num for use in follow_link_number() + * Note: Current line may not equal links[cur].line + */ + number = curdoc.line; + switch (follow_link_number(c, lindx, &newdoc, &number)) { + case DO_LINK_STUFF: + /* + * Follow a normal link. + */ + set_address(&newdoc, links[lindx].lname); + StrAllocCopy(newdoc.title, LYGetHiliteStr(lindx, 0)); + /* + * For internal links, retain POST content if present. If we are on + * the List Page, prevent pushing it on the history stack. Otherwise + * set try_internal to signal that the top of the loop should attempt + * to reposition directly, without calling getfile. - kw + */ + if (track_internal_links) { + if (links[lindx].type == WWW_INTERN_LINK_TYPE) { + LYinternal_flag = TRUE; + newdoc.internal_link = TRUE; + if (LYIsListpageTitle(NonNull(curdoc.title)) && + (LYIsUIPage(curdoc.address, UIP_LIST_PAGE) || + LYIsUIPage(curdoc.address, UIP_ADDRLIST_PAGE))) { + if (check_history()) { + LYinternal_flag = TRUE; + } else { + HTLastConfirmCancelled(); /* reset flag */ + if (!confirm_post_resub(newdoc.address, + newdoc.title, + ((LYresubmit_posts && + HText_POSTReplyLoaded(&newdoc)) + ? 1 + : 2), + 2)) { + if (HTLastConfirmCancelled() || + (LYresubmit_posts && + !HText_POSTReplyLoaded(&newdoc))) { + /* cancel the whole thing */ + LYforce_no_cache = FALSE; + reloading = FALSE; + copy_address(&newdoc, &curdoc); + StrAllocCopy(newdoc.title, curdoc.title); + newdoc.internal_link = curdoc.internal_link; + HTInfoMsg(CANCELLED); + if (nlinks > 0) + HText_pageDisplay(curdoc.line, prev_target->str); + break; + } else if (LYresubmit_posts) { + /* If LYresubmit_posts is set, and the + answer was No, and we have a cached + copy, then use it. - kw */ + LYforce_no_cache = FALSE; + } else { + /* if No, but not ^C or ^G, drop + * the post data. Maybe the link + * wasn't meant to be internal after + * all, here we can recover from that + * assumption. - kw */ + LYFreePostData(&newdoc); + newdoc.internal_link = FALSE; + HTAlert(DISCARDING_POST_DATA); + } + } + } + /* + * Don't push the List Page if we follow an internal link given + * by it. - kw + */ + free_address(&curdoc); + } else + *try_internal = TRUE; + if (!(LYresubmit_posts && newdoc.post_data)) + LYinternal_flag = TRUE; + *force_load = TRUE; + break; + } else { + /* + * Free POST content if not an internal link. - kw + */ + LYFreePostData(&newdoc); + } + } + /* + * Might be an anchor in the same doc from a POST form. If so, don't + * free the content. -- FM + */ + if (are_different(&curdoc, &newdoc)) { + LYFreePostData(&newdoc); + FREE(newdoc.bookmark); + newdoc.isHEAD = FALSE; + newdoc.safe = FALSE; + if (isLYNXMESSAGES(newdoc.address)) + LYforce_no_cache = TRUE; + } + newdoc.internal_link = FALSE; + *force_load = TRUE; /* force MainLoop to reload */ + break; + + case DO_GOTOLINK_STUFF: + /* + * Position on a normal link, don't follow it. - KW + */ + LYSetNewline(newdoc.line); + newdoc.line = 1; + if (LYGetNewline() == curdoc.line) { + /* + * It's a link in the current page. - FM + */ + if (nlinks > 0 && curdoc.link > -1) { + if (curdoc.link == newdoc.link) { + /* + * It's the current link, and presumably reflects a typo in + * the statusline entry, so issue a statusline message for + * the typo-prone users (like me 8-). - FM + */ + HTSprintf0(&temp, LINK_ALREADY_CURRENT, number); + HTUserMsg(temp); + FREE(temp); + } else { + /* + * It's a different link on this page, + */ + set_curdoc_link(newdoc.link); + newdoc.link = 0; + } + } + } + break; /* nothing more to do */ + + case DO_GOTOPAGE_STUFF: + /* + * Position on a page in this document. - FM + */ + LYSetNewline(newdoc.line); + newdoc.line = 1; + if (LYGetNewline() == curdoc.line) { + /* + * It's the current page, so issue a statusline message for the + * typo-prone users (like me 8-). - FM + */ + if (LYGetNewline() <= 1) { + HTInfoMsg(ALREADY_AT_BEGIN); + } else if (!more_text) { + HTInfoMsg(ALREADY_AT_END); + } else { + HTSprintf0(&temp, ALREADY_AT_PAGE, number); + HTUserMsg(temp); + FREE(temp); + } + } + break; + + case PRINT_ERROR: + *old_c = real_c; + HTUserMsg(BAD_LINK_NUM_ENTERED); + break; + } + return; +} + +#ifdef SUPPORT_CHDIR + +/* original implementation by VH */ +void handle_LYK_CHDIR(void) +{ + static bstring *buf = NULL; + char *p = NULL; + + if (no_chdir) { + HTUserMsg(CHDIR_DISABLED); + return; + } + + _statusline(gettext("cd to:")); + if (LYgetBString(&buf, FALSE, 0, NORECALL) < 0 || isBEmpty(buf)) { + HTInfoMsg(CANCELLED); + return; + } + + if (LYIsTilde(buf->str[0]) && + (LYIsPathSep(buf->str[1]) || buf->str[1] == '\0')) { + HTSprintf0(&p, "%s%s", Home_Dir(), buf->str + 1); + } else { + StrAllocCopy(p, buf->str); + } + + CTRACE((tfp, "changing directory to '%s'\n", p)); + if (chdir(p)) { + switch (errno) { + case EACCES: + HTInfoMsg(COULD_NOT_ACCESS_DIR); + break; + case ENOENT: + HTInfoMsg(gettext("No such directory")); + break; + case ENOTDIR: + HTInfoMsg(gettext("A component of path is not a directory")); + break; + default: + HTInfoMsg(gettext("failed to change directory")); + break; + } + } else { +#ifdef DIRED_SUPPORT + /*if in dired, load content of other directory */ + if (!no_dired_support + && (lynx_edit_mode || (LYIsUIPage(curdoc.address, UIP_DIRED_MENU)))) { + char buf2[LY_MAXPATH]; + char *addr = NULL; + + Current_Dir(buf2); + LYLocalFileToURL(&addr, buf2); + + newdoc.address = addr; + newdoc.isHEAD = FALSE; + StrAllocCopy(newdoc.title, gettext("A URL specified by the user")); + LYFreePostData(&newdoc); + FREE(newdoc.bookmark); + newdoc.safe = FALSE; + newdoc.internal_link = FALSE; + /**force_load = TRUE;*/ + if (lynx_edit_mode) { + DIRED_UNCACHE_2; + } + } else +#endif + HTInfoMsg(OPERATION_DONE); + } + FREE(p); +} + +static void handle_LYK_PWD(void) +{ + char buffer[LY_MAXPATH]; + int save_secs = InfoSecs; + BOOLEAN save_wait = no_pause; + + if (Secs2SECS(save_secs) < 1) + InfoSecs = SECS2Secs(1); + no_pause = FALSE; + + HTInfoMsg(Current_Dir(buffer)); + + InfoSecs = save_secs; + no_pause = save_wait; +} +#endif + +#ifdef USE_CURSES_PADS +/* + * Having jumps larger than this is counter-productive. Indeed, it is natural + * to expect that when the relevant text appears, one would "overshoot" and + * would scroll 3-4 extra full screens. When going back, the "accumulation" + * logic would again start moving in full screens, so one would overshoot + * again, etc. + * + * Going back, one can fix it in 28 keypresses. The relevant text will appear + * on the screen soon enough for the key-repeat to become not that important, + * and we are still moving in smaller steps than when we overshot. Since key + * repeat is not important, even if we overshoot again, it is going to be by 30 + * steps, which is easy to fix by reversing the direction again. + */ +static int repeat_to_delta(int n) +{ + int threshold = LYcols / 3; + + while (threshold > 0) { + if (n >= threshold) { + n = threshold; + break; + } + threshold = (threshold * 2) / 3; + } + return n; +} + +static void handle_LYK_SHIFT_LEFT(BOOLEAN *flag, int count) +{ + if (!LYwideLines) { + HTAlert(SHIFT_VS_LINEWRAP); + return; + } + if (LYshiftWin > 0) { + LYshiftWin -= repeat_to_delta(count); + *flag = TRUE; + } + if (LYshiftWin < 0) + LYshiftWin = 0; +} + +static void handle_LYK_SHIFT_RIGHT(BOOLEAN *flag, int count) +{ + if (!LYwideLines) { + HTAlert(SHIFT_VS_LINEWRAP); + return; + } + LYshiftWin += repeat_to_delta(count); + *flag = TRUE; +} + +static BOOLEAN handle_LYK_LINEWRAP_TOGGLE(int *cmd, + BOOLEAN *flag) +{ + static const char *choices[] = + { + "Try to fit screen width", + "No line wrap in columns", + "Wrap columns at screen width", + "Wrap columns at 3/4 screen width", + "Wrap columns at 2/3 screen width", + "Wrap columns at 1/2 screen width", + "Wrap columns at 1/3 screen width", + "Wrap columns at 1/4 screen width", + NULL + }; + static int wrap[] = + { + 0, + 0, + 12, /* In units of 1/12 */ + 9, + 8, + 6, + 4, + 3 + }; + int c; + int code = FALSE; + + CTRACE((tfp, "Entering handle_LYK_LINEWRAP_TOGGLE\n")); + if (LYwin != stdscr) { + /* Somehow the mouse is over the number instead of being over the + name, so we decrease x. */ + c = LYChoosePopup(!LYwideLines, + LYlines / 2 - 2, + LYcolLimit / 2 - 6, + choices, (int) TABLESIZE(choices) - 1, + FALSE, TRUE); + /* + * LYhandlePopupList() wasn't really meant to be used outside of + * old-style Options menu processing. One result of mis-using it here + * is that we have to deal with side-effects regarding SIGINT signal + * handler and the term_options global variable. - kw + */ + if (!term_options) { + CTRACE((tfp, + "...setting LYwideLines %d, LYtableCols %d (have %d and %d)\n", + c, wrap[c], + LYwideLines, + LYtableCols)); + + LYwideLines = c; + LYtableCols = wrap[c]; + + if (LYwideLines == 0) + LYshiftWin = 0; + *flag = TRUE; + HTUserMsg(LYwideLines ? LINEWRAP_OFF : LINEWRAP_ON); + code = reparse_or_reload(cmd); + } + } + return (BOOLEAN) code; +} +#endif + +#ifdef USE_MAXSCREEN_TOGGLE +static BOOLEAN handle_LYK_MAXSCREEN_TOGGLE(int *cmd) +{ + static int flag = 0; + + CTRACE((tfp, "Entering handle_LYK_MAXSCREEN_TOGGLE\n")); + if (flag) { + CTRACE((tfp, "Calling recoverWindowSize()\n")); + recoverWindowSize(); + flag = 0; + } else { + CTRACE((tfp, "Calling maxmizeWindowSize()\n")); + maxmizeWindowSize(); + flag = 1; + } + return reparse_or_reload(cmd); +} +#endif + +#ifdef LY_FIND_LEAKS +#define CleanupMainLoop() \ + BStrFree(prev_target); \ + BStrFree(user_input_buffer) +#else +#define CleanupMainLoop() /* nothing */ +#endif + +/* + * Here's where we do all the work. + * mainloop is basically just a big switch dependent on the users input. I + * have tried to offload most of the work done here to procedures to make it + * more modular, but this procedure still does a lot of variable manipulation. + * This needs some work to make it neater. - Lou Moutilli + * (memoir from the original Lynx - FM) + */ +int mainloop(void) +{ +#if defined(WIN_EX) /* 1997/10/08 (Wed) 14:52:06 */ + char sjis_buff[MAX_LINE]; + char temp_buff[sizeof(sjis_buff) * 4]; +#endif + int c = 0; + int real_c = 0; + int old_c = 0; + int pending_form_c = -1; + int cmd = LYK_DO_NOTHING, real_cmd = LYK_DO_NOTHING; + int getresult; + int arrowup = FALSE, show_help = FALSE; + bstring *user_input_buffer = NULL; + const char *cshelpfile = NULL; + BOOLEAN first_file = TRUE; + BOOLEAN popped_doc = FALSE; + BOOLEAN refresh_screen = FALSE; + BOOLEAN force_load = FALSE; + BOOLEAN try_internal = FALSE; + BOOLEAN crawl_ok = FALSE; + BOOLEAN vi_keys_flag = vi_keys; + BOOLEAN emacs_keys_flag = emacs_keys; + BOOLEAN trace_mode_flag = FALSE; + BOOLEAN forced_HTML_mode = LYforce_HTML_mode; + char cfile[128]; + FILE *cfp; + char *cp; + int ch = 0; + RecallType recall = NORECALL; + int URLTotal = 0; + int URLNum; + BOOLEAN FirstURLRecall = TRUE; + char *temp = NULL; + BOOLEAN ForcePush = FALSE; + BOOLEAN override_LYresubmit_posts = FALSE; + BOOLEAN newdoc_link_is_absolute = FALSE; + BOOLEAN curlink_is_editable; + BOOLEAN use_last_tfpos; + unsigned int len; + int i; + int follow_col = -1, key_count = 0, last_key = 0; + int tmpNewline; + DocInfo tmpDocInfo; + + /* "internal" means "within the same document, with certainty". It includes a + * space so it cannot conflict with any (valid) "TYPE" attributes on A + * elements. [According to which DTD, anyway??] - kw + */ + HTInternalLink = HTAtom_for("internal link"); /* init, used as const */ + +#ifndef WWW_SOURCE + WWW_SOURCE = HTAtom_for(STR_SOURCE); /* init, used as const */ +#endif + + /* + * curdoc.address contains the name of the file that is currently open. + * newdoc.address contains the name of the file that will soon be + * opened if it exits. + * prev_target contains the last search string the user searched for. + * newdoc.title contains the link name that the user last chose to get + * into the current link (file). + */ + /* initialize some variables */ + newdoc.address = NULL; + newdoc.title = NULL; + newdoc.post_data = NULL; + newdoc.post_content_type = NULL; + newdoc.bookmark = NULL; + newdoc.internal_link = FALSE; + curdoc.address = NULL; + curdoc.title = NULL; + curdoc.post_data = NULL; + curdoc.post_content_type = NULL; + curdoc.bookmark = NULL; + curdoc.internal_link = FALSE; +#ifdef USE_COLOR_STYLE + curdoc.style = NULL; + newdoc.style = NULL; +#endif +#ifndef USE_SESSIONS + nhist = 0; +#endif + BStrCopy0(user_input_buffer, ""); + BStrCopy0(prev_target, ""); +#ifdef LY_FIND_LEAKS + atexit(free_mainloop_variables); +#endif + initialize: + set_address(&newdoc, startfile); + StrAllocCopy(startrealm, startfile); + StrAllocCopy(newdoc.title, gettext("Entry into main screen")); + newdoc.isHEAD = FALSE; + newdoc.safe = FALSE; + newdoc.line = 1; + newdoc.link = 0; + +#ifdef USE_SLANG + if (TRACE && LYCursesON) { + LYaddstr("\n"); + LYrefresh(); + } +#endif /* USE_SLANG */ + CTRACE((tfp, "Entering mainloop, startfile=%s\n", startfile)); + + if (form_post_data) { + BStrCopy0(newdoc.post_data, form_post_data); + StrAllocCopy(newdoc.post_content_type, + "application/x-www-form-urlencoded"); + } else if (form_get_data) { + StrAllocCat(newdoc.address, form_get_data); + } + + if (bookmark_start) { + if (LYValidate) { + HTAlert(BOOKMARKS_DISABLED); + bookmark_start = FALSE; + goto initialize; + } else if (traversal) { + HTAlert(BOOKMARKS_NOT_TRAVERSED); + traversal = FALSE; + crawl = FALSE; + bookmark_start = FALSE; + goto initialize; + } else { + const char *cp1; + + /* + * See if a bookmark page exists. If it does, replace + * newdoc.address with its name + */ + if ((cp1 = get_bookmark_filename(&newdoc.address)) != NULL && + *cp1 != '\0' && strcmp(cp1, " ")) { + StrAllocCopy(newdoc.title, BOOKMARK_TITLE); + StrAllocCopy(newdoc.bookmark, BookmarkPage); + StrAllocCopy(startrealm, newdoc.address); + LYFreePostData(&newdoc); + newdoc.isHEAD = FALSE; + newdoc.safe = FALSE; + CTRACE((tfp, "Using bookmarks=%s\n", newdoc.address)); + } else { + HTUserMsg(BOOKMARKS_NOT_OPEN); + bookmark_start = FALSE; + goto initialize; + } + } + } + + FREE(form_post_data); + FREE(form_get_data); + + LYSetDisplayLines(); + + while (TRUE) { +#ifdef USE_COLOR_STYLE + if (curdoc.style != NULL) + force_load = TRUE; +#endif + /* + * If newdoc.address is different from curdoc.address then we need to + * go out and find and load newdoc.address. + */ + if (LYforce_no_cache || force_load || + are_different(&curdoc, &newdoc)) { + + force_load = FALSE; /* done */ + if (TRACE && LYCursesON) { + LYHideCursor(); /* make sure cursor is down */ +#ifdef USE_SLANG + LYaddstr("\n"); +#endif /* USE_SLANG */ + LYrefresh(); + } + try_again: + /* + * Push the old file onto the history stack if we have a current + * doc and a new address. - FM + */ + if (curdoc.address && newdoc.address) { + /* + * Don't actually push if this is a LYNXDOWNLOAD URL, because + * that returns NORMAL even if it fails due to a spoof attempt + * or file access problem, and we set the newdoc structure + * elements to the curdoc structure elements under case NORMAL. + * - FM + */ + if (!isLYNXDOWNLOAD(newdoc.address)) { + LYpush(&curdoc, ForcePush); + } + } else if (!newdoc.address) { + /* + * If newdoc.address is empty then pop a file and load it. - + * FM + */ + LYhist_prev(&newdoc); + popped_doc = TRUE; + + /* + * If curdoc had been reached via an internal + * (fragment) link from what we now have just + * popped into newdoc, then override non-caching in + * all cases. - kw + */ + if (track_internal_links && + curdoc.internal_link && + !are_phys_different(&curdoc, &newdoc)) { + LYinternal_flag = TRUE; + LYoverride_no_cache = TRUE; + LYforce_no_cache = FALSE; + try_internal = TRUE; + } else { + /* + * Force a no_cache override unless it's a bookmark file, + * or it has POST content and LYresubmit_posts is set + * without safe also set, and we are not going to another + * position in the current document or restoring the + * previous document due to a NOT_FOUND or NULLFILE return + * value from getfile(). - FM + */ + if ((newdoc.bookmark != NULL) || + (newdoc.post_data != NULL && + !newdoc.safe && + LYresubmit_posts && + !override_LYresubmit_posts && + NO_INTERNAL_OR_DIFFERENT(&curdoc, &newdoc))) { + LYoverride_no_cache = FALSE; + } else { + LYoverride_no_cache = TRUE; + } + } + } + override_LYresubmit_posts = FALSE; + + if (HEAD_request) { + /* + * Make SURE this is an appropriate request. - FM + */ + if (newdoc.address) { + if (LYCanDoHEAD(newdoc.address) == TRUE) { + newdoc.isHEAD = TRUE; + } else if (isLYNXIMGMAP(newdoc.address)) { + if (LYCanDoHEAD(newdoc.address + LEN_LYNXIMGMAP) == TRUE) { + StrAllocCopy(temp, newdoc.address + LEN_LYNXIMGMAP); + free_address(&newdoc); + newdoc.address = temp; + newdoc.isHEAD = TRUE; + temp = NULL; + } + } + } + try_internal = FALSE; + HEAD_request = FALSE; + } + + /* + * If we're getting the TRACE log and it's not new, check whether + * its HText structure has been dumped, and if so, fflush() and + * fclose() it to ensure it's fully updated, and then fopen() it + * again. - FM + */ + if (LYUseTraceLog == TRUE && + trace_mode_flag == FALSE && + LYTraceLogFP != NULL && + LYIsUIPage(newdoc.address, UIP_TRACELOG)) { + DocAddress WWWDoc; + HTParentAnchor *tmpanchor; + + WWWDoc.address = newdoc.address; + WWWDoc.post_data = newdoc.post_data; + WWWDoc.post_content_type = newdoc.post_content_type; + WWWDoc.bookmark = newdoc.bookmark; + WWWDoc.isHEAD = newdoc.isHEAD; + WWWDoc.safe = newdoc.safe; + tmpanchor = HTAnchor_findAddress(&WWWDoc); + if ((HText *) HTAnchor_document(tmpanchor) == NULL) { + if (!LYReopenTracelog(&trace_mode_flag)) { + old_c = 0; + cmd = LYK_PREV_DOC; + goto new_cmd; + } + } + } + + LYRequestTitle = newdoc.title; + if (newdoc.bookmark) + LYforce_HTML_mode = TRUE; + if (LYValidate && + startfile_ok && + newdoc.address && startfile && homepage && + (!strcmp(newdoc.address, startfile) || + !strcmp(newdoc.address, homepage))) { + LYPermitURL = TRUE; + } + + /* reset these two variables here before getfile() + * so they will be available in partial mode + * (was previously implemented in case NORMAL). + */ + BStrCopy0(prev_target, ""); /* Reset for new coming document */ + LYSetNewline(newdoc.line); /* set for LYGetNewline() */ + +#ifdef USE_PRETTYSRC + psrc_first_tag = TRUE; +#endif +#ifdef TEXTFIELDS_MAY_NEED_ACTIVATION + textfields_need_activation = textfields_activation_option; +#endif + FREE(LYRequestReferer); + /* + * Don't send Referer if we have to load a document again that we + * got from the history stack. We don't know any more how we + * originally got to that page. Using a Referer based on the + * current HTMainText could only be right by coincidence. - kw + * 1999-11-01 + */ + if (popped_doc) + LYNoRefererForThis = TRUE; + + if (track_internal_links) { + if (try_internal) { + if (newdoc.address && + isLYNXIMGMAP(newdoc.address)) { + try_internal = FALSE; + } else if (curdoc.address && + isLYNXIMGMAP(curdoc.address)) { + try_internal = FALSE; + } + } + if (try_internal) { + char *hashp = findPoundSelector(newdoc.address); + + if (hashp) { + HTFindPoundSelector(hashp + 1); + } + getresult = (HTMainText != NULL) ? NORMAL : NOT_FOUND; + try_internal = FALSE; /* done */ + /* fix up newdoc.address which may have been fragment-only */ + if (getresult == NORMAL && (!hashp || hashp == newdoc.address)) { + if (!hashp) { + set_address(&newdoc, HTLoadedDocumentURL()); + } else { + StrAllocCopy(temp, HTLoadedDocumentURL()); + StrAllocCat(temp, hashp); /* append fragment */ + set_address(&newdoc, temp); + FREE(temp); + } + } + } else { + if (newdoc.internal_link && newdoc.address && + *newdoc.address == '#' && nhist > 0) { + char *cp0; + + if (isLYNXIMGMAP(HDOC(nhist_1).address)) + cp0 = HDOC(nhist_1).address + LEN_LYNXIMGMAP; + else + cp0 = HDOC(nhist_1).address; + StrAllocCopy(temp, cp0); + (void) trimPoundSelector(temp); + StrAllocCat(temp, newdoc.address); + free_address(&newdoc); + newdoc.address = temp; + temp = NULL; + } + tmpDocInfo = newdoc; + tmpNewline = -1; + fill_JUMP_Params(&newdoc.address); + getresult = getfile(&newdoc, &tmpNewline); + if (!reloading && !popped_doc && (tmpNewline >= 0)) { + LYSetNewline(tmpNewline); + } else { + newdoc.link = tmpDocInfo.link; + } + } + } else { + tmpDocInfo = newdoc; + tmpNewline = -1; + fill_JUMP_Params(&newdoc.address); + getresult = getfile(&newdoc, &tmpNewline); + if (!reloading && !popped_doc && (tmpNewline >= 0)) { + LYSetNewline(tmpNewline); + } else { + newdoc.link = tmpDocInfo.link; + } + } + +#ifdef INACTIVE_INPUT_STYLE_VH + textinput_redrawn = FALSE; /* for sure */ +#endif + + switch (getresult) { + + case NOT_FOUND: + /* + * OK! can't find the file, so it must not be around now. Do + * any error logging, if appropriate. + */ + LYoverride_no_cache = FALSE; /* Was TRUE if popped. - FM */ + LYinternal_flag = FALSE; /* Reset to default. - kw */ + turn_trace_back_on(&trace_mode_flag); + if (!first_file && !LYCancelledFetch) { + /* + * Do error mail sending and/or traversal stuff. Note that + * the links[] elements may not be valid at this point, if + * we did call HTuncache_current_document! This should not + * have happened for traversal, but for sending error mail + * check that HTMainText exists for this reason. - kw + */ + if (error_logging && nhist > 0 && !popped_doc && + !LYUserSpecifiedURL && + HTMainText && + nlinks > 0 && curdoc.link < nlinks && + !isLYNXHIST(NonNull(newdoc.address)) && + !isLYNXCACHE(NonNull(newdoc.address)) && + !isLYNXCOOKIE(NonNull(newdoc.address))) { + char *mail_owner = NULL; + + if (owner_address && isMAILTO_URL(owner_address)) { + mail_owner = owner_address + LEN_MAILTO_URL; + } + /* + * Email a bad link message to the owner of the + * document, or to ALERTMAIL if defined, but NOT to + * lynx-dev (it is rejected in mailmsg). - FM, kw + */ +#ifndef ALERTMAIL + if (mail_owner) +#endif + mailmsg(curdoc.link, + mail_owner, + HDOC(nhist_1).address, + HDOC(nhist_1).title); + } + if (traversal) { + FILE *ofp; + + if ((ofp = LYAppendToTxtFile(TRAVERSE_ERRORS)) == NULL) { + if ((ofp = LYNewTxtFile(TRAVERSE_ERRORS)) == NULL) { + perror(NOOPEN_TRAV_ERR_FILE); + exit_immediately(EXIT_FAILURE); + } + } + if (nhist > 0) { + fprintf(ofp, + "%s %s\tin %s\n", + popped_doc ? + newdoc.address : links[curdoc.link].lname, + links[curdoc.link].target, + HDOC(nhist_1).address); + } else { + fprintf(ofp, + "%s %s\t\n", + popped_doc ? + newdoc.address : links[curdoc.link].lname, + links[curdoc.link].target); + } + LYCloseOutput(ofp); + } + } + + /* + * Fall through to do the NULL stuff and reload the old file, + * unless the first file wasn't found or has gone missing. + */ + if (!nhist) { + /* + * If nhist = 0 then it must be the first file. + */ + CleanupMainLoop(); + exit_immediately_with_error_message(NOT_FOUND, first_file); + return (EXIT_FAILURE); + } + /* FALLTHRU */ + + case NULLFILE: + /* + * Not supposed to return any file. + */ + LYoverride_no_cache = FALSE; /* Was TRUE if popped. - FM */ + popped_doc = FALSE; /* Was TRUE if popped. - FM */ + LYinternal_flag = FALSE; /* Reset to default. - kw */ + turn_trace_back_on(&trace_mode_flag); + free_address(&newdoc); /* to pop last doc */ + FREE(newdoc.bookmark); + LYJumpFileURL = FALSE; + reloading = FALSE; + LYPermitURL = FALSE; + LYCancelledFetch = FALSE; + ForcePush = FALSE; + LYforce_HTML_mode = FALSE; + force_old_UCLYhndl_on_reload = FALSE; + if (traversal) { + crawl_ok = FALSE; + if (traversal_link_to_add) { + /* + * It's a binary file, or the fetch attempt failed. + * Add it to TRAVERSE_REJECT_FILE so we don't try again + * in this run. + */ + if (!lookup_reject(traversal_link_to_add)) { + add_to_reject_list(traversal_link_to_add); + } + FREE(traversal_link_to_add); + } + } + /* + * Make sure the first file was found and has not gone missing. + */ + if (!nhist) { + /* + * If nhist = 0 then it must be the first file. + */ + if (first_file && homepage && + !LYSameFilename(homepage, startfile)) { + /* + * Couldn't return to the first file but there is a + * homepage we can use instead. Useful for when the + * first URL causes a program to be invoked. - GL + * + * But first make sure homepage is different from + * startfile (above), then make it the same (below) so + * we don't enter an infinite getfile() loop on on + * failures to find the files. - FM + */ + set_address(&newdoc, homepage); + LYFreePostData(&newdoc); + FREE(newdoc.bookmark); + StrAllocCopy(startfile, homepage); + newdoc.isHEAD = FALSE; + newdoc.safe = FALSE; + newdoc.internal_link = FALSE; + goto try_again; + } else { + CleanupMainLoop(); + exit_immediately_with_error_message(NULLFILE, first_file); + return (EXIT_FAILURE); + } + } + + /* + * If we're going to pop from history because getfile didn't + * succeed, reset LYforce_no_cache first. This would have been + * done in HTAccess.c if the request got that far, but the URL + * may have been handled or rejected in getfile without taking + * care of that. - kw + */ + LYforce_no_cache = FALSE; + /* + * Retrieval of a newdoc just failed, and just going to + * try_again would pop the next doc from history and try to get + * it without further questions. This may not be the right + * thing to do if we have POST data, so fake a PREV_DOC key if + * it seems that some prompting should be done. This doesn't + * affect the traversal logic, since with traversal POST data + * can never occur. - kw + */ + if (HDOC(nhist - 1).post_data && + !HDOC(nhist - 1).safe) { + if (HText_POSTReplyLoaded((DocInfo *) &history[(nhist_1)])) { + override_LYresubmit_posts = TRUE; + goto try_again; + } + /* Set newdoc fields, just in case the PREV_DOC gets + * cancelled. - kw + */ + if (!curdoc.address) { + set_address(&newdoc, HTLoadedDocumentURL()); + StrAllocCopy(newdoc.title, HTLoadedDocumentTitle()); + if (HTMainAnchor + && HTMainAnchor->post_data) { + BStrCopy(newdoc.post_data, + HTMainAnchor->post_data); + StrAllocCopy(newdoc.post_content_type, + HTMainAnchor->post_content_type); + } else { + BStrFree(newdoc.post_data); + } + newdoc.isHEAD = HTLoadedDocumentIsHEAD(); + newdoc.safe = HTLoadedDocumentIsSafe(); + newdoc.internal_link = FALSE; + } else { + copy_address(&newdoc, &curdoc); + StrAllocCopy(newdoc.title, curdoc.title); + BStrCopy(newdoc.post_data, curdoc.post_data); + StrAllocCopy(newdoc.post_content_type, + curdoc.post_content_type); + newdoc.isHEAD = curdoc.isHEAD; + newdoc.safe = curdoc.safe; + newdoc.internal_link = curdoc.internal_link; + newdoc.line = curdoc.line; + newdoc.link = curdoc.link; + } + cmd = LYK_PREV_DOC; + goto new_cmd; + } + override_LYresubmit_posts = TRUE; + goto try_again; + + case NORMAL: + /* + * Marvelously, we got the document! + */ + LYoverride_no_cache = FALSE; /* Was TRUE if popped. - FM */ + LYinternal_flag = FALSE; /* Reset to default. - kw */ + turn_trace_back_on(&trace_mode_flag); + + /* + * If it's the first file and we're interactive, check whether + * it's a bookmark file which was not accessed via the -book + * switch. - FM + */ + if (((first_file == TRUE) && + (dump_output_immediately == FALSE) && + isEmpty(newdoc.bookmark)) && + ((LYisLocalFile(newdoc.address) == TRUE) && + !(strcmp(NonNull(HText_getTitle()), + BOOKMARK_TITLE))) && + (temp = HTParse(newdoc.address, "", + PARSE_PATH + PARSE_PUNCTUATION)) != NULL) { + const char *name = wwwName(Home_Dir()); + + len = (unsigned) strlen(name); +#ifdef VMS + if (!strncasecomp(temp, name, len) && + strlen(temp) > len) +#else + if (!StrNCmp(temp, name, len) && + strlen(temp) > len) +#endif /* VMS */ + { + /* + * We're interactive and this might be a bookmark file + * entered as a startfile rather than invoked via + * -book. Check if it's in our bookmark file list, and + * if so, reload if with the relevant bookmark elements + * set. - FM + */ + cp = NULL; + if (temp[len] == '/') { + if (StrChr(&temp[(len + 1)], '/')) { + HTSprintf0(&cp, ".%s", &temp[len]); + } else { + StrAllocCopy(cp, &temp[(len + 1)]); + } + } else { + StrAllocCopy(cp, &temp[len]); + } + for (i = 0; i <= MBM_V_MAXFILES; i++) { + if (MBM_A_subbookmark[i] && + LYSameFilename(cp, MBM_A_subbookmark[i])) { + StrAllocCopy(BookmarkPage, + MBM_A_subbookmark[i]); + break; + } + } + FREE(cp); + if (i <= MBM_V_MAXFILES) { + FREE(temp); + if (LYValidate) { + HTAlert(BOOKMARKS_DISABLED); + CleanupMainLoop(); + return (EXIT_FAILURE); + } + if ((temp = HTParse(newdoc.address, "", + PARSE_ACCESS + PARSE_HOST + PARSE_PUNCTUATION))) { + set_address(&newdoc, temp); + HTuncache_current_document(); + free_address(&curdoc); + StrAllocCat(newdoc.address, + wwwName(Home_Dir())); + StrAllocCat(newdoc.address, "/"); + StrAllocCat(newdoc.address, + (StrNCmp(BookmarkPage, "./", 2) ? + BookmarkPage : + (BookmarkPage + 2))); + StrAllocCopy(newdoc.title, BOOKMARK_TITLE); + StrAllocCopy(newdoc.bookmark, BookmarkPage); +#ifdef USE_COLOR_STYLE + if (curdoc.style) + StrAllocCopy(newdoc.style, curdoc.style); +#endif + StrAllocCopy(startrealm, newdoc.address); + LYFreePostData(&newdoc); + newdoc.isHEAD = FALSE; + newdoc.safe = FALSE; + FREE(temp); + if (!strcmp(homepage, startfile)) + StrAllocCopy(homepage, newdoc.address); + StrAllocCopy(startfile, newdoc.address); + CTRACE((tfp, "Reloading as bookmarks=%s\n", + newdoc.address)); + goto try_again; + } + } + } + cp = NULL; + } + FREE(temp); + + if (traversal) { + /* + * During traversal build up lists of all links traversed. + * Traversal mode is a special feature for traversing http + * links in the web. + */ + if (traversal_link_to_add) { + /* + * Add the address we sought to TRAVERSE_FILE. + */ + if (!lookup_link(traversal_link_to_add)) + add_to_table(traversal_link_to_add); + FREE(traversal_link_to_add); + } + if (curdoc.address && curdoc.title && + !isLYNXIMGMAP(curdoc.address)) + /* + * Add the address we got to TRAVERSE_FOUND_FILE. + */ + add_to_traverse_list(curdoc.address, curdoc.title); + } + + /* + * If this was a LYNXDOWNLOAD, we still have curdoc, not a + * newdoc, so reset the address, title and positioning + * elements. - FM + */ + if (newdoc.address && curdoc.address && + isLYNXDOWNLOAD(newdoc.address)) { + copy_address(&newdoc, &curdoc); + StrAllocCopy(newdoc.title, NonNull(curdoc.title)); + StrAllocCopy(newdoc.bookmark, curdoc.bookmark); + newdoc.line = curdoc.line; + newdoc.link = curdoc.link; + newdoc.internal_link = FALSE; /* can't be true. - kw */ + } + + /* + * Set Newline to the saved line. It contains the line the + * user was on if s/he has been in the file before, or it is 1 + * if this is a new file. + * + * We already set Newline before getfile() and probably update + * it explicitly if popping from the history stack via LYpop() + * or LYpop_num() within getfile() cycle. + * + * In partial mode, Newline was probably updated in + * LYMainLoop_pageDisplay() if user scrolled the document while + * loading. Incremental loading stage already closed in + * HT*Copy(). + */ +#ifdef DISP_PARTIAL + /* Newline = newdoc.line; */ + display_partial = FALSE; /* for sure, LYNXfoo:/ may be a problem */ +#else + /* Should not be needed either if we remove "DISP_PARTIAL" from + * LYHistory.c, but lets leave it as an important comment for + * now. + */ + /* Newline = newdoc.line; */ +#endif + + /* + * If we are going to a target line or the first page of a + * popped document, override any www_search line result. + */ + if (LYGetNewline() > 1 || popped_doc == TRUE) + www_search_result = -1; + + /* + * Make sure curdoc.line will not be equal to Newline, so we + * get a redraw. + */ + curdoc.line = -1; + break; + } /* end switch */ + + if (TRACE) { + if (!LYTraceLogFP || trace_mode_flag) { + LYSleepAlert(); /* allow me to look at the results */ + } + } + + /* + * Set the files the same. + */ + copy_address(&curdoc, &newdoc); + BStrCopy(curdoc.post_data, newdoc.post_data); + StrAllocCopy(curdoc.post_content_type, newdoc.post_content_type); + StrAllocCopy(curdoc.bookmark, newdoc.bookmark); +#ifdef USE_COLOR_STYLE + StrAllocCopy(curdoc.style, HText_getStyle()); + if (curdoc.style != NULL) + style_readFromFile(curdoc.style); +#endif + curdoc.isHEAD = newdoc.isHEAD; + curdoc.internal_link = newdoc.internal_link; + + /* + * Set the remaining document elements and add to the visited links + * list. - FM + */ + if (ownerS_address != NULL) { +#ifndef USE_PRETTYSRC + if (HTOutputFormat == WWW_SOURCE && !HText_getOwner()) +#else + if ((LYpsrc ? psrc_view : HTOutputFormat == WWW_SOURCE) + && !HText_getOwner()) +#endif + HText_setMainTextOwner(ownerS_address); + FREE(ownerS_address); + } + if (HText_getTitle()) { + StrAllocCopy(curdoc.title, HText_getTitle()); + } else if (!dump_output_immediately) { + StrAllocCopy(curdoc.title, newdoc.title); + } + StrAllocCopy(owner_address, HText_getOwner()); + curdoc.safe = HTLoadedDocumentIsSafe(); + if (!dump_output_immediately) { + LYAddVisitedLink(&curdoc); + } + + /* + * Reset WWW present mode so that if we were getting the source, we + * get rendered HTML from now on. + */ + HTOutputFormat = WWW_PRESENT; +#ifdef USE_PRETTYSRC + psrc_view = FALSE; +#endif + + HTMLSetCharacterHandling(current_char_set); /* restore, for sure? */ + + /* + * Reset all of the other relevant flags. - FM + */ + LYUserSpecifiedURL = FALSE; /* only set for goto's and jumps's */ + LYJumpFileURL = FALSE; /* only set for jump's */ + LYNoRefererForThis = FALSE; /* always reset on return here */ + reloading = FALSE; /* set for RELOAD and NOCACHE keys */ + HEAD_request = FALSE; /* only set for HEAD requests */ + LYPermitURL = FALSE; /* only for LYValidate or check_realm */ + ForcePush = FALSE; /* only set for some PRINT requests. */ + LYforce_HTML_mode = FALSE; + force_old_UCLYhndl_on_reload = FALSE; + popped_doc = FALSE; + pending_form_c = -1; + + } + /* end if (LYforce_no_cache || force_load || are_different(...)) */ + if (dump_output_immediately) { + if (crawl) { + print_crawl_to_fd(stdout, curdoc.address, curdoc.title); + } else if (!dump_links_only) { + print_wwwfile_to_fd(stdout, FALSE, FALSE); + } + CleanupMainLoop(); + return ((dump_server_status >= 400) ? EXIT_FAILURE : EXIT_SUCCESS); + } + + /* + * If the recent_sizechange variable is set to TRUE then the window + * size changed recently. + */ + CheckScreenSize(); + if (recent_sizechange) { + /* + * First we need to make sure the display library - curses, slang, + * whatever - gets notified about the change, and gets a chance to + * update external structures appropriately. Hopefully the + * stop_curses()/start_curses() sequence achieves this, at least if + * the display library has a way to get the new screen size from + * the OS. + * + * However, at least for ncurses, the update of the internal + * structures will come still too late - the changed screen size is + * detected in doupdate(), which would only be called (indirectly + * through the HText_pageDisplay below) after the WINDOW structures + * are already filled based on the old size. So we notify the + * ncurses library directly here. - kw + */ +#if defined(USE_CURSES_RESIZE) + resizeterm(LYlines, LYcols); + wresize(LYwin, LYlines, LYcols); + wgetch(LYtopwindow()); /* eat the KEY_RESIZE */ +#else + stop_curses(); + start_curses(); + LYclear(); +#endif + refresh_screen = TRUE; /* to force a redraw */ + if (HTMainText) /* to REALLY force it... - kw */ + HText_setStale(HTMainText); + recent_sizechange = FALSE; + + LYSetDisplayLines(); + } + + if (www_search_result != -1) { + /* + * This was a WWW search, set the line to the result of the search. + */ + LYSetNewline(www_search_result); + www_search_result = -1; /* reset */ + } + + if (first_file == TRUE) { + /* + * We can never again have the first file. + */ + first_file = FALSE; + + /* + * Set the startrealm, and deal as best we can with preserving + * forced HTML mode for a local startfile. - FM + */ + temp = HTParse(curdoc.address, "", + PARSE_ACCESS + PARSE_HOST + PARSE_PUNCTUATION); + if (isEmpty(temp)) { + StrAllocCopy(startrealm, NO_NOTHING); + } else { + StrAllocCopy(startrealm, temp); + FREE(temp); + if (!(temp = HTParse(curdoc.address, "", + PARSE_PATH + PARSE_PUNCTUATION))) { + LYAddHtmlSep(&startrealm); + } else { + if (forced_HTML_mode && + !dump_output_immediately && + !curdoc.bookmark && + isFILE_URL(curdoc.address) && + strlen(temp) > 1) { + /* + * We forced HTML for a local startfile which is not a + * bookmark file and has a path of at least two + * letters. If it doesn't have a suffix mapped to + * text/html, we'll set the entire path (including the + * lead slash) as a "suffix" mapped to text/html to + * ensure it is always treated as an HTML source file. + * We are counting on a tail match to this full path + * for some other URL fetched during the session having + * too low a probability to worry about, but it could + * happen. - FM + */ + HTAtom *encoding; + + if (HTFileFormat(temp, &encoding, NULL) != WWW_HTML) { + HTSetSuffix(temp, STR_HTML, "8bit", 1.0); + } + } + if ((cp = strrchr(temp, '/')) != NULL) { + *(cp + 1) = '\0'; + StrAllocCat(startrealm, temp); + } + } + } + FREE(temp); + CTRACE((tfp, "Starting realm is '%s'\n\n", startrealm)); + if (traversal) { + /* + * Set up the crawl output stuff. + */ + if (curdoc.address && !lookup_link(curdoc.address)) { + if (!isLYNXIMGMAP(curdoc.address)) + crawl_ok = TRUE; + add_to_table(curdoc.address); + } + /* + * Set up the traversal_host comparison string. + */ + if (StrNCmp((curdoc.address ? curdoc.address : "NULL"), + "http", 4)) { + StrAllocCopy(traversal_host, NO_NOTHING); + } else if (check_realm) { + StrAllocCopy(traversal_host, startrealm); + } else { + temp = HTParse(curdoc.address, "", + PARSE_ACCESS + PARSE_HOST + PARSE_PUNCTUATION); + if (isEmpty(temp)) { + StrAllocCopy(traversal_host, NO_NOTHING); + } else { + StrAllocCopy(traversal_host, temp); + LYAddHtmlSep(&traversal_host); + } + FREE(temp); + } + CTRACE((tfp, "Traversal host is '%s'\n\n", traversal_host)); + } + if (startfile) { + /* + * If homepage was not equated to startfile, make the homepage + * URL the first goto entry. - FM + */ + if (homepage && strcmp(startfile, homepage)) + HTAddGotoURL(homepage); + /* + * If we are not starting up with startfile (e.g., had -book), + * or if we are using the startfile and it has no POST content, + * make the startfile URL a goto entry. - FM + */ + if (strcmp(startfile, newdoc.address) || + newdoc.post_data == NULL) + HTAddGotoURL(startfile); + } + if (TRACE) { + refresh_screen = TRUE; + if (!LYTraceLogFP || trace_mode_flag) { + LYSleepAlert(); + } + } + } +#ifdef USE_SOURCE_CACHE + /* + * If the parse settings have changed since this HText was + * generated, we need to reparse and redraw it. -dsb + * + * Should be configured to avoid shock for experienced lynx users. + * Currently enabled for cached sources only. + */ + if (HTdocument_settings_changed()) { + if (HTcan_reparse_document()) { + HTInfoMsg(gettext("Reparsing document under current settings...")); + reparse_document(); + } else { + /* + * Urk. I have no idea how to recover from a failure here. + * At a guess, I'll try reloading. -dsb + */ + /* currently disabled *** + HTUserMsg(gettext("Reparsing document under current settings...")); + cmd = LYK_RELOAD; + goto new_cmd; + */ + } + } + + if (from_source_cache) { + from_source_cache = FALSE; /* reset */ + curdoc.line = -1; /* so curdoc.line != Newline, see below */ + } +#endif + + /* + * If the curdoc.line is different than Newline then there must have + * been a change since last update. Run HText_pageDisplay() to create + * a fresh screen of text output. + * + * If we got new HTMainText go this way. All display_partial calls + * ends here for final redraw. + */ + if (curdoc.line != LYGetNewline()) { +#ifdef INACTIVE_INPUT_STYLE_VH + textinput_redrawn = FALSE; +#endif + + refresh_screen = FALSE; + + HText_pageDisplay(LYGetNewline(), prev_target->str); + +#ifdef DIRED_SUPPORT + if (lynx_edit_mode && nlinks > 0 && !HTList_isEmpty(tagged)) + showtags(tagged); +#endif /* DIRED_SUPPORT */ + + /* + * Check if there is more info below this page. + */ + more_text = HText_canScrollDown(); + + if (newdoc.link < 0) + goto_line(LYGetNewline()); + LYSetNewline(HText_getTopOfScreen() + 1); + curdoc.line = LYGetNewline(); + + if (curdoc.title == NULL) { + /* + * If we don't yet have a title, try to get it, or set to that + * for newdoc.title. - FM + */ + if (HText_getTitle()) { + StrAllocCopy(curdoc.title, HText_getTitle()); + } else { + StrAllocCopy(curdoc.title, newdoc.title); + } + } + + /* + * If the request is to highlight a link which is counted from the + * start of document, correct the link number: + */ + if (newdoc_link_is_absolute) { + newdoc_link_is_absolute = FALSE; + if (curdoc.line > 1) + newdoc.link -= HText_LinksInLines(HTMainText, 1, + curdoc.line - 1); + } + + if (arrowup) { + /* + * arrowup is set if we just came up from a page below. + */ + curdoc.link = nlinks - 1; + arrowup = FALSE; + } else { + curdoc.link = newdoc.link; + if (curdoc.link >= nlinks) { + curdoc.link = nlinks - 1; + } else if (curdoc.link < 0 && nlinks > 0) { + /* + * We may have popped a doc (possibly in local_dired) which + * didn't have any links when it was pushed, but does have + * links now (e.g., a file was created). Code below + * assumes that curdoc.link is valid and that + * (curdoc.link==-1) only occurs if (nlinks==0) is true. - + * KW + */ + curdoc.link = 0; + } + } + + show_help = FALSE; /* reset */ + newdoc.line = 1; + newdoc.link = 0; + curdoc.line = LYGetNewline(); /* set */ + } else if (newdoc.link < 0) { + newdoc.link = 0; /* ...just in case getfile set this */ + } + + /* + * Refresh the screen if necessary. + */ + if (refresh_screen) { +#if defined(FANCY_CURSES) || defined (USE_SLANG) + if (enable_scrollback) { + LYclear(); + } else { + LYerase(); + } +#else + LYclear(); +#endif /* FANCY_CURSES || USE_SLANG */ + HText_pageDisplay(LYGetNewline(), prev_target->str); + +#ifdef DIRED_SUPPORT + if (lynx_edit_mode && nlinks > 0 && !HTList_isEmpty(tagged)) + showtags(tagged); +#endif /* DIRED_SUPPORT */ + + /* + * Check if there is more info below this page. + */ + more_text = HText_canScrollDown(); + + /* + * Adjust curdoc.link as above; nlinks may have changed, if the + * refresh_screen flag was set as a result of a size change. Code + * below assumes that curdoc.link is valid and that + * (curdoc.link==-1) only occurs if (nlinks==0) is true. - kw + */ + if (curdoc.link >= nlinks) { + curdoc.link = nlinks - 1; + } else if (curdoc.link < 0 && nlinks > 0) { + curdoc.link = 0; + } + + if (user_mode == NOVICE_MODE) + noviceline(more_text); /* print help message */ + refresh_screen = FALSE; + + } + + curlink_is_editable = (BOOLEAN) + (nlinks > 0 && + LinkIsTextLike(curdoc.link)); + + use_last_tfpos = (BOOLEAN) + (curlink_is_editable && + (real_cmd == LYK_LPOS_PREV_LINK || + real_cmd == LYK_LPOS_NEXT_LINK)); + +#ifdef TEXTFIELDS_MAY_NEED_ACTIVATION + if (!textfields_need_activation) + textinput_activated = TRUE; +#endif + +#if defined(WIN_EX) /* 1997/10/08 (Wed) 14:52:06 */ + if (nlinks > 0) { + char *p = "LYNX (unknown link type)"; + + /* Show the URL & kanji code . */ + if (strlen(links[curdoc.link].lname) == 0) { + + if (links[curdoc.link].type == WWW_FORM_LINK_TYPE) { + + switch (links[curdoc.link].l_form->type) { + case F_TEXT_SUBMIT_TYPE: + case F_SUBMIT_TYPE: + case F_IMAGE_SUBMIT_TYPE: + p = "[SUBMIT]"; + break; + case F_PASSWORD_TYPE: + p = "Password"; + break; + case F_OPTION_LIST_TYPE: + p = "Option list"; + break; + case F_CHECKBOX_TYPE: + p = "Check box"; + break; + case F_RADIO_TYPE: + p = "[Radio]"; + break; + case F_RESET_TYPE: + p = "[Reset]"; + break; + case F_TEXT_TYPE: + p = "Text input"; + break; + case F_TEXTAREA_TYPE: + p = "Text input lines"; + break; + default: + break; + } + set_ws_title(p); + } + } else { + if (user_mode == ADVANCED_MODE || user_mode == MINIMAL_MODE) { + p = curdoc.title; + } else { + p = links[curdoc.link].lname; + } + + if (strlen(p) < ((sizeof(sjis_buff) / 2) - 1)) { + strcpy(temp_buff, p); + if (StrChr(temp_buff, '%')) { + HTUnEscape(temp_buff); + } + str_sjis(sjis_buff, temp_buff); + set_ws_title(LYElideString(sjis_buff, 10)); + } + } + } else { + if (strlen(curdoc.address) < sizeof(temp_buff) - 1) { + if (user_mode == ADVANCED_MODE || user_mode == MINIMAL_MODE) { + str_sjis(temp_buff, curdoc.title); + } else { + strcpy(temp_buff, curdoc.address); + } + set_ws_title(HTUnEscape(temp_buff)); + } + } +#endif /* WIN_EX */ + + /* + * Report unread or new mail, if appropriate. + */ + if (check_mail && !no_mail) + LYCheckMail(); + + /* + * If help is not on the screen, then put a message on the screen to + * tell the user other misc info. + */ + if (!show_help) { + show_main_statusline(curdoc.link >= 0 ? &links[curdoc.link] : NULL, + ((curlink_is_editable && + textinput_activated) + ? FOR_INPUT + : FOR_PANEL)); + } else { + show_help = FALSE; + } + + if (nlinks > 0) { + /* + * Highlight current link, unless it is an active text input field. + */ + if (!curlink_is_editable) { + LYhighlight(TRUE, curdoc.link, prev_target->str); +#ifndef INACTIVE_INPUT_STYLE_VH + } else if (!textinput_activated) { + LYhighlight(TRUE, curdoc.link, prev_target->str); +#endif + } + } + + if (traversal) { + /* + * Don't go interactively into forms, or accept keystrokes from the + * user + */ + if (crawl && crawl_ok) { + crawl_ok = FALSE; +#ifdef FNAMES_8_3 + sprintf(cfile, "lnk%05d.dat", crawl_count); +#else + sprintf(cfile, "lnk%08d.dat", crawl_count); +#endif /* FNAMES_8_3 */ + crawl_count = crawl_count + 1; + if ((cfp = LYNewTxtFile(cfile)) != NULL) { + print_crawl_to_fd(cfp, curdoc.address, curdoc.title); + LYCloseOutput(cfp); + } else { +#ifdef UNIX + FILE *fp = (dump_output_immediately + ? stderr + : stdout); + +#else + FILE *fp = stdout; +#endif + if (!dump_output_immediately) + cleanup(); + fprintf(fp, + gettext("Fatal error - could not open output file %s\n"), + cfile); + CleanupMainLoop(); + if (!dump_output_immediately) { + exit_immediately(EXIT_FAILURE); + } + return (EXIT_FAILURE); + } + } + } else { + /* + * Normal, non-traversal handling. + */ + if (curlink_is_editable && + (textinput_activated || pending_form_c != -1)) { + if (pending_form_c != -1) { + real_c = pending_form_c; + pending_form_c = -1; + } else { + /* + * Replace novice lines if in NOVICE_MODE. + */ + if (user_mode == NOVICE_MODE) { + form_noviceline(FormIsReadonly(links[curdoc.link].l_form)); + } + real_c = change_form_link(curdoc.link, + &newdoc, &refresh_screen, + use_last_tfpos, FALSE); + } +#ifdef TEXTFIELDS_MAY_NEED_ACTIVATION + if (textfields_need_activation) + textinput_activated = FALSE; +#ifdef INACTIVE_INPUT_STYLE_VH + textinput_redrawn = FALSE; +#endif +#endif + + c = (real_c == LKC_DONE) ? DO_NOTHING : LKC_TO_C(real_c); + if (c != DO_NOTHING && + peek_mouse_link() != -1 && peek_mouse_link() != -2) + old_c = 0; + if (peek_mouse_link() >= 0 && + LKC_TO_LAC(keymap, real_c) != LYK_CHANGE_LINK) { + do_change_link(); + if ((c == '\n' || c == '\r') && + LinkIsTextLike(curdoc.link) && + !textfields_need_activation) { + c = DO_NOTHING; + } +#ifdef TEXTFIELDS_MAY_NEED_ACTIVATION + } else if (LinkIsTextarea(curdoc.link) + && textfields_need_activation + && !FormIsReadonly(links[curdoc.link].l_form) + && peek_mouse_link() < 0 && + (((LKC_TO_LAC(keymap, real_c) == LYK_NEXT_LINK || +#ifdef TEXTAREA_AUTOGROW + LKC_TO_LAC(keymap, real_c) == LYK_ACTIVATE || +#endif + LKC_TO_LAC(keymap, real_c) == LYK_LPOS_NEXT_LINK || + LKC_TO_LAC(keymap, real_c) == LYK_DOWN_LINK) && + ((curdoc.link < nlinks - 1 && + LinkIsTextarea(curdoc.link + 1) + && (links[curdoc.link].l_form->number == + links[curdoc.link + 1].l_form->number) + && strcmp(links[curdoc.link].l_form->name, + links[curdoc.link + 1].l_form->name) + == 0) || + (curdoc.link == nlinks - 1 && more_text && + HText_TAHasMoreLines(curdoc.link, 1)))) || + ((LKC_TO_LAC(keymap, real_c) == LYK_PREV_LINK || + LKC_TO_LAC(keymap, real_c) == LYK_LPOS_PREV_LINK || + LKC_TO_LAC(keymap, real_c) == LYK_UP_LINK) && + ((curdoc.link > 0 && + LinkIsTextarea(curdoc.link - 1) + && (links[curdoc.link].l_form->number == + links[curdoc.link - 1].l_form->number) && + strcmp(links[curdoc.link].l_form->name, + links[curdoc.link - 1].l_form->name) == 0) + || (curdoc.link == 0 && curdoc.line > 1 && + HText_TAHasMoreLines(curdoc.link, -1)))))) { + textinput_activated = TRUE; +#ifdef TEXTAREA_AUTOGROW + if ((c == '\n' || c == '\r') && + LKC_TO_LAC(keymap, real_c) == LYK_ACTIVATE) + c = LAC_TO_LKC0(LYK_NEXT_LINK); +#endif /* TEXTAREA_AUTOGROW */ +#endif /* TEXTFIELDS_MAY_NEED_ACTIVATION */ + } else + switch (c) { + case '\n': + case '\r': +#ifdef TEXTAREA_AUTOGROW + /* + * If on the bottom line of a TEXTAREA, and the user + * hit the ENTER key, we add a new line/anchor + * automatically, positioning the cursor on it. + * + * If at the bottom of the screen, we effectively + * perform an LYK_DOWN_HALF-like operation, then move + * down to the new line we just added. --KED 02/14/99 + * + * [There is some redundancy and non-standard + * indentation in the monster-if() below. This is + * intentional ... to try and improve the + * "readability" (such as it is). Caveat emptor to + * anyone trying to change it.] + */ + if (LinkIsTextarea(curdoc.link) + && ((curdoc.link == nlinks - 1 && + !(more_text && + HText_TAHasMoreLines(curdoc.link, 1))) + || + ((curdoc.link < nlinks - 1) && + !LinkIsTextarea(curdoc.link + 1)) + || + ((curdoc.link < nlinks - 1) && + (LinkIsTextarea(curdoc.link + 1) + && ((links[curdoc.link].l_form->number != + links[curdoc.link + 1].l_form->number) || + (strcmp(links[curdoc.link].l_form->name, + links[curdoc.link + 1].l_form->name) + != 0)))))) { + + HText_ExpandTextarea(&links[curdoc.link], 1); + + if (links[curdoc.link].ly < display_lines) { + refresh_screen = TRUE; + } else { + LYChgNewline(display_lines / 2); + if (nlinks > 0 && curdoc.link > -1 && + links[curdoc.link].ly > display_lines / 2) { + newdoc.link = curdoc.link; + for (i = 0; + links[i].ly <= (display_lines / 2); + i++) + --newdoc.link; + newdoc.link++; + } + } +#ifdef TEXTFIELDS_MAY_NEED_ACTIVATION + if (textfields_need_activation) { + textinput_activated = TRUE; + textfields_need_activation = textfields_activation_option; +#ifdef INACTIVE_INPUT_STYLE_VH + textinput_redrawn = TRUE; +#endif + }; +#endif + + } +#endif /* TEXTAREA_AUTOGROW */ + + /* + * Make return in input field (if it was returned by + * change_form_link) act as LYK_NEXT_LINK, independent + * of what key (if any) is mapped to LYK_NEXT_LINK. - + * kw + */ + c = LAC_TO_LKC0(LYK_NEXT_LINK); + break; + default: + + if (old_c != c && old_c != real_c && c != real_c) + real_c = c; + } + } else { +#if defined(TEXTFIELDS_MAY_NEED_ACTIVATION) && defined(INACTIVE_INPUT_STYLE_VH) + if (curlink_is_editable && !textinput_redrawn) { + /*draw the text entry, but don't activate it */ + textinput_redrawn = TRUE; + change_form_link_ex(curdoc.link, + &newdoc, &refresh_screen, + use_last_tfpos, FALSE, TRUE); + if (LYShowCursor) { + LYmove(links[curdoc.link].ly, + ((links[curdoc.link].lx > 0) ? + (links[curdoc.link].lx - 1) : 0)); + } else { + LYHideCursor(); + } + } +#endif /* TEXTFIELDS_MAY_NEED_ACTIVATION && INACTIVE_INPUT_STYLE_VH */ + /* + * Get a keystroke from the user. Save the last keystroke to + * avoid redundant error reporting. + */ + real_c = c = LYgetch(); /* get user input */ + + if (c != last_key) + key_count = 0; + key_count++; + last_key = c; +#ifndef VMS + if (c == 3) { /* ^C */ + /* + * This shouldn't happen. We'll try to deal with whatever + * bug caused it. - FM + */ + signal(SIGINT, cleanup_sig); + old_c = 0; + cmd = LYK_QUIT; + goto new_cmd; + } +#endif /* !VMS */ + if (LKC_HAS_ESC_MOD(c) && EditBinding(c) != LYE_FORM_PASS) { + /* + * If ESC + was read (and not recognized as a + * terminal escape sequence for another key), ignore the + * ESC modifier and act on only if the line editor + * binding would have passed the same ESC-modified + * lynxkeycode back to us if it had been pressed in a text + * input field. Otherwise set interesting part so that it + * will map to 0, to prevent that ESC + acts like + * , which might be unexpected. - kw + */ + c = (c & ~LKC_MASK) | LAC_TO_LKC(0); + } + if (old_c != real_c) { + old_c = 0; + } + } + } + +#ifdef VMS + if (HadVMSInterrupt) { + HadVMSInterrupt = FALSE; + c = DO_NOTHING; + } +#else + CheckScreenSize(); + if (recent_sizechange) { + if (c <= 0) + c = DO_NOTHING; + } +#endif /* VMS */ + + new_keyboard_input: + /* + * A goto point for new input without going back through the getch() + * loop. + */ + if (traversal) { + if ((c = DoTraversal(c, &crawl_ok)) < 0) { + CleanupMainLoop(); + return (EXIT_FAILURE); + } + } + /* traversal */ +#ifdef WIN_EX + if (c == DO_NOTHING) + cmd = LYK_DO_NOTHING; + else +#endif + cmd = LKC_TO_LAC(keymap, c); /* adds 1 to map EOF to 0 */ + +#if defined(DIRED_SUPPORT) && defined(OK_OVERRIDE) + if (lynx_edit_mode && !no_dired_support && LKC_TO_LAC(key_override, c)) + cmd = LKC_TO_LAC(key_override, c); +#endif /* DIRED_SUPPORT && OK_OVERRIDE */ + + real_cmd = cmd; + + /* + * A goto point for new input without going back through the getch() + * loop. + */ + new_cmd: + + force_old_UCLYhndl_on_reload = FALSE; + CTRACE_FLUSH(tfp); + + if (cmd != LYK_UP_LINK && cmd != LYK_DOWN_LINK) + follow_col = -1; + + CTRACE((tfp, "Handling key %d as %s\n", cmd, + ((LYKeycodeToKcmd((LYKeymapCode) cmd) != 0) + ? LYKeycodeToKcmd((LYKeymapCode) cmd)->name + : "unknown"))); + switch (cmd) { + case -1: + HTUserMsg(COMMAND_UNKNOWN); + break; + case 0: /* unmapped character */ + default: + if (curdoc.link >= 0 && curdoc.link < nlinks && + LinkIsTextLike(curdoc.link)) { + +#ifdef TEXTFIELDS_MAY_NEED_ACTIVATION + if (textfields_need_activation) { + show_main_statusline(&links[curdoc.link], FOR_PANEL); +#ifdef INACTIVE_INPUT_STYLE_VH + textinput_redrawn = FALSE; +#endif + } else +#endif + show_main_statusline(&links[curdoc.link], FOR_INPUT); + } else if (more_text) { + HTInfoMsg(MOREHELP); + } else { + HTInfoMsg(HELP); + } + show_help = TRUE; + + if (TRACE) { + sprintf(cfile, "%d", c); + LYaddstr(cfile); /* show the user input */ + cfile[0] = '\0'; + } + break; + + case LYK_COMMAND: + cmd = handle_LYK_COMMAND(&user_input_buffer); + goto new_cmd; + + case LYK_INTERRUPT: + /* + * No network transmission to interrupt - 'til we multithread. + */ + break; + + case LYK_F_LINK_NUM: + c = '\0'; + /* FALLTHRU */ + case LYK_1: /* FALLTHRU */ + case LYK_2: /* FALLTHRU */ + case LYK_3: /* FALLTHRU */ + case LYK_4: /* FALLTHRU */ + case LYK_5: /* FALLTHRU */ + case LYK_6: /* FALLTHRU */ + case LYK_7: /* FALLTHRU */ + case LYK_8: /* FALLTHRU */ + case LYK_9: + handle_LYK_digit(c, &force_load, &old_c, real_c, &try_internal); + break; + + case LYK_SOURCE: /* toggle view source mode */ + handle_LYK_SOURCE(&ownerS_address); + break; + + case LYK_CHANGE_CENTER: /* ^Q */ + + if (no_table_center) { + no_table_center = FALSE; + HTInfoMsg(gettext("TABLE center enable.")); + } else { + no_table_center = TRUE; + HTInfoMsg(gettext("TABLE center disable.")); + } + /* FALLTHRU */ + + case LYK_RELOAD: /* control-R to reload and refresh */ + handle_LYK_RELOAD(real_cmd); + break; + + case LYK_HISTORICAL: /* toggle 'historical' comments parsing */ + handle_LYK_HISTORICAL(); + break; + + case LYK_MINIMAL: /* toggle 'minimal' comments parsing */ + handle_LYK_MINIMAL(); + break; + + case LYK_SOFT_DQUOTES: + handle_LYK_SOFT_DQUOTES(); + break; + + case LYK_SWITCH_DTD: + handle_LYK_SWITCH_DTD(); + break; + + case LYK_QUIT: /* quit */ + if (handle_LYK_QUIT()) { + CleanupMainLoop(); + return (EXIT_SUCCESS); + } + break; + + case LYK_ABORT: /* don't ask the user about quitting */ + CleanupMainLoop(); + return (EXIT_SUCCESS); + + case LYK_NEXT_PAGE: /* next page */ + handle_LYK_NEXT_PAGE(&old_c, real_c); + break; + + case LYK_PREV_PAGE: /* page up */ + handle_LYK_PREV_PAGE(&old_c, real_c); + break; + + case LYK_UP_TWO: + handle_LYK_UP_TWO(&arrowup, &old_c, real_c); + break; + + case LYK_DOWN_TWO: + handle_LYK_DOWN_TWO(&old_c, real_c); + break; + + case LYK_UP_HALF: + handle_LYK_UP_HALF(&arrowup, &old_c, real_c); + break; + + case LYK_DOWN_HALF: + handle_LYK_DOWN_HALF(&old_c, real_c); + break; + +#ifdef CAN_CUT_AND_PASTE + case LYK_TO_CLIPBOARD: /* ^S */ + { + char *s; + int ch2; + + /* The logic resembles one of ADD_BOOKMARK */ + if (nlinks > 0 && links[curdoc.link].lname + && links[curdoc.link].type != WWW_FORM_LINK_TYPE) { + /* Makes sense to copy a link */ + _statusline("Copy D)ocument's or L)ink's URL to clipboard or C)ancel?"); + ch2 = LYgetch_single(); + if (ch2 == 'D') + s = curdoc.address; + else if (ch2 == 'C') + break; + else + s = links[curdoc.link].lname; + } else + s = curdoc.address; + if (isEmpty(s)) + HTInfoMsg(gettext("Current URL is empty.")); + if (put_clip(s)) + HTInfoMsg(gettext("Copy to clipboard failed.")); + else if (s == curdoc.address) + HTInfoMsg(gettext("Document URL put to clipboard.")); + else + HTInfoMsg(gettext("Link URL put to clipboard.")); + } + break; + + case LYK_PASTE_URL: + if (no_goto && !LYValidate) { /* Go to not allowed. - FM */ + HTUserMsg(GOTO_DISALLOWED); + } else { + unsigned char *s = (unsigned char *) get_clip_grab(), *e, *t; + char *buf; + int len2; + + if (!s) + break; + len2 = (int) strlen((const char *) s); + e = s + len2; + while (s < e && StrChr(" \t\n\r", *s)) + s++; + while (s < e && StrChr(" \t\n\r", e[-1])) + e--; + if (s[0] == '<' && e > s && e[-1] == '>') { + s++; + e--; + if (!strncasecomp((const char *) s, "URL:", 4)) + s += 4; + } + if (s >= e) { + HTInfoMsg(gettext("No URL in the clipboard.")); + break; + } + len = (unsigned) (e - s + 1); + if (len < MAX_LINE) + len = MAX_LINE; /* Required for do_check_goto_URL() */ + buf = typeMallocn(char, len); + + LYStrNCpy(buf, (const char *) s, (e - s)); + t = (unsigned char *) buf; + + while (s < e) { + if (StrChr(" \t\n\r", *s)) { + int nl2 = 0; /* Keep whitespace without NL - file names! */ + unsigned char *s1 = s; + + while (StrChr(" \t\n\r", *s)) { + if (!nl2 && *s == '\n') + nl2 = 1; + s++; + } + if (!nl2) { + while (s1 < s) { + if (*s1 != '\r' && *s1 != '\n') + *t = *s1; + t++, s1++; + } + } + } else + *t++ = *s++; + } + *t = '\0'; + get_clip_release(); + BStrCopy0(user_input_buffer, buf); + do_check_goto_URL(&user_input_buffer, &temp, &force_load); + free(buf); + } + break; +#endif + +#ifdef KANJI_CODE_OVERRIDE + case LYK_CHG_KCODE: + if (LYRawMode && (HTCJK == JAPANESE)) { + switch (last_kcode) { + case NOKANJI: + last_kcode = SJIS; + break; + case SJIS: + last_kcode = EUC; + break; + case EUC: + last_kcode = NOKANJI; + break; + default: + break; + } + } + LYmove(0, 0); + lynx_start_title_color(); + LYaddstr(str_kcode(last_kcode)); + lynx_stop_title_color(); + + break; +#endif + + case LYK_REFRESH: + refresh_screen = TRUE; + lynx_force_repaint(); + break; + + case LYK_HOME: + if (curdoc.line > 1) { + LYSetNewline(1); + } else { + cmd = LYK_PREV_PAGE; + goto new_cmd; + } + break; + + case LYK_END: + i = HText_getNumOfLines() - display_lines + 2; + if (i >= 1 && LYGetNewline() != i) { + LYSetNewline(i); /* go to end of file */ + arrowup = TRUE; /* position on last link */ + } else { + cmd = LYK_NEXT_PAGE; + goto new_cmd; + } + break; + + case LYK_FIRST_LINK: + handle_LYK_FIRST_LINK(); + break; + + case LYK_LAST_LINK: + handle_LYK_LAST_LINK(); + break; + + case LYK_PREV_LINK: + case LYK_LPOS_PREV_LINK: + handle_LYK_PREV_LINK(&arrowup, &old_c, real_c); + break; + + case LYK_NEXT_LINK: + case LYK_LPOS_NEXT_LINK: + handle_LYK_NEXT_LINK(c, &old_c, real_c); + break; + + case LYK_FASTFORW_LINK: + handle_LYK_FASTFORW_LINK(&old_c, real_c); + break; + + case LYK_FASTBACKW_LINK: + if (handle_LYK_FASTBACKW_LINK(&cmd, &old_c, real_c)) + goto new_cmd; + break; + + case LYK_UP_LINK: + handle_LYK_UP_LINK(&follow_col, &arrowup, &old_c, real_c); + break; + + case LYK_DOWN_LINK: + handle_LYK_DOWN_LINK(&follow_col, &old_c, real_c); + break; + + case LYK_CHANGE_LINK: + do_change_link(); +#if defined(TEXTFIELDS_MAY_NEED_ACTIVATION) && defined(INACTIVE_INPUT_STYLE_VH) + if (textfields_need_activation) + textinput_redrawn = FALSE; +#endif /* TEXTFIELDS_MAY_NEED_ACTIVATION && INACTIVE_INPUT_STYLE_VH */ + break; + + case LYK_RIGHT_LINK: + handle_LYK_RIGHT_LINK(); + break; + + case LYK_LEFT_LINK: + handle_LYK_LEFT_LINK(); + break; + + case LYK_COOKIE_JAR: /* show the cookie jar */ + if (handle_LYK_COOKIE_JAR(&cmd)) + goto new_cmd; + break; + +#ifdef USE_CACHEJAR + case LYK_CACHE_JAR: /* show the cache jar */ + if (handle_LYK_CACHE_JAR(&cmd)) + goto new_cmd; + break; +#endif + + case LYK_HISTORY: /* show the history page */ + if (handle_LYK_HISTORY(ForcePush)) + break; + + /* FALLTHRU */ + case LYK_PREV_DOC: /* back up a level */ + switch (handle_PREV_DOC(&cmd, &old_c, real_c)) { + case 1: + CleanupMainLoop(); + return (EXIT_SUCCESS); + case 2: + goto new_cmd; + } + break; + + case LYK_NEXT_DOC: /* undo back up a level */ + handle_NEXT_DOC(); + break; + + case LYK_NOCACHE: /* Force submission of form or link with no-cache */ + if (!handle_LYK_NOCACHE(&old_c, real_c)) + break; + + /* FALLTHRU */ + case LYK_ACTIVATE: /* follow a link */ + case LYK_MOUSE_SUBMIT: /* follow a link, submit TEXT_SUBMIT input */ + switch (handle_LYK_ACTIVATE(&c, + cmd, + &try_internal, + &refresh_screen, + &force_load, + real_cmd)) { + case 1: + continue; + case 2: + goto new_keyboard_input; + case 3: + pending_form_c = c; + break; + } + break; + + case LYK_SUBMIT: + handle_LYK_SUBMIT(curdoc.link, &newdoc, &refresh_screen); + break; + + case LYK_RESET: + handle_LYK_RESET(curdoc.link, &refresh_screen); + break; + + case LYK_ELGOTO: /* edit URL of current link and go to it */ + if (handle_LYK_ELGOTO(&ch, &user_input_buffer, &temp, &old_c, real_c)) + do_check_goto_URL(&user_input_buffer, &temp, &force_load); + break; + + case LYK_ECGOTO: /* edit current URL and go to to it */ + if (handle_LYK_ECGOTO(&ch, &user_input_buffer, &temp, &old_c, real_c)) + do_check_goto_URL(&user_input_buffer, &temp, &force_load); + break; + + case LYK_GOTO: /* 'g' to goto a random URL */ + if (handle_LYK_GOTO(&ch, &user_input_buffer, &temp, &recall, + &URLTotal, &URLNum, &FirstURLRecall, &old_c, + real_c)) { + if (do_check_recall(ch, &user_input_buffer, &temp, URLTotal, + &URLNum, recall, &FirstURLRecall)) + do_check_goto_URL(&user_input_buffer, &temp, &force_load); + } + break; + + case LYK_DWIMHELP: /* show context-dependent help file */ + handle_LYK_DWIMHELP(&cshelpfile); + /* FALLTHRU */ + + case LYK_HELP: /* show help file */ + handle_LYK_HELP(&cshelpfile); + break; + + case LYK_INDEX: /* index file */ + handle_LYK_INDEX(&old_c, real_c); + break; + + case LYK_MAIN_MENU: /* return to main screen */ + handle_LYK_MAIN_MENU(&old_c, real_c); + break; + +#ifdef EXP_NESTED_TABLES + case LYK_NESTED_TABLES: + if (handle_LYK_NESTED_TABLES(&cmd)) + goto new_cmd; + break; +#endif + case LYK_OPTIONS: /* options screen */ + if (handle_LYK_OPTIONS(&cmd, &refresh_screen)) + goto new_cmd; + break; + + case LYK_INDEX_SEARCH: /* search for a user string */ + handle_LYK_INDEX_SEARCH(&force_load, ForcePush, &old_c, real_c); + break; + + case LYK_WHEREIS: /* search within the document */ + case LYK_NEXT: /* find the next occurrence in the document */ + case LYK_PREV: /* find the previous occurrence in the document */ + handle_LYK_WHEREIS(cmd, &refresh_screen); + break; + + case LYK_COMMENT: /* reply by mail */ + handle_LYK_COMMENT(&refresh_screen, &owner_address, &old_c, real_c); + break; + +#ifdef DIRED_SUPPORT + case LYK_TAG_LINK: /* tag or untag the current link */ + handle_LYK_TAG_LINK(); + break; + + case LYK_MODIFY: /* rename a file or directory */ + handle_LYK_MODIFY(&refresh_screen); + break; + + case LYK_CREATE: /* create a new file or directory */ + handle_LYK_CREATE(); + break; +#endif /* DIRED_SUPPORT */ + + case LYK_DWIMEDIT: /* context-dependent edit */ + switch (handle_LYK_DWIMEDIT(&cmd, &old_c, real_c)) { + case 1: + continue; + case 2: + goto new_cmd; + } + /* FALLTHRU */ + + case LYK_EDIT: /* edit */ + handle_LYK_EDIT(&old_c, real_c); + break; + + case LYK_DEL_BOOKMARK: /* remove a bookmark file link */ + handle_LYK_DEL_BOOKMARK(&refresh_screen, &old_c, real_c); + break; + +#ifdef DIRED_SUPPORT + case LYK_REMOVE: /* remove files and directories */ + handle_LYK_REMOVE(&refresh_screen); + break; +#endif /* DIRED_SUPPORT */ + +#if defined(DIRED_SUPPORT) && defined(OK_INSTALL) + case LYK_INSTALL: /* install a file into system area */ + handle_LYK_INSTALL(); + break; +#endif /* DIRED_SUPPORT && OK_INSTALL */ + + case LYK_INFO: /* show document info */ + if (handle_LYK_INFO(&cmd)) + goto new_cmd; + break; + + case LYK_EDITTEXTAREA: /* use external editor on a TEXTAREA - KED */ + handle_LYK_EDIT_TEXTAREA(&refresh_screen, &old_c, real_c); + break; + + case LYK_GROWTEXTAREA: /* add new lines to bottom of TEXTAREA - KED */ + handle_LYK_GROW_TEXTAREA(&refresh_screen); + break; + + case LYK_INSERTFILE: /* insert file in TEXTAREA, above cursor - KED */ + handle_LYK_INSERT_FILE(&refresh_screen, &old_c, real_c); + break; + + case LYK_PRINT: /* print the file */ + handle_LYK_PRINT(&ForcePush, &old_c, real_c); + break; + + case LYK_LIST: /* list links in the current document */ + if (handle_LYK_LIST(&cmd)) + goto new_cmd; + break; + +#ifdef USE_ADDRLIST_PAGE + case LYK_ADDRLIST: /* always list URL's (only) */ + if (handle_LYK_ADDRLIST(&cmd)) + goto new_cmd; + break; +#endif /* USE_ADDRLIST_PAGE */ + + case LYK_VLINKS: /* list links visited during the current session */ + if (handle_LYK_VLINKS(&cmd, &newdoc_link_is_absolute)) + goto new_cmd; + break; + + case LYK_TOOLBAR: /* go to Toolbar or Banner in current document */ + handle_LYK_TOOLBAR(&try_internal, &force_load, &old_c, real_c); + break; + +#if defined(DIRED_SUPPORT) || defined(VMS) + case LYK_DIRED_MENU: /* provide full file management menu */ + handle_LYK_DIRED_MENU(&refresh_screen, &old_c, real_c); + break; +#endif /* DIRED_SUPPORT || VMS */ + +#ifdef USE_EXTERNALS + case LYK_EXTERN_LINK: /* use external program on url */ + handle_LYK_EXTERN_LINK(&refresh_screen); + break; + case LYK_EXTERN_PAGE: /* use external program on current page */ + handle_LYK_EXTERN_PAGE(&refresh_screen); + break; +#endif /* USE_EXTERNALS */ + + case LYK_ADD_BOOKMARK: /* add link to bookmark file */ + handle_LYK_ADD_BOOKMARK(&refresh_screen, &old_c, real_c); + break; + + case LYK_VIEW_BOOKMARK: /* v to view home page */ + handle_LYK_VIEW_BOOKMARK(&refresh_screen, &old_c, real_c); + break; + + case LYK_SHELL: /* (!) shell escape */ + handle_LYK_SHELL(&refresh_screen, &old_c, real_c); + break; + + case LYK_DOWNLOAD: + switch (handle_LYK_DOWNLOAD(&cmd, &old_c, real_c)) { + case 1: + continue; + case 2: + goto new_cmd; + } + break; + +#ifdef DIRED_SUPPORT + case LYK_UPLOAD: + handle_LYK_UPLOAD(); + break; +#endif /* DIRED_SUPPORT */ + + case LYK_TRACE_TOGGLE: /* Toggle TRACE mode. */ + handle_LYK_TRACE_TOGGLE(); + break; + + case LYK_TRACE_LOG: /* View TRACE log. */ + handle_LYK_TRACE_LOG(&trace_mode_flag); + break; + + case LYK_IMAGE_TOGGLE: + if (handle_LYK_IMAGE_TOGGLE(&cmd)) + goto new_cmd; + break; + + case LYK_INLINE_TOGGLE: + if (handle_LYK_INLINE_TOGGLE(&cmd)) + goto new_cmd; + break; + + case LYK_RAW_TOGGLE: + if (handle_LYK_RAW_TOGGLE(&cmd)) + goto new_cmd; + break; + + case LYK_HEAD: + if (handle_LYK_HEAD(&cmd)) + goto new_cmd; + break; + + case LYK_TOGGLE_HELP: + handle_LYK_TOGGLE_HELP(); + break; + + case LYK_EDITMAP: + handle_LYK_EDITMAP(&old_c, real_c); + break; + + case LYK_KEYMAP: + handle_LYK_KEYMAP(&vi_keys_flag, &emacs_keys_flag, &old_c, real_c); + break; + + case LYK_JUMP: + if (handle_LYK_JUMP(c, &user_input_buffer, &temp, &recall, + &FirstURLRecall, &URLNum, &URLTotal, &ch, + &old_c, real_c)) { + if (do_check_recall(ch, &user_input_buffer, &temp, URLTotal, + &URLNum, recall, &FirstURLRecall)) + do_check_goto_URL(&user_input_buffer, &temp, &force_load); + } + break; + + case LYK_CLEAR_AUTH: + handle_LYK_CLEAR_AUTH(&old_c, real_c); + break; + + case LYK_DO_NOTHING: /* pretty self explanatory */ + break; +#ifdef SUPPORT_CHDIR + case LYK_CHDIR: + handle_LYK_CHDIR(); + break; + case LYK_PWD: + handle_LYK_PWD(); + break; +#endif +#ifdef USE_CURSES_PADS + case LYK_SHIFT_LEFT: + handle_LYK_SHIFT_LEFT(&refresh_screen, key_count); + break; + case LYK_SHIFT_RIGHT: + handle_LYK_SHIFT_RIGHT(&refresh_screen, key_count); + break; + case LYK_LINEWRAP_TOGGLE: + if (handle_LYK_LINEWRAP_TOGGLE(&cmd, &refresh_screen)) + goto new_cmd; + break; +#else + (void) key_count; +#endif + +#ifdef USE_MAXSCREEN_TOGGLE + case LYK_MAXSCREEN_TOGGLE: + if (handle_LYK_MAXSCREEN_TOGGLE(&cmd)) + goto new_cmd; + break; +#endif + } /* end of BIG switch */ + } +} + +static int are_different(DocInfo *doc1, DocInfo *doc2) +{ + char *cp1, *cp2; + + /* + * Do we have two addresses? + */ + if (!doc1->address || !doc2->address) + return (TRUE); + + /* + * Do they differ in the type of request? + */ + if (doc1->isHEAD != doc2->isHEAD) + return (TRUE); + + /* + * See if the addresses are different, making sure we're not tripped up by + * multiple anchors in the the same document from a POST form. -- FM + */ + cp1 = trimPoundSelector(doc1->address); + cp2 = trimPoundSelector(doc2->address); + /* + * Are the base addresses different? + */ + if (strcmp(doc1->address, doc2->address)) { + restorePoundSelector(cp1); + restorePoundSelector(cp2); + return (TRUE); + } + restorePoundSelector(cp1); + restorePoundSelector(cp2); + + /* + * Do the docs have different contents? + */ + if (doc1->post_data) { + if (doc2->post_data) { + if (!BINEQ(doc1->post_data, doc2->post_data)) + return (TRUE); + } else + return (TRUE); + } else if (doc2->post_data) + return (TRUE); + + /* + * We'll assume the two documents in fact are the same. + */ + return (FALSE); +} + +/* This determines whether two docs are _physically_ different, + * meaning they are "from different files". - kw + */ +static int are_phys_different(DocInfo *doc1, DocInfo *doc2) +{ + char *cp1, *cp2, *ap1 = doc1->address, *ap2 = doc2->address; + + /* + * Do we have two addresses? + */ + if (!doc1->address || !doc2->address) + return (TRUE); + + /* + * Do they differ in the type of request? + */ + if (doc1->isHEAD != doc2->isHEAD) + return (TRUE); + + /* + * Skip over possible LYNXIMGMAP parts. - kw + */ + if (isLYNXIMGMAP(doc1->address)) + ap1 += LEN_LYNXIMGMAP; + if (isLYNXIMGMAP(doc2->address)) + ap2 += LEN_LYNXIMGMAP; + /* + * If there isn't any real URL in doc2->address, but maybe just + * a fragment, doc2 is assumed to be an internal reference in + * the same physical document, so return FALSE. - kw + */ + if (*ap2 == '\0' || *ap2 == '#') + return (FALSE); + + /* + * See if the addresses are different, making sure we're not tripped up by + * multiple anchors in the the same document from a POST form. -- FM + */ + cp1 = trimPoundSelector(doc1->address); + cp2 = trimPoundSelector(doc2->address); + /* + * Are the base addresses different? + */ + if (strcmp(ap1, ap2)) { + restorePoundSelector(cp1); + restorePoundSelector(cp2); + return (TRUE); + } + restorePoundSelector(cp1); + restorePoundSelector(cp2); + + /* + * Do the docs have different contents? + */ + if (doc1->post_data) { + if (doc2->post_data) { + if (!BINEQ(doc1->post_data, doc2->post_data)) + return (TRUE); + } else + return (TRUE); + } else if (doc2->post_data) + return (TRUE); + + /* + * We'll assume the two documents in fact are the same. + */ + return (FALSE); +} + +/* + * Utility for freeing the list of goto URLs. - FM + */ +#ifdef LY_FIND_LEAKS +static void HTGotoURLs_free(void) +{ + LYFreeStringList(Goto_URLs); + Goto_URLs = NULL; +} +#endif + +/* + * Utility for listing Goto URLs, making any repeated URLs the most current in + * the list. - FM + */ +void HTAddGotoURL(char *url) +{ + char *mycopy = NULL; + char *old; + HTList *cur; + + if (isEmpty(url)) + return; + + CTRACE((tfp, "HTAddGotoURL %s\n", url)); + StrAllocCopy(mycopy, url); + + if (!Goto_URLs) { + Goto_URLs = HTList_new(); +#ifdef LY_FIND_LEAKS + atexit(HTGotoURLs_free); +#endif + HTList_addObject(Goto_URLs, mycopy); + return; + } + + cur = Goto_URLs; + while (NULL != (old = (char *) HTList_nextObject(cur))) { + if (!strcmp(old, mycopy)) { + HTList_removeObject(Goto_URLs, old); + FREE(old); + break; + } + } + HTList_addObject(Goto_URLs, mycopy); + + return; +} + +/* + * When help is not on the screen, put a message on the screen to tell the user + * other misc info. + */ +static void show_main_statusline(const LinkInfo *curlink, + int for_what) +{ + /* + * Make sure form novice lines are replaced. + */ + if (user_mode == NOVICE_MODE && for_what != FOR_INPUT) { + noviceline(more_text); + } + + if (HTisDocumentSource()) { + /* + * Currently displaying HTML source. + */ + _statusline(SOURCE_HELP); + + /* + * If we are in forms mode then explicitly tell the user what each kind + * of link is. + */ +#ifdef INDICATE_FORMS_MODE_FOR_ALL_LINKS_ON_PAGE + } else if (lynx_mode == FORMS_LYNX_MODE && nlinks > 0) { +#else +#ifdef NORMAL_NON_FORM_LINK_STATUSLINES_FOR_ALL_USER_MODES + } else if (lynx_mode == FORMS_LYNX_MODE && nlinks > 0 && curlink && + !(curlink->type & WWW_LINK_TYPE)) { +#else + } else if (lynx_mode == FORMS_LYNX_MODE && nlinks > 0 && curlink && + !((user_mode == ADVANCED_MODE || user_mode == MINIMAL_MODE) && + (curlink->type & WWW_LINK_TYPE))) { +#endif /* NORMAL_NON_FORM_LINK_STATUSLINES_FOR_ALL_USER_MODES */ +#endif /* INDICATE_FORMS_MODE_FOR_ALL_LINKS_ON_PAGE */ + if (curlink->type == WWW_FORM_LINK_TYPE) { + show_formlink_statusline(curlink->l_form, for_what); + } else { + statusline(NORMAL_LINK_MESSAGE); + } + + /* + * Let them know if it's an index -- very rare. + */ + if (is_www_index) { + const char *indx = gettext("-index-"); + + LYmove(LYlines - 1, LYcolLimit - (int) strlen(indx)); + lynx_start_reverse(); + LYaddstr(indx); + lynx_stop_reverse(); + } + + } else if ((user_mode == ADVANCED_MODE) && nlinks > 0 && curlink) { + /* + * Show the URL or, for some internal links, the fragment + */ + char *cp = NULL; + + if (curlink->type == WWW_INTERN_LINK_TYPE && + !isLYNXIMGMAP(curlink->lname)) { + cp = findPoundSelector(curlink->lname); + } + if (!cp) + cp = curlink->lname; + status_link(cp, more_text, is_www_index); + } else if ((user_mode == MINIMAL_MODE) && nlinks > 0) { + /* + * no URL + */ + status_link("", more_text, is_www_index); + } else if (is_www_index && more_text) { + char buf[128]; + + sprintf(buf, WWW_INDEX_MORE_MESSAGE, key_for_func(LYK_INDEX_SEARCH)); + _statusline(buf); + } else if (is_www_index) { + char buf[128]; + + sprintf(buf, WWW_INDEX_MESSAGE, key_for_func(LYK_INDEX_SEARCH)); + _statusline(buf); + } else if (more_text) { + if (user_mode == NOVICE_MODE) + _statusline(MORE); + else + _statusline(MOREHELP); + } else if (user_mode != MINIMAL_MODE) { + _statusline(HELP); + } else { + _statusline(""); + } + + /* turn off cursor since now it's probably on statusline -HV */ + /* But not if LYShowCursor is on. -show_cursor may be used as a + * workaround to avoid putting the cursor in the last position, for + * curses implementations or terminals that cannot deal with that + * correctly. - kw */ + if (!LYShowCursor) { + LYHideCursor(); + } +} + +/* + * Public function for redrawing the statusline appropriate for the selected + * link. It should only be called at times when curdoc.link, nlinks, and the + * links[] array are valid. - kw + */ +void repaint_main_statusline(int for_what) +{ + if (curdoc.link >= 0 && curdoc.link < nlinks) + show_main_statusline(&links[curdoc.link], for_what); +} + +static void form_noviceline(int disabled) +{ + LYmove(LYlines - 2, 0); + LYclrtoeol(); + if (!disabled) { + LYaddstr(FORM_NOVICELINE_ONE); + } + LYParkCursor(); + + if (disabled) + return; + if (EditBinding(FROMASCII('\025')) == LYE_ERASE) { + LYaddstr(FORM_NOVICELINE_TWO); + } else if (EditBinding(FROMASCII('\025')) == LYE_DELBL) { + LYaddstr(FORM_NOVICELINE_TWO_DELBL); + } else { + char *temp = NULL; + char *erasekey = fmt_keys(LYKeyForEditAction(LYE_ERASE), -1); + + if (erasekey) { + HTSprintf0(&temp, FORM_NOVICELINE_TWO_VAR, erasekey); + } else { + erasekey = fmt_keys(LYKeyForEditAction(LYE_DELBL), -1); + if (erasekey) + HTSprintf0(&temp, + FORM_NOVICELINE_TWO_DELBL_VAR, erasekey); + } + if (temp) { + LYaddstr(temp); + FREE(temp); + } + FREE(erasekey); + } +} + +static void exit_immediately_with_error_message(int state, int first_file) +{ + char *buf = 0; + char *buf2 = 0; + + if (first_file) { + /* print statusline messages as a hint, if any */ + LYstatusline_messages_on_exit(&buf2); + } + + if (state == NOT_FOUND) { + HTSprintf0(&buf, "%s\n%s %s\n", + NonNull(buf2), + gettext("lynx: Can't access startfile"), + /* + * hack: if we fail in HTAccess.c + * avoid duplicating URL, oh. + */ + (buf2 && strstr(buf2, gettext("Can't Access"))) ? + "" : startfile); + } + + if (state == NULLFILE) { + HTSprintf0(&buf, "%s\n%s\n%s\n", + NonNull(buf2), + gettext("lynx: Start file could not be found or is not text/html or text/plain"), + gettext(" Exiting...")); + } + + FREE(buf2); + + if (!dump_output_immediately) + cleanup(); + + if (buf != 0) { +#ifdef UNIX + if (dump_output_immediately) { + fputs(buf, stderr); + } else +#endif /* UNIX */ + { + SetOutputMode(O_TEXT); + fputs(buf, stdout); + SetOutputMode(O_BINARY); + } + + FREE(buf); + } + + if (!dump_output_immediately) { + exit_immediately(EXIT_FAILURE); + } + /* else: return(EXIT_FAILURE) in mainloop */ +} + +static void status_link(const char *curlink_name, + int show_more, + int show_indx) +{ +#define MAX_STATUS (LYcolLimit - 1) +#define MIN_STATUS 0 + char format[MAX_LINE]; + int prefix = 0; + int length; + + *format = 0; + if (show_more && !nomore) { + sprintf(format, "%.*s ", + (int) (sizeof(format) - 2), + gettext("-more-")); + prefix = (int) strlen(format); + } + if (show_indx) { + sprintf(format + prefix, "%.*s ", + ((int) sizeof(format) - prefix - 2), + gettext("-index-")); + } + prefix = (int) strlen(format); + length = (int) strlen(curlink_name); + + if (prefix > MAX_STATUS || prefix >= MAX_LINE - 10) { + _user_message("%s", format); /* no room for url */ + } else { + sprintf(format + prefix, "%%.%ds", MAX_STATUS - prefix); + + if ((length + prefix > MAX_STATUS) && long_url_ok) { + char *buf = NULL; + int cut_from_pos; + int cut_to_pos; + int n; + + StrAllocCopy(buf, curlink_name); + /* + * Scan to find the final leaf of the URL. Ignore trailing '/'. + */ + for (cut_to_pos = length - 2; + (cut_to_pos > 0) && (buf[cut_to_pos] != '/'); + cut_to_pos--) ; + /* + * Jump back to the next leaf to remove. + */ + for (cut_from_pos = cut_to_pos - 4; + (cut_from_pos > 0) && ((buf[cut_from_pos] != '/') + || ((prefix + cut_from_pos + + 4 + + (length - cut_to_pos)) >= MAX_STATUS)); + cut_from_pos--) ; + /* + * Replace some leaves to '...', if possible, and put the final + * leaf at the end. We assume that one can recognize the link from + * at least MIN_STATUS characters. + */ + if (cut_from_pos > MIN_STATUS) { + for (n = 1; n <= 3; n++) + buf[cut_from_pos + n] = '.'; + for (n = 0; cut_to_pos + n <= length; n++) + buf[cut_from_pos + 4 + n] = buf[cut_to_pos + n]; + } + _user_message(format, buf); + CTRACE((tfp, "lastline = %s\n", buf)); /* don't forget to erase me */ + FREE(buf); + } else { /* show (possibly truncated) url */ + _user_message(format, curlink_name); + } + } +} + +const char *LYDownLoadAddress(void) +{ + return NonNull(newdoc.address); +} diff --git a/src/LYMainLoop.h b/src/LYMainLoop.h new file mode 100644 index 0000000..bd2926a --- /dev/null +++ b/src/LYMainLoop.h @@ -0,0 +1,34 @@ +#ifndef LYMAINLOOP_H +#define LYMAINLOOP_H + +#ifndef HTUTILS_H +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif +#ifdef DISP_PARTIAL + extern BOOL LYMainLoop_pageDisplay(int line_num); +#endif + + extern BOOLEAN LYOpenTraceLog(void); + extern const char *LYDownLoadAddress(void); + extern int LYGetNewline(void); + extern int mainloop(void); + extern void HTAddGotoURL(char *url); + extern void LYChgNewline(int adjust); + extern void LYCloseTracelog(void); + extern void LYSetNewline(int value); + extern void handle_LYK_TRACE_TOGGLE(void); + extern void handle_LYK_WHEREIS(int cmd, BOOLEAN *refresh_screen); + extern void repaint_main_statusline(int for_what); + +#ifdef SUPPORT_CHDIR + extern void handle_LYK_CHDIR(void); +#endif + +#ifdef __cplusplus +} +#endif +#endif /* LYMAINLOOP_H */ diff --git a/src/LYMap.c b/src/LYMap.c new file mode 100644 index 0000000..29b60f1 --- /dev/null +++ b/src/LYMap.c @@ -0,0 +1,646 @@ +/* + * $LynxId: LYMap.c,v 1.50 2018/03/05 22:32:14 tom Exp $ + * Lynx Client-side Image MAP Support LYMap.c + * ================================== + * + * Author: FM Foteos Macrides (macrides@sci.wfbr.edu) + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef DIRED_SUPPORT +#include +#include +#endif + +#include +#include + +#define NO_MAP_TITLE "[USEMAP]" + +typedef struct _LYMapElement { + char *address; + char *title; + BOOLEAN intern_flag; +} LYMapElement; + +typedef struct _LYImageMap { + char *address; + char *title; + HTList *elements; +} LYImageMap; + +static HTList *LynxMaps = NULL; + +BOOL LYMapsOnly = FALSE; + +/* + * Utility for freeing a list of MAPs. + */ +void ImageMapList_free(HTList *theList) +{ + LYImageMap *map; + LYMapElement *element; + HTList *cur = theList; + HTList *current; + + if (!cur) + return; + + while (NULL != (map = (LYImageMap *) HTList_nextObject(cur))) { + FREE(map->address); + FREE(map->title); + if (map->elements) { + current = map->elements; + while (NULL != + (element = (LYMapElement *) HTList_nextObject(current))) { + FREE(element->address); + FREE(element->title); + FREE(element); + } + HTList_delete(map->elements); + map->elements = NULL; + } + FREE(map); + } + HTList_delete(theList); + return; +} + +#ifdef LY_FIND_LEAKS +/* + * Utility for freeing the global list of MAPs. - kw + */ +static void LYLynxMaps_free(void) +{ + ImageMapList_free(LynxMaps); + LynxMaps = NULL; + return; +} +#endif /* LY_FIND_LEAKS */ + +/* + * We keep two kinds of lists: + * - A global list (LynxMaps) shared by MAPs from all documents that + * do not have POST data. + * - For each response to a POST which contains MAPs, a list specific + * to this combination of URL and post_data. It is kept in the + * HTParentAnchor structure and is freed when the document is removed + * from memory, in the course of normal removal of anchors. + * MAPs from POST responses can only be accessed via internal links, + * i.e., from within the same document (with the same post_data). + * The notion of "same document" is extended, so that LYNXIMGMAP: + * and List Page screens are logically part of the document on which + * they are based. - kw + * + * If track_internal_links is false, only the global list will be used + * for all MAPs. + * + */ + +/* + * Utility for creating an LYImageMap list, if it doesn't exist already, adding + * LYImageMap entry structures if needed, and removing any LYMapElements in a + * pre-existing LYImageMap entry so that it will have only those from AREA tags + * for the current analysis of MAP element content. - FM + */ +BOOL LYAddImageMap(char *address, + char *title, + HTParentAnchor *node_anchor) +{ + LYImageMap *tmp = NULL; + LYImageMap *old = NULL; + HTList *cur = NULL; + HTList *theList = NULL; + HTList *curele = NULL; + LYMapElement *ele = NULL; + + if (isEmpty(address)) + return FALSE; + if (!(node_anchor && node_anchor->address)) + return FALSE; + + /* + * Set theList to either the global LynxMaps list or, if we are associated + * with post data, the specific list. The list is created if it doesn't + * already exist. - kw + */ + if (track_internal_links && node_anchor->post_data) { + /* + * We are handling a MAP element found while parsing node_anchor's + * stream of data, and node_anchor has post_data associated and should + * therefore represent a POST response, so use the specific list. - kw + */ + theList = node_anchor->imaps; + if (!theList) { + theList = node_anchor->imaps = HTList_new(); + } + } else { + if (!LynxMaps) { + LynxMaps = HTList_new(); +#ifdef LY_FIND_LEAKS + atexit(LYLynxMaps_free); +#endif + } + theList = LynxMaps; + } + + if (theList) { + cur = theList; + while (NULL != (old = (LYImageMap *) HTList_nextObject(cur))) { + if (old->address == 0) /* shouldn't happen */ + continue; + if (!strcmp(old->address, address)) { + FREE(old->address); + FREE(old->title); + if (old->elements) { + curele = old->elements; + while (NULL != + (ele = (LYMapElement *) HTList_nextObject(curele))) { + FREE(ele->address); + FREE(ele->title); + FREE(ele); + } + HTList_delete(old->elements); + old->elements = NULL; + } + break; + } + } + } + + tmp = (old != NULL) ? + old : typecalloc(LYImageMap); + if (tmp == NULL) { + outofmem(__FILE__, "LYAddImageMap"); + return FALSE; + } + StrAllocCopy(tmp->address, address); + if (non_empty(title)) + StrAllocCopy(tmp->title, title); + if (tmp != old) + HTList_addObject(theList, tmp); + return TRUE; +} + +/* + * Utility for adding LYMapElement's to LYImageMap's + * in the appropriate list. - FM + */ +BOOL LYAddMapElement(char *map, + char *address, + char *title, + HTParentAnchor *node_anchor, + int intern_flag GCC_UNUSED) +{ + LYMapElement *tmp = NULL; + LYImageMap *theMap = NULL; + HTList *theList = NULL; + HTList *cur = NULL; + + if (isEmpty(map) || isEmpty(address)) + return FALSE; + if (!(node_anchor && node_anchor->address)) + return FALSE; + + /* + * Set theList to either the global LynxMaps list or, if we are associated + * with post data, the specific list. The list should already exist, since + * this function is only called if the AREA tag we are handling was within + * a MAP element in node_anchor's stream of data, so that LYAddImageMap has + * been called. - kw + */ + if (track_internal_links && node_anchor->post_data) { + /* + * We are handling an AREA tag found while parsing node_anchor's stream + * of data, and node_anchor has post_data associated and should + * therefore represent a POST response, so use the specific list. - kw + */ + theList = node_anchor->imaps; + if (!theList) { + return FALSE; + } + } else { + if (!LynxMaps) + LYAddImageMap(map, NULL, node_anchor); + theList = LynxMaps; + } + + cur = theList; + while (NULL != (theMap = (LYImageMap *) HTList_nextObject(cur))) { + if (!strcmp(theMap->address, map)) { + break; + } + } + if (!theMap) + return FALSE; + if (!theMap->elements) + theMap->elements = HTList_new(); + cur = theMap->elements; + while (NULL != (tmp = (LYMapElement *) HTList_nextObject(cur))) { + if (!strcmp(tmp->address, address)) { + FREE(tmp->address); + FREE(tmp->title); + HTList_removeObject(theMap->elements, tmp); + FREE(tmp); + break; + } + } + + tmp = typecalloc(LYMapElement); + if (tmp == NULL) { + perror("Out of memory in LYAddMapElement"); + return FALSE; + } + StrAllocCopy(tmp->address, address); + if (non_empty(title)) + StrAllocCopy(tmp->title, title); + else + StrAllocCopy(tmp->title, address); + if (track_internal_links) + tmp->intern_flag = (BOOLEAN) intern_flag; + HTList_appendObject(theMap->elements, tmp); + + CTRACE((tfp, + "LYAddMapElement\n\tmap %s\n\taddress %s\n\ttitle %s)\n", + NonNull(map), NonNull(address), NonNull(title))); + + return TRUE; +} + +/* + * Utility for checking whether an LYImageMap entry with a given address + * already exists in the LynxMaps structure. - FM + */ +BOOL LYHaveImageMap(char *address) +{ + LYImageMap *Map; + HTList *cur = LynxMaps; + + if (!(cur && non_empty(address))) + return FALSE; + + while (NULL != (Map = (LYImageMap *) HTList_nextObject(cur))) { + if (!strcmp(Map->address, address)) { + return TRUE; + } + } + + return FALSE; +} + +/* + * Fills in a DocAddress structure for getting the HTParentAnchor of the + * underlying resource. ALso returns a pointer to that anchor in + * *punderlying if we are dealing with POST data. - kw + * + * address is the address of the underlying resource, i.e., the one + * containing the MAP element, the MAP's name appended as + * fragment is ignored. + * anAnchor is the LYNXIMGMAP: anchor; if it is associated with POST + * data, we want the specific list, otherwise the global list. + */ +static void fill_DocAddress(DocAddress *wwwdoc, + const char *address, + HTParentAnchor *anAnchor, + HTParentAnchor **punderlying) +{ + char *doc_address = NULL; + HTParentAnchor *underlying; + + StrAllocCopy(doc_address, address); + if (anAnchor && anAnchor->post_data) { + wwwdoc->address = doc_address; + wwwdoc->post_data = anAnchor->post_data; + wwwdoc->post_content_type = anAnchor->post_content_type; + wwwdoc->bookmark = NULL; + wwwdoc->isHEAD = FALSE; + wwwdoc->safe = FALSE; + underlying = HTAnchor_findAddress(wwwdoc); + if (underlying->safe) + wwwdoc->safe = TRUE; + if (punderlying) + *punderlying = underlying; + } else { + wwwdoc->address = doc_address; + wwwdoc->post_data = NULL; + wwwdoc->post_content_type = NULL; + wwwdoc->bookmark = NULL; + wwwdoc->isHEAD = FALSE; + wwwdoc->safe = FALSE; + if (punderlying) + *punderlying = NULL; + } +} + +/* + * Get the appropriate list for creating a LYNXIMGMAP: pseudo- document: + * either the global list (LynxMaps), or the specific list if a List Page for a + * POST response is requested. Also fill in the DocAddress structure etc. by + * calling fill_DocAddress(). + * + * address is the address of the underlying resource, i.e., the one + * containing the MAP element, the MAP's name appended as + * fragment is ignored. + * anchor is the LYNXIMGMAP: anchor for which LYLoadIMGmap() is + * requested; if it is associated with POST data, we want the + * specific list for this combination of address+post_data. + * + * if track_internal_links is false, the Anchor passed to + * LYLoadIMGmap() will never have post_data, so that the global list + * will be used. - kw + */ +static HTList *get_the_list(DocAddress *wwwdoc, + const char *address, + HTParentAnchor *anchor, + HTParentAnchor **punderlying) +{ + HTList *result; + + if (anchor->post_data) { + fill_DocAddress(wwwdoc, address, anchor, punderlying); + if (non_empty(punderlying)) { + result = (*punderlying)->imaps; + } else { + result = anchor->imaps; + } + } else { + fill_DocAddress(wwwdoc, address, NULL, punderlying); + result = LynxMaps; + } + return result; +} + +/* LYLoadIMGmap - F.Macrides (macrides@sci.wfeb.edu) + * ------------ + * Create a text/html stream with a list of links + * for HyperText References in AREAs of a MAP. + */ + +static int LYLoadIMGmap(const char *arg, + HTParentAnchor *anAnchor, + HTFormat format_out, + HTStream *sink) +{ + HTFormat format_in = WWW_HTML; + HTStream *target = NULL; + char *buf = NULL; + LYMapElement *tmp = NULL; + LYImageMap *theMap = NULL; + char *MapTitle = NULL; + char *MapAddress = NULL; + HTList *theList; + HTList *cur = NULL; + const char *address = NULL; + char *cp = NULL; + DocAddress WWWDoc; + HTParentAnchor *underlying; + BOOL old_cache_setting = LYforce_no_cache; + BOOL old_reloading = reloading; + HTFormat old_format_out = HTOutputFormat; + + if (isLYNXIMGMAP(arg)) { + address = (arg + LEN_LYNXIMGMAP); + } + if (!(address && StrChr(address, ':'))) { + HTAlert(MISDIRECTED_MAP_REQUEST); + return (HT_NOT_LOADED); + } + + theList = get_the_list(&WWWDoc, address, anAnchor, &underlying); + if (WWWDoc.safe) + anAnchor->safe = TRUE; + + if (!theList) { + if (anAnchor->post_data && !WWWDoc.safe && + ((underlying && underlying->document && !LYforce_no_cache) || + HTConfirm(CONFIRM_POST_RESUBMISSION) != TRUE)) { + HTAlert(FAILED_MAP_POST_REQUEST); + return (HT_NOT_LOADED); + } + LYforce_no_cache = TRUE; + reloading = TRUE; + HTOutputFormat = WWW_PRESENT; + LYMapsOnly = TRUE; + if (!HTLoadAbsolute(&WWWDoc)) { + LYforce_no_cache = old_cache_setting; + reloading = old_reloading; + HTOutputFormat = old_format_out; + LYMapsOnly = FALSE; + HTAlert(MAP_NOT_ACCESSIBLE); + return (HT_NOT_LOADED); + } + LYforce_no_cache = old_cache_setting; + reloading = old_reloading; + HTOutputFormat = old_format_out; + LYMapsOnly = FALSE; + theList = get_the_list(&WWWDoc, address, anAnchor, &underlying); + } + + if (!theList) { + HTAlert(MAPS_NOT_AVAILABLE); + return (HT_NOT_LOADED); + } + + cur = theList; + while (NULL != (theMap = (LYImageMap *) HTList_nextObject(cur))) { + if (!strcmp(theMap->address, address)) { + break; + } + } + if (theMap && HTList_count(theMap->elements) == 0) { + /* + * We found a MAP without any usable AREA. Fake a redirection to the + * address with fragment. We do this even for post data (internal link + * within a document with post data) if it will not result in an + * unwanted network request. - kw + */ + if (!anAnchor->post_data) { + StrAllocCopy(redirecting_url, address); + return (HT_REDIRECTING); + } else if (WWWDoc.safe || + (underlying->document && !anAnchor->document && + (LYinternal_flag || LYoverride_no_cache))) { + StrAllocCopy(redirecting_url, address); + redirect_post_content = TRUE; + return (HT_REDIRECTING); + } + } + if (!(theMap && theMap->elements)) { + if (anAnchor->post_data && !WWWDoc.safe && + ((underlying && underlying->document && !LYforce_no_cache) || + HTConfirm(CONFIRM_POST_RESUBMISSION) != TRUE)) { + HTAlert(FAILED_MAP_POST_REQUEST); + return (HT_NOT_LOADED); + } + LYforce_no_cache = TRUE; + reloading = TRUE; + HTOutputFormat = WWW_PRESENT; + LYMapsOnly = TRUE; + if (!HTLoadAbsolute(&WWWDoc)) { + LYforce_no_cache = old_cache_setting; + reloading = old_reloading; + HTOutputFormat = old_format_out; + LYMapsOnly = FALSE; + HTAlert(MAP_NOT_ACCESSIBLE); + return (HT_NOT_LOADED); + } + LYforce_no_cache = old_cache_setting; + reloading = old_reloading; + HTOutputFormat = old_format_out; + LYMapsOnly = FALSE; + cur = get_the_list(&WWWDoc, address, anAnchor, &underlying); + while (NULL != (theMap = (LYImageMap *) HTList_nextObject(cur))) { + if (!strcmp(theMap->address, address)) { + break; + } + } + if (!(theMap && theMap->elements)) { + HTAlert(MAP_NOT_AVAILABLE); + return (HT_NOT_LOADED); + } + } + if (track_internal_links) + anAnchor->no_cache = TRUE; + + target = HTStreamStack(format_in, + format_out, + sink, anAnchor); + + if (target == NULL) { + HTSprintf0(&buf, CANNOT_CONVERT_I_TO_O, + HTAtom_name(format_in), HTAtom_name(format_out)); + HTAlert(buf); + FREE(buf); + return (HT_NOT_LOADED); + } + + if (non_empty(theMap->title)) { + StrAllocCopy(MapTitle, theMap->title); + } else if (non_empty(anAnchor->title)) { + StrAllocCopy(MapTitle, anAnchor->title); + } else if (non_empty(LYRequestTitle) && + strcasecomp(LYRequestTitle, NO_MAP_TITLE)) { + StrAllocCopy(MapTitle, LYRequestTitle); + } else if ((cp = StrChr(address, '#')) != NULL) { + StrAllocCopy(MapTitle, (cp + 1)); + } + if (isEmpty(MapTitle)) { + StrAllocCopy(MapTitle, NO_MAP_TITLE); + } else { + LYEntify(&MapTitle, TRUE); + } + +#define PUTS(buf) (*target->isa->put_block)(target, buf, (int) strlen(buf)) + + HTSprintf0(&buf, "\n\n"); + PUTS(buf); + HTSprintf0(&buf, "\n", + "http-equiv=\"content-type\"", + LYCharSet_UC[current_char_set].MIMEname); + PUTS(buf); + /* + * This page is a list of titles and anchors for them. Since titles + * already passed SGML/HTML stage they are converted to current_char_set. + * That is why we insist on META charset for this page. + */ + HTSprintf0(&buf, "%s\n", MapTitle); + PUTS(buf); + HTSprintf0(&buf, "\n\n"); + PUTS(buf); + + HTSprintf0(&buf, "

          %s

          \n", MapTitle); + PUTS(buf); + + StrAllocCopy(MapAddress, address); + LYEntify(&MapAddress, FALSE); + HTSprintf0(&buf, "

          MAP: %s

          \n", MapAddress); + PUTS(buf); + + HTSprintf0(&buf, "<%s compact>\n", ((keypad_mode == NUMBERS_AS_ARROWS) ? + "ol" : "ul")); + PUTS(buf); + cur = theMap->elements; + while (NULL != (tmp = (LYMapElement *) HTList_nextObject(cur))) { + StrAllocCopy(MapAddress, tmp->address); + LYEntify(&MapAddress, FALSE); + PUTS("
        2. intern_flag) { + PUTS(" TYPE=\"internal link\""); + } + PUTS("\n>"); + LYformTitle(&MapTitle, tmp->title); + LYEntify(&MapTitle, TRUE); + PUTS(MapTitle); + PUTS("\n"); + } + HTSprintf0(&buf, "\n\n\n", + ((keypad_mode == NUMBERS_AS_ARROWS) + ? "ol" + : "ul")); + PUTS(buf); + + (*target->isa->_free) (target); + FREE(MapAddress); + FREE(MapTitle); + FREE(buf); + return (HT_LOADED); +} + +void LYPrintImgMaps(FILE *fp) +{ + const char *only = HTLoadedDocumentURL(); + size_t only_len = strlen(only); + HTList *outer = LynxMaps; + HTList *inner; + LYImageMap *map; + LYMapElement *elt; + int count; + + if (HTList_count(outer) > 0) { + while (NULL != (map = (LYImageMap *) HTList_nextObject(outer))) { + if (only_len != 0) { + if (StrNCmp(only, map->address, only_len) + || (map->address[only_len] != '\0' + && map->address[only_len] != '#')) { + continue; + } + } + fprintf(fp, "\n%s\n", isEmpty(map->title) ? NO_MAP_TITLE : map->title); + fprintf(fp, "%s\n", map->address); + inner = map->elements; + count = 0; + while (NULL != (elt = (LYMapElement *) HTList_nextObject(inner))) { + fprintf(fp, "%4d. %s", ++count, elt->address); + if (track_internal_links && elt->intern_flag) + fprintf(fp, " TYPE=\"internal link\""); + fprintf(fp, "\n"); + } + } + } +} + +#ifdef GLOBALDEF_IS_MACRO +#define _LYIMGMAP_C_GLOBALDEF_1_INIT { "LYNXIMGMAP", LYLoadIMGmap, 0} +GLOBALDEF(HTProtocol, LYLynxIMGmap, _LYIMGMAP_C_GLOBALDEF_1_INIT); +#else +GLOBALDEF HTProtocol LYLynxIMGmap = +{"LYNXIMGMAP", LYLoadIMGmap, 0}; +#endif /* GLOBALDEF_IS_MACRO */ diff --git a/src/LYMap.h b/src/LYMap.h new file mode 100644 index 0000000..7c0b9ff --- /dev/null +++ b/src/LYMap.h @@ -0,0 +1,29 @@ +/* $LynxId: LYMap.h,v 1.12 2010/09/25 11:35:42 tom Exp $ */ +#ifndef LYMAP_H +#define LYMAP_H + +#ifndef HTUTILS_H +#include +#endif + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + extern BOOL LYMapsOnly; + + extern void ImageMapList_free(HTList *list); + extern void LYPrintImgMaps(FILE *fp); + extern BOOL LYAddImageMap(char *address, char *title, + HTParentAnchor *node_anchor); + extern BOOL LYAddMapElement(char *map, char *address, char *title, + HTParentAnchor *node_anchor, + int intern_flag); + extern BOOL LYHaveImageMap(char *address); + +#ifdef __cplusplus +} +#endif +#endif /* LYMAP_H */ diff --git a/src/LYNews.c b/src/LYNews.c new file mode 100644 index 0000000..bb49289 --- /dev/null +++ b/src/LYNews.c @@ -0,0 +1,509 @@ +/* + * $LynxId: LYNews.c,v 1.62 2018/03/18 18:51:02 tom Exp $ + */ +#include +#ifndef DISABLE_NEWS +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +/* + * Global variable for async i/o. + */ +BOOLEAN term_message = FALSE; +static void terminate_message(int sig); + +static BOOLEAN message_has_content(const char *filename, + BOOLEAN *nonspaces) +{ + FILE *fp; + char *buffer = NULL; + BOOLEAN in_headers = TRUE; + + *nonspaces = FALSE; + + if (!filename || (fp = fopen(filename, "r")) == NULL) { + CTRACE((tfp, "Failed to open file %s for reading!\n", + NONNULL(filename))); + return FALSE; + } + while (LYSafeGets(&buffer, fp) != NULL) { + char *cp = buffer; + char firstnonblank = '\0'; + + LYTrimNewline(cp); + for (; *cp; cp++) { + if (!firstnonblank && isgraph(UCH(*cp))) { + firstnonblank = *cp; + } else if (!isspace(UCH(*cp))) { + *nonspaces = TRUE; + } + } + if (firstnonblank && firstnonblank != '>') { + if (!in_headers) { + LYCloseInput(fp); + FREE(buffer); + return TRUE; + } + } + if (!firstnonblank) { + in_headers = FALSE; + } + } + FREE(buffer); + LYCloseInput(fp); + return FALSE; +} + +/* + * This function is called from HTLoadNews() to have the user + * create a file with news headers and a body for posting of + * a new message (based on a newspost://nntp_host/newsgroups + * or snewspost://secure_nntp_host/newsgroups URL), or to post + * a followup (based on a newsreply://nntp_host/newsgroups or + * snewsreply://secure_nntp_host/newsgroups URL). The group + * or comma-separated list of newsgroups is passed without + * a lead slash, and followup is TRUE for newsreply or + * snewsreply URLs. - FM + */ +char *LYNewsPost(char *newsgroups, + int followup) +{ + char user_input[MAX_LINE]; + char CJKinput[MAX_LINE]; + char *cp = NULL; + const char *kp = NULL; + int c = 0; /* user input */ + int len; + FILE *fd = NULL; + char my_tempfile[LY_MAXPATH]; + FILE *fc = NULL; + char CJKfile[LY_MAXPATH]; + char *postfile = NULL; + char *NewsGroups = NULL; + char *References = NULL; + char *org = NULL; + FILE *fp = NULL; + BOOLEAN nonempty = FALSE; + BOOLEAN nonspaces = FALSE; + + /* + * Make sure a non-zero length newspost, newsreply, snewspost or snewsreply + * path was sent to us. - FM + */ + if (isEmpty(newsgroups)) + return (postfile); + + /* + * Return immediately if we do get called, maybe by some quirk of HTNews.c, + * when we shouldn't. - kw + */ + if (no_newspost) + return (postfile); + + /* + * Open a temporary file for the headers and message body. - FM + */ +#ifdef __DJGPP__ + if ((fd = LYOpenTemp(my_tempfile, HTML_SUFFIX, BIN_W)) == NULL) +#else + if ((fd = LYOpenTemp(my_tempfile, HTML_SUFFIX, "w")) == NULL) +#endif /* __DJGPP__ */ + { + HTAlert(CANNOT_OPEN_TEMP); + return (postfile); + } + + /* + * If we're using a Japanese display character set, open a temporary file + * for a conversion to JIS. - FM + */ + CJKfile[0] = '\0'; + if (current_char_set == UCGetLYhndl_byMIME("euc-jp") || + current_char_set == UCGetLYhndl_byMIME("shift_jis")) { + if ((fc = LYOpenTemp(CJKfile, HTML_SUFFIX, "w")) == NULL) { + HTAlert(CANNOT_OPEN_TEMP); + (void) LYRemoveTemp(my_tempfile); + return (postfile); + } + } + + /* + * The newsgroups could be a comma-seperated list. It need not have + * spaces, but deal with any that may also have been hex escaped. - FM + */ + StrAllocCopy(NewsGroups, newsgroups); + if ((cp = strstr(NewsGroups, ";ref="))) { + *cp = '\0'; + cp += 5; + if (*cp == '<') { + StrAllocCopy(References, cp); + } else { + StrAllocCopy(References, "<"); + StrAllocCat(References, cp); + StrAllocCat(References, ">"); + } + HTUnEscape(References); + if (!((cp = StrChr(References, '@')) && cp > References + 1 && + isalnum(UCH(cp[1])))) { + FREE(References); + } + } + HTUnEscape(NewsGroups); + if (!*NewsGroups) { + LYCloseTempFP(fd); /* Close the temp file. */ + goto cleanup; + } + + /* + * Allow ^C to cancel the posting, i.e., don't let SIGINTs exit Lynx. + */ + signal(SIGINT, terminate_message); + term_message = FALSE; + + /* + * Show the list of newsgroups. - FM + */ + LYclear(); + LYmove(2, 0); + scrollok(LYwin, TRUE); /* Enable scrolling. */ + LYaddstr(gettext("You will be posting to:")); + LYaddstr("\n\t"); + LYaddstr(NewsGroups); + LYaddch('\n'); + + /* + * Get the mail address for the From header, offering personal_mail_address + * as default. + */ + LYaddstr(gettext("\n\n Please provide your mail address for the From: header\n")); + sprintf(user_input, "From: %.*s", (int) sizeof(user_input) - 8, + NonNull(personal_mail_address)); + if (LYGetStr(user_input, FALSE, + sizeof(user_input), NORECALL) < 0 || + term_message) { + HTInfoMsg(NEWS_POST_CANCELLED); + LYCloseTempFP(fd); /* Close the temp file. */ + scrollok(LYwin, FALSE); /* Stop scrolling. */ + goto cleanup; + } + fprintf(fd, "%s\n", user_input); + + /* + * Get the Subject header, offering the current document's title as the + * default if this is a followup rather than a new post. - FM + */ + LYaddstr(gettext("\n\n Please provide or edit the Subject: header\n")); + strcpy(user_input, "Subject: "); + if ((followup == TRUE && nhist > 0) && + (kp = HText_getTitle()) != NULL) { + /* + * Add the default subject. + */ + kp = LYSkipCBlanks(kp); +#ifdef CJK_EX /* 1998/05/15 (Fri) 09:10:38 */ + if (HTCJK == JAPANESE) { + CJKinput[0] = '\0'; + switch (kanji_code) { + case EUC: + TO_EUC((const unsigned char *) kp, (unsigned char *) CJKinput); + kp = CJKinput; + break; + case SJIS: + TO_SJIS((const unsigned char *) kp, (unsigned char *) CJKinput); + kp = CJKinput; + break; + default: + break; + } + } +#endif + if (strncasecomp(kp, "Re:", 3)) { + strcat(user_input, "Re: "); + } + len = (int) strlen(user_input); + LYStrNCpy(user_input + len, kp, (int) sizeof(user_input) - len - 1); + } + cp = NULL; + if (LYGetStr(user_input, FALSE, + sizeof(user_input), NORECALL) < 0 || + term_message) { + HTInfoMsg(NEWS_POST_CANCELLED); + LYCloseTempFP(fd); /* Close the temp file. */ + scrollok(LYwin, FALSE); /* Stop scrolling. */ + goto cleanup; + } + fprintf(fd, "%s\n", user_input); + + /* + * Add Organization: header. + */ + StrAllocCopy(cp, "Organization: "); + if ((org = LYGetEnv("ORGANIZATION")) != NULL) { + StrAllocCat(cp, org); + } else if ((org = LYGetEnv("NEWS_ORGANIZATION")) != NULL) { + StrAllocCat(cp, org); + } +#ifdef UNIX + else if ((fp = fopen("/etc/organization", TXT_R)) != NULL) { + char *buffer = 0; + + if (LYSafeGets(&buffer, fp) != NULL) { + if (user_input[0] != '\0') { + LYTrimNewline(buffer); + StrAllocCat(cp, buffer); + } + } + FREE(buffer); + LYCloseInput(fp); + } +#else +#ifdef _WINDOWS /* 1998/05/14 (Thu) 17:47:01 */ + else { + char *p, fname[LY_MAXPATH]; + + strcpy(fname, LynxSigFile); + p = strrchr(fname, '/'); + if (p != 0 && (p - fname) < sizeof(fname) - 15) { + strcpy(p + 1, "LYNX_ETC.TXT"); + if ((fp = fopen(fname, TXT_R)) != NULL) { + if (fgets(user_input, (int) sizeof(user_input), fp) != NULL) { + if ((org = StrChr(user_input, '\n')) != NULL) { + *org = '\0'; + } + if (user_input[0] != '\0') { + StrAllocCat(cp, user_input); + } + } + LYCloseInput(fp); + } + } + } +#endif /* _WINDOWS */ +#endif /* !UNIX */ + LYStrNCpy(user_input, cp, (sizeof(user_input) - 16)); + FREE(cp); + LYaddstr(gettext("\n\n Please provide or edit the Organization: header\n")); + if (LYGetStr(user_input, FALSE, + sizeof(user_input), NORECALL) < 0 || + term_message) { + HTInfoMsg(NEWS_POST_CANCELLED); + LYCloseTempFP(fd); /* Close the temp file. */ + scrollok(LYwin, FALSE); /* Stop scrolling. */ + goto cleanup; + } + fprintf(fd, "%s\n", user_input); + + if (References) { + fprintf(fd, "References: %s\n", References); + } + /* + * Add Newsgroups Summary and Keywords headers. + */ + fprintf(fd, "Newsgroups: %s\nSummary: \nKeywords: \n\n", NewsGroups); + + /* + * Have the user create the message body. + */ + if (!no_editor && non_empty(editor)) { + + if (followup && nhist > 0) { + /* + * Ask if the user wants to include the original message. + */ + if (term_message) { + _statusline(INC_ORIG_MSG_PROMPT); + } else if (HTConfirm(INC_ORIG_MSG_PROMPT) == YES) { + /* + * The 'TRUE' will add the reply ">" in front of every line. + * We're assuming that if the display character set is Japanese + * and the document did not have a CJK charset, any non-EUC or + * non-SJIS 8-bit characters in it where converted to 7-bit + * equivalents. - FM + */ + print_wwwfile_to_fd(fd, FALSE, TRUE); + } + } + LYCloseTempFP(fd); /* Close the temp file. */ + scrollok(LYwin, FALSE); /* Stop scrolling. */ + if (term_message || LYCharIsINTERRUPT(c)) + goto cleanup; + + /* + * Spawn the user's editor on the news file. + */ + edit_temporary_file(my_tempfile, "", SPAWNING_EDITOR_FOR_NEWS); + + nonempty = message_has_content(my_tempfile, &nonspaces); + + } else { + /* + * Use the built in line editior. + */ + LYaddstr(gettext("\n\n Please enter your message below.")); + LYaddstr(gettext("\n When you are done, press enter and put a single period (.)")); + LYaddstr(gettext("\n on a line and press enter again.")); + LYaddstr("\n\n"); + LYrefresh(); + *user_input = '\0'; + if (LYGetStr(user_input, FALSE, + sizeof(user_input), NORECALL) < 0 || + term_message) { + HTInfoMsg(NEWS_POST_CANCELLED); + LYCloseTempFP(fd); /* Close the temp file. */ + scrollok(LYwin, FALSE); /* Stop scrolling. */ + goto cleanup; + } + while (!STREQ(user_input, ".") && !term_message) { + LYaddch('\n'); + fprintf(fd, "%s\n", user_input); + if (!nonempty && strlen(user_input)) + nonempty = TRUE; + *user_input = '\0'; + if (LYGetStr(user_input, FALSE, + sizeof(user_input), NORECALL) < 0) { + HTInfoMsg(NEWS_POST_CANCELLED); + LYCloseTempFP(fd); /* Close the temp file. */ + scrollok(LYwin, FALSE); /* Stop scrolling. */ + goto cleanup; + } + } + fprintf(fd, "\n"); + LYCloseTempFP(fd); /* Close the temp file. */ + scrollok(LYwin, FALSE); /* Stop scrolling. */ + } + + if (nonempty) { + /* + * Confirm whether to post, and if so, whether to append the sig file. + * - FM + */ + LYStatusLine = (LYlines - 1); + c = HTConfirm(POST_MSG_PROMPT); + LYStatusLine = -1; + if (c != YES) { + LYclear(); /* clear the screen */ + goto cleanup; + } + } else { + HTAlert(gettext("Message has no original text!")); + if (!nonspaces + || HTConfirmDefault(POST_MSG_PROMPT, NO) != YES) + goto cleanup; + } + if ((non_empty(LynxSigFile)) && (fp = fopen(LynxSigFile, TXT_R)) != NULL) { + char *msg = NULL; + + HTSprintf0(&msg, APPEND_SIG_FILE, LynxSigFile); + + LYStatusLine = (LYlines - 1); + if (term_message) { + _user_message(APPEND_SIG_FILE, LynxSigFile); + } else if (HTConfirm(msg) == YES) { + if ((fd = LYAppendToTxtFile(my_tempfile)) != NULL) { + char *buffer = NULL; + + fputs("-- \n", fd); + while (LYSafeGets(&buffer, fp) != NULL) { + fputs(buffer, fd); + } + LYCloseOutput(fd); + } + } + LYCloseInput(fp); + FREE(msg); + LYStatusLine = -1; + } + LYclear(); /* clear the screen */ + + /* + * If we are using a Japanese display character set, convert the contents + * of the temp file to JIS (nothing should change if it does not, in fact, + * contain EUC or SJIS di-bytes). Otherwise, use the temp file as is. - + * FM + */ + if (CJKfile[0] != '\0') { + if ((fd = fopen(my_tempfile, TXT_R)) != NULL) { + char *buffer = NULL; + + while (LYSafeGets(&buffer, fd) != NULL) { + TO_JIS((unsigned char *) buffer, + (unsigned char *) CJKinput); + fputs(CJKinput, fc); + } + LYCloseTempFP(fc); + StrAllocCopy(postfile, CJKfile); + LYCloseInput(fd); + (void) LYRemoveTemp(my_tempfile); + strcpy(my_tempfile, CJKfile); + CJKfile[0] = '\0'; + } else { + StrAllocCopy(postfile, my_tempfile); + } + } else { + StrAllocCopy(postfile, my_tempfile); + } + if (!followup) { + /* + * If it's not a followup, the current document most likely is the + * group listing, so force a to have the article show up in the list + * after the posting. Note, that if it's a followup via a link in a + * news article, the user must do a reload manually on returning to the + * group listing. - FM + */ + LYforce_no_cache = TRUE; + } + LYStatusLine = (LYlines - 1); + HTUserMsg(POSTING_TO_NEWS); + LYStatusLine = -1; + + /* + * Come here to cleanup and exit. + */ + cleanup: +#ifndef VMS + signal(SIGINT, cleanup_sig); +#endif /* !VMS */ + term_message = FALSE; + if (!postfile) + (void) LYRemoveTemp(my_tempfile); + (void) LYRemoveTemp(CJKfile); + FREE(NewsGroups); + FREE(References); + + return (postfile); +} + +static void terminate_message(int sig GCC_UNUSED) +{ + term_message = TRUE; + /* + * Reassert the AST. + */ + signal(SIGINT, terminate_message); +#ifdef VMS + /* + * Refresh the screen to get rid of the "interrupt" message. + */ + lynx_force_repaint(); + LYrefresh(); +#endif /* VMS */ +} + +#endif /* not DISABLE_NEWS */ diff --git a/src/LYNews.h b/src/LYNews.h new file mode 100644 index 0000000..9a6a3f6 --- /dev/null +++ b/src/LYNews.h @@ -0,0 +1,19 @@ +/* $LynxId: LYNews.h,v 1.10 2010/09/25 11:35:12 tom Exp $ */ +#ifndef LYNEWSPOST_H +#define LYNEWSPOST_H + +#ifndef LYSTRUCTS_H +#include +#endif /* LYSTRUCTS_H */ + +#ifdef __cplusplus +extern "C" { +#endif + extern BOOLEAN term_message; + + extern char *LYNewsPost(char *newsgroups, int followup); + +#ifdef __cplusplus +} +#endif +#endif /* LYNEWSPOST_H */ diff --git a/src/LYOptions.c b/src/LYOptions.c new file mode 100644 index 0000000..828aacc --- /dev/null +++ b/src/LYOptions.c @@ -0,0 +1,4365 @@ +/* $LynxId: LYOptions.c,v 1.186 2023/01/05 09:17:16 tom Exp $ */ +#include +#include +#include /* 'reloading' flag */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef USE_COLOR_STYLE +#include +#endif + +#include + +BOOLEAN term_options; + +#define TOP_LINK "/" +#define MBM_LINK "//MBM_MENU" + +#define MARGIN_STR (no_margins ? "" : "  ") +#define MARGIN_LEN (no_margins ? 0 : 2) + +static void terminate_options(int sig); + +#define COL_OPTION_VALUES 36 /* display column where option values start */ + +#if defined(USE_SLANG) || defined(COLOR_CURSES) +static BOOLEAN can_do_colors = FALSE; +#endif + +static int LYChosenShowColor = SHOW_COLOR_UNKNOWN; /* whether to show and save */ + +BOOLEAN LYCheckUserAgent(void) +{ + if (non_empty(LYUserAgent)) { + if (strstr(LYUserAgent, "Lynx") == 0 + && strstr(LYUserAgent, "lynx") == 0 + && strstr(LYUserAgent, "L_y_n_x") == 0 + && strstr(LYUserAgent, "l_y_n_x") == 0) { + return FALSE; + } + } + return TRUE; +} + +static void validate_x_display(void) +{ + char *cp; + + if ((cp = LYgetXDisplay()) != NULL) { + StrAllocCopy(x_display, cp); + } else { + FREE(x_display); + } +} + +static void summarize_x_display(char *display_option) +{ + if ((x_display == NULL && *display_option == '\0') || + (x_display != NULL && !strcmp(x_display, display_option))) { + if (x_display == NULL && LYisConfiguredForX == TRUE) { + _statusline(VALUE_ACCEPTED_WARNING_X); + } else if (x_display != NULL && LYisConfiguredForX == FALSE) { + _statusline(VALUE_ACCEPTED_WARNING_NONX); + } else { + _statusline(VALUE_ACCEPTED); + } + } else { + if (*display_option) { + _statusline(FAILED_TO_SET_DISPLAY); + } else { + _statusline(FAILED_CLEAR_SET_DISPLAY); + } + } +} + +#if defined(USE_SLANG) || defined(COLOR_CURSES) +static void SetupChosenShowColor(void) +{ + can_do_colors = TRUE; +#if defined(COLOR_CURSES) + if (LYCursesON) /* could crash if called before initialization */ + can_do_colors = (has_colors() + ? TRUE + : FALSE); +#endif + if (!no_option_save) { + if (LYChosenShowColor == SHOW_COLOR_UNKNOWN) { + switch (LYrcShowColor) { + case SHOW_COLOR_NEVER: + LYChosenShowColor = + (LYShowColor >= SHOW_COLOR_ON) ? + SHOW_COLOR_ON : SHOW_COLOR_NEVER; + break; + case SHOW_COLOR_ALWAYS: + if (!can_do_colors) + LYChosenShowColor = SHOW_COLOR_ALWAYS; + else + LYChosenShowColor = + (LYShowColor >= SHOW_COLOR_ON) ? + SHOW_COLOR_ALWAYS : SHOW_COLOR_OFF; + break; + default: + LYChosenShowColor = + (LYShowColor >= SHOW_COLOR_ON) ? + SHOW_COLOR_ON : SHOW_COLOR_OFF; + } + } + } +} +#endif /* USE_SLANG || COLOR_CURSES */ + +#ifndef NO_OPTION_MENU +static int boolean_choice(int status, + int line, + int column, + STRING2PTR choices); + +#define LYChooseBoolean(status, line, column, choices) \ + (BOOLEAN) boolean_choice(status, line, column, (const char *const*)choices) + +#define LYChooseEnum(status, line, column, choices) \ + boolean_choice(status, line, column, (const char *const*)choices) + +#define MAXCHOICES 10 + +/* + * Values for the options menu. - FM + * + * L_foo values are the Y coordinates for the menu item. + * B_foo values are the X coordinates for the item's prompt string. + * C_foo values are the X coordinates for the item's value string. + */ +#define L_EDITOR 2 +#define L_DISPLAY 3 + +#define L_HOME 4 +#define C_MULTI 24 +#define B_BOOK 34 +#define C_DEFAULT 50 + +#define L_FTPSTYPE 5 +#define L_MAIL_ADDRESS 6 +#define L_SSEARCH 7 +#define L_LANGUAGE 8 +#define L_PREF_CHARSET 9 +#define L_ASSUME_CHARSET (L_PREF_CHARSET + 1) +#define L_CHARSET 10 +#define L_RAWMODE 11 + +#define L_COLOR L_RAWMODE +#define B_COLOR 44 +#define C_COLOR 62 + +#define L_BOOL_A 12 +#define B_VIKEYS 5 +#define C_VIKEYS 15 +#define B_EMACSKEYS 22 +#define C_EMACSKEYS 36 +#define B_SHOW_DOTFILES 44 +#define C_SHOW_DOTFILES 62 + +#define L_BOOL_B 13 +#define B_SELECT_POPUPS 5 +#define C_SELECT_POPUPS 36 +#define B_SHOW_CURSOR 44 +#define C_SHOW_CURSOR 62 + +#define L_KEYPAD 14 +#define L_LINEED 15 +#define L_LAYOUT 16 + +#ifdef DIRED_SUPPORT +#define L_DIRED 17 +#define L_USER_MODE 18 +#define L_USER_AGENT 19 +#define L_EXEC 20 +#else +#define L_USER_MODE 17 +#define L_USER_AGENT 18 +#define L_EXEC 19 +#endif /* DIRED_SUPPORT */ + +#define L_VERBOSE_IMAGES L_USER_MODE +#define B_VERBOSE_IMAGES 50 +#define C_VERBOSE_IMAGES (B_VERBOSE_IMAGES + 21) + +/* a kludge to add assume_charset only in ADVANCED mode... */ +#define L_Bool_A (use_assume_charset ? L_BOOL_A + 1 : L_BOOL_A) +#define L_Bool_B (use_assume_charset ? L_BOOL_B + 1 : L_BOOL_B) +#define L_Exec (use_assume_charset ? L_EXEC + 1 : L_EXEC) +#define L_Rawmode (use_assume_charset ? L_RAWMODE + 1 : L_RAWMODE) +#define L_Charset (use_assume_charset ? L_CHARSET + 1 : L_CHARSET) +#define L_Color (use_assume_charset ? L_COLOR + 1 : L_COLOR) +#define L_Keypad (use_assume_charset ? L_KEYPAD + 1 : L_KEYPAD) +#define L_Lineed (use_assume_charset ? L_LINEED + 1 : L_LINEED) +#define L_Layout (use_assume_charset ? L_LAYOUT + 1 : L_LAYOUT) +#define L_Dired (use_assume_charset ? L_DIRED + 1 : L_DIRED) +#define L_User_Mode (use_assume_charset ? L_USER_MODE + 1 : L_USER_MODE) +#define L_User_Agent (use_assume_charset ? L_USER_AGENT + 1 : L_USER_AGENT) + +#define LPAREN '(' +#define RPAREN ')' + +static int add_it(char *text, int len) +{ + if (len) { + text[len] = '\0'; + LYaddstr(text); + } + return 0; +} + +/* + * addlbl() is used instead of plain LYaddstr() in old-style options menu + * to show hot keys in bold. + */ +static void addlbl(const char *text) +{ + char actual[80]; + int s, d; + BOOL b = FALSE; + + for (s = d = 0; text[s]; s++) { + actual[d++] = text[s]; + if (text[s] == LPAREN) { + d = add_it(actual, d - 1); + lynx_start_bold(); + b = TRUE; + actual[d++] = text[s]; + } else if (text[s] == RPAREN) { + d = add_it(actual, d); + lynx_stop_bold(); + b = FALSE; + } + } + add_it(actual, d); + if (b) + lynx_stop_bold(); +} + +#if !defined(VMS) || defined(USE_SLANG) +#define HANDLE_LYOPTIONS \ + if (term_options) { \ + term_options = FALSE; \ + } else { \ + AddValueAccepted = TRUE; \ + } \ + goto draw_options +#else +#define HANDLE_LYOPTIONS \ + term_options = FALSE; \ + if (use_assume_charset != old_use_assume_charset) \ + goto draw_options +#endif /* !VMS || USE_SLANG */ + +void LYoptions(void) +{ +#define ShowBool(value) LYaddstr((value) ? "ON " : "OFF") + static const char *bool_choices[] = + { + "OFF", + "ON", + NULL + }; + static const char *const caseless_choices[] = + { + "CASE INSENSITIVE", + "CASE SENSITIVE", + NULL + }; + +#ifdef DIRED_SUPPORT + static const char *dirList_choices[] = + { + "Directories first", + "Files first", + "Mixed style", + NULL + }; +#endif + +#if defined(ENABLE_OPTS_CHANGE_EXEC) && (defined(EXEC_LINKS) || defined(EXEC_SCRIPTS)) + static const char *exec_choices[] = + { + "ALWAYS OFF", + "FOR LOCAL FILES ONLY", +#ifndef NEVER_ALLOW_REMOTE_EXEC + "ALWAYS ON", +#endif /* !NEVER_ALLOW_REMOTE_EXEC */ + NULL + }; +#endif + static const char *fileSort_choices[] = + { + "By Filename", + "By Type", + "By Size", + "By Date", + NULL + }; + static const char *keypad_choices[] = + { + "Numbers act as arrows", + "Links are numbered", + "Links and form fields are numbered", + NULL + }; + static const char *mbm_choices[] = + { + "OFF ", + "STANDARD", + "ADVANCED", + NULL + }; + static const char *userMode_choices[] = + { + "Novice", + "Intermediate", + "Advanced", + "Minimal", + NULL + }; + +#if defined(ENABLE_OPTS_CHANGE_EXEC) && (defined(EXEC_LINKS) || defined(EXEC_SCRIPTS)) + int itmp; +#endif /* ENABLE_OPTS_CHANGE_EXEC */ + int response, ch; + + /* + * If the user changes the display we need memory to put it in. + */ + bstring *my_data = NULL; + char *choices[MAXCHOICES]; + int CurrentCharSet = current_char_set; + int CurrentAssumeCharSet = UCLYhndl_for_unspec; + int CurrentShowColor = LYShowColor; + BOOLEAN CurrentRawMode = LYRawMode; + BOOLEAN AddValueAccepted = FALSE; + BOOL use_assume_charset; + +#if defined(VMS) || defined(USE_SLANG) + BOOL old_use_assume_charset; +#endif + +#ifdef DIRED_SUPPORT +#ifdef ENABLE_OPTS_CHANGE_EXEC + if (LYlines < 24) { + HTAlert(OPTION_SCREEN_NEEDS_24); + return; + } +#else + if (LYlines < 23) { + HTAlert(OPTION_SCREEN_NEEDS_23); + return; + } +#endif /* ENABLE_OPTS_CHANGE_EXEC */ +#else +#ifdef ENABLE_OPTS_CHANGE_EXEC + if (LYlines < 23) { + HTAlert(OPTION_SCREEN_NEEDS_23); + return; + } +#else + if (LYlines < 22) { + HTAlert(OPTION_SCREEN_NEEDS_22); + return; + } +#endif /* ENABLE_OPTS_CHANGE_EXEC */ +#endif /* DIRED_SUPPORT */ + + term_options = FALSE; + LYStatusLine = (LYlines - 1); /* screen is otherwise too crowded */ + signal(SIGINT, terminate_options); + if (no_option_save) { + if (LYShowColor == SHOW_COLOR_NEVER) { + LYShowColor = SHOW_COLOR_OFF; + } else if (LYShowColor == SHOW_COLOR_ALWAYS) { + LYShowColor = SHOW_COLOR_ON; + } +#if defined(USE_SLANG) || defined(COLOR_CURSES) + } else { + SetupChosenShowColor(); +#endif /* USE_SLANG || COLOR_CURSES */ + } + + use_assume_charset = (BOOLEAN) (user_mode == ADVANCED_MODE); + + draw_options: + +#if defined(VMS) || defined(USE_SLANG) + old_use_assume_charset = use_assume_charset; +#endif + /* + * NOTE that printw() should be avoided for strings that might have + * non-ASCII or multibyte/CJK characters. - FM + */ +#if defined(FANCY_CURSES) || defined (USE_SLANG) + if (enable_scrollback) { + LYclear(); + } else { + LYerase(); + } +#else + LYclear(); +#endif /* FANCY_CURSES || USE_SLANG */ + LYmove(0, 5); + + lynx_start_h1_color(); + LYaddstr(" Options Menu ("); + LYaddstr(LYNX_NAME); + LYaddstr(" Version "); + LYaddstr(LYNX_VERSION); + LYaddch(')'); + lynx_stop_h1_color(); + LYmove(L_EDITOR, 5); + addlbl("(E)ditor : "); + LYaddstr(non_empty(editor) ? editor : "NONE"); + + LYmove(L_DISPLAY, 5); + addlbl("(D)ISPLAY variable : "); + LYaddstr(non_empty(x_display) ? x_display : "NONE"); + + LYmove(L_HOME, 5); + addlbl("mu(L)ti-bookmarks: "); + LYaddstr(mbm_choices[LYMultiBookmarks]); + LYmove(L_HOME, B_BOOK); + if (LYMultiBookmarks != MBM_OFF) { + addlbl("review/edit (B)ookmarks files"); + } else { + addlbl("(B)ookmark file: "); + LYaddstr(non_empty(bookmark_page) ? bookmark_page : "NONE"); + } + + LYmove(L_FTPSTYPE, 5); + addlbl("(F)TP sort criteria : "); + LYaddstr((HTfileSortMethod == FILE_BY_NAME ? "By Filename" : + (HTfileSortMethod == FILE_BY_SIZE ? "By Size " : + (HTfileSortMethod == FILE_BY_TYPE ? "By Type " : + "By Date ")))); + + LYmove(L_MAIL_ADDRESS, 5); + addlbl("(P)ersonal mail address : "); + LYaddstr(non_empty(personal_mail_address) ? + personal_mail_address : "NONE"); + + LYmove(L_SSEARCH, 5); + addlbl("(S)earching type : "); + LYaddstr(LYcase_sensitive ? "CASE SENSITIVE " : "CASE INSENSITIVE"); + + LYmove(L_Charset, 5); + addlbl("display (C)haracter set : "); + LYaddstr(LYchar_set_names[current_char_set]); + + LYmove(L_LANGUAGE, 5); + addlbl("preferred document lan(G)uage: "); + LYaddstr(non_empty(language) ? language : "NONE"); + + LYmove(L_PREF_CHARSET, 5); + addlbl("preferred document c(H)arset : "); + LYaddstr(non_empty(pref_charset) ? pref_charset : "NONE"); + + if (use_assume_charset) { + LYmove(L_ASSUME_CHARSET, 5); + addlbl("(^A)ssume charset if unknown : "); + if (UCAssume_MIMEcharset) + LYaddstr(UCAssume_MIMEcharset); + else + LYaddstr((UCLYhndl_for_unspec >= 0) ? + LYCharSet_UC[UCLYhndl_for_unspec].MIMEname + : "NONE"); + } + + LYmove(L_Rawmode, 5); + addlbl("Raw 8-bit or CJK m(O)de : "); + ShowBool(LYRawMode); + +#if defined(USE_SLANG) || defined(COLOR_CURSES) + LYmove(L_Color, B_COLOR); + addlbl("show color (&) : "); + if (no_option_save) { + ShowBool(LYShowColor == SHOW_COLOR_OFF); + } else { + switch (LYChosenShowColor) { + case SHOW_COLOR_NEVER: + LYaddstr("NEVER "); + break; + case SHOW_COLOR_OFF: + LYaddstr("OFF"); + break; + case SHOW_COLOR_ON: + LYaddstr("ON "); + break; + case SHOW_COLOR_ALWAYS: +#if defined(COLOR_CURSES) + if (!has_colors()) + LYaddstr("Always try"); + else +#endif + LYaddstr("ALWAYS "); + } + } +#endif /* USE_SLANG || COLOR_CURSES */ + + LYmove(L_Bool_A, B_VIKEYS); + addlbl("(V)I keys: "); + ShowBool(vi_keys); + + LYmove(L_Bool_A, B_EMACSKEYS); + addlbl("e(M)acs keys: "); + ShowBool(emacs_keys); + + LYmove(L_Bool_A, B_SHOW_DOTFILES); + addlbl("sho(W) dot files: "); + ShowBool(!no_dotfiles && show_dotfiles); + + LYmove(L_Bool_B, B_SELECT_POPUPS); + addlbl("popups for selec(T) fields : "); + ShowBool(LYSelectPopups); + + LYmove(L_Bool_B, B_SHOW_CURSOR); + addlbl("show cursor (@) : "); + ShowBool(LYShowCursor); + + LYmove(L_Keypad, 5); + addlbl("(K)eypad mode : "); + LYaddstr((fields_are_numbered() && links_are_numbered()) + ? "Links and form fields are numbered" + : (links_are_numbered() + ? "Links are numbered " + : (fields_are_numbered() + ? "Form fields are numbered " + : "Numbers act as arrows "))); + + LYmove(L_Lineed, 5); + addlbl("li(N)e edit style : "); + LYaddstr(LYEditorNames[current_lineedit]); + +#ifdef EXP_KEYBOARD_LAYOUT + LYmove(L_Layout, 5); + addlbl("Ke(Y)board layout : "); + LYaddstr(LYKbLayoutNames[current_layout]); +#endif + +#ifdef DIRED_SUPPORT + LYmove(L_Dired, 5); + addlbl("l(I)st directory style : "); + LYaddstr((dir_list_style == FILES_FIRST) ? "Files first " : + ((dir_list_style == MIXED_STYLE) ? "Mixed style " : + "Directories first")); +#endif /* DIRED_SUPPORT */ + + LYmove(L_User_Mode, 5); + addlbl("(U)ser mode : "); + LYaddstr((user_mode == NOVICE_MODE) ? "Novice " : + ((user_mode == INTERMEDIATE_MODE) ? "Intermediate" : + ((user_mode == ADVANCED_MODE) ? "Advanced " : + "Minimal "))); + + addlbl(" verbose images (!) : "); + ShowBool(verbose_img); + + LYmove(L_User_Agent, 5); + addlbl("user (A)gent : "); + LYaddstr(non_empty(LYUserAgent) ? LYUserAgent : "NONE"); + +#if defined(ENABLE_OPTS_CHANGE_EXEC) && (defined(EXEC_LINKS) || defined(EXEC_SCRIPTS)) + LYmove(L_Exec, 5); + addlbl("local e(X)ecution links : "); +#ifndef NEVER_ALLOW_REMOTE_EXEC + LYaddstr(local_exec ? "ALWAYS ON " : + (local_exec_on_local_files ? "FOR LOCAL FILES ONLY" : + "ALWAYS OFF ")); +#else + LYaddstr(local_exec_on_local_files ? "FOR LOCAL FILES ONLY" : + "ALWAYS OFF "); +#endif /* !NEVER_ALLOW_REMOTE_EXEC */ +#endif /* ENABLE_OPTS_CHANGE_EXEC */ + + LYmove(LYlines - 3, 2); + LYaddstr(SELECT_SEGMENT); + lynx_start_bold(); + LYaddstr(CAP_LETT_SEGMENT); + lynx_stop_bold(); + LYaddstr(OF_OPT_LINE_SEGMENT); + if (!no_option_save) { + LYaddstr(" '"); + lynx_start_bold(); + LYaddstr(">"); + lynx_stop_bold(); + LYaddstr("'"); + LYaddstr(TO_SAVE_SEGMENT); + } + LYaddstr(OR_SEGMENT); + LYaddstr("'"); + lynx_start_bold(); + LYaddstr("r"); + lynx_stop_bold(); + LYaddstr("'"); + LYaddstr(TO_RETURN_SEGMENT); + + response = 0; + while (response != 'R' && + !LYisNonAlnumKeyname(response, LYK_PREV_DOC) && + response != '>' && !term_options && + !LYCharIsINTERRUPT_NO_letter(response)) { + if (AddValueAccepted == TRUE) { + _statusline(VALUE_ACCEPTED); + AddValueAccepted = FALSE; + } + LYmove((LYlines - 2), 0); + lynx_start_prompt_color(); + LYaddstr(COMMAND_PROMPT); + lynx_stop_prompt_color(); + + LYrefresh(); + response = LYgetch_single(); + if (term_options || LYCharIsINTERRUPT_NO_letter(response)) + response = 'R'; + if (LYisNonAlnumKeyname(response, LYK_REFRESH)) { + lynx_force_repaint(); + goto draw_options; + } + switch (response) { + case 'E': /* Change the editor. */ + if (no_editor) { + _statusline(EDIT_DISABLED); + } else if (system_editor) { + _statusline(EDITOR_LOCKED); + } else { + if (non_empty(editor)) { + BStrCopy0(my_data, editor); + } else { /* clear the NONE */ + LYmove(L_EDITOR, COL_OPTION_VALUES); + LYaddstr(" "); + BStrCopy0(my_data, ""); + } + _statusline(ACCEPT_DATA); + LYmove(L_EDITOR, COL_OPTION_VALUES); + lynx_start_bold(); + ch = LYgetBString(&my_data, FALSE, 0, NORECALL); + lynx_stop_bold(); + LYmove(L_EDITOR, COL_OPTION_VALUES); + if (term_options || ch == -1) { + LYaddstr(non_empty(editor) ? + editor : "NONE"); + } else if (isBEmpty(my_data)) { + FREE(editor); + LYaddstr("NONE"); + } else { + StrAllocCopy(editor, my_data->str); + LYaddstr(editor); + } + LYclrtoeol(); + if (ch == -1) { + HTInfoMsg(CANCELLED); + HTInfoMsg(""); + } else { + _statusline(VALUE_ACCEPTED); + } + } + response = ' '; + break; + + case 'D': /* Change the display. */ + if (non_empty(x_display)) { + BStrCopy0(my_data, x_display); + } else { /* clear the NONE */ + LYmove(L_DISPLAY, COL_OPTION_VALUES); + LYaddstr(" "); + BStrCopy0(my_data, ""); + } + _statusline(ACCEPT_DATA); + LYmove(L_DISPLAY, COL_OPTION_VALUES); + lynx_start_bold(); + ch = LYgetBString(&my_data, FALSE, 0, NORECALL); + lynx_stop_bold(); + LYmove(L_DISPLAY, COL_OPTION_VALUES); + +#ifdef VMS +#define CompareEnvVars(a,b) strcasecomp(a, b) +#else +#define CompareEnvVars(a,b) strcmp(a, b) +#endif /* VMS */ + + if ((term_options || ch == -1) || + (x_display != NULL && + !CompareEnvVars(x_display, my_data->str))) { + /* + * Cancelled, or a non-NULL display string wasn't changed. - + * FM + */ + LYaddstr(non_empty(x_display) ? x_display : "NONE"); + LYclrtoeol(); + if (ch == -1) { + HTInfoMsg(CANCELLED); + HTInfoMsg(""); + } else { + _statusline(VALUE_ACCEPTED); + } + response = ' '; + break; + } else if (isBEmpty(my_data)) { + if ((x_display == NULL) || + (x_display != NULL && *x_display == '\0')) { + /* + * NULL or zero-length display string wasn't changed. - FM + */ + LYaddstr("NONE"); + LYclrtoeol(); + _statusline(VALUE_ACCEPTED); + response = ' '; + break; + } + } + /* + * Set the new DISPLAY variable. - FM + */ + LYsetXDisplay(my_data->str); + validate_x_display(); + LYaddstr(x_display ? x_display : "NONE"); + LYclrtoeol(); + summarize_x_display(my_data->str); + response = ' '; + break; + + case 'L': /* Change multibookmarks option. */ + if (LYMBMBlocked) { + _statusline(MULTIBOOKMARKS_DISALLOWED); + response = ' '; + break; + } + if (!LYSelectPopups) { + LYMultiBookmarks = LYChooseEnum(LYMultiBookmarks, + L_HOME, C_MULTI, + mbm_choices); + } else { + LYMultiBookmarks = LYChoosePopup(LYMultiBookmarks, + L_HOME, (C_MULTI - 1), + mbm_choices, + 3, FALSE, FALSE); + } +#if defined(VMS) || defined(USE_SLANG) + if (LYSelectPopups) { + LYmove(L_HOME, C_MULTI); + LYclrtoeol(); + LYaddstr(mbm_choices[LYMultiBookmarks]); + } +#endif /* VMS || USE_SLANG */ +#if !defined(VMS) && !defined(USE_SLANG) + if (!LYSelectPopups) +#endif /* !VMS && !USE_SLANG */ + { + LYmove(L_HOME, B_BOOK); + LYclrtoeol(); + if (LYMultiBookmarks != MBM_OFF) { + LYaddstr(gettext("review/edit B)ookmarks files")); + } else { + LYaddstr(gettext("B)ookmark file: ")); + LYaddstr(non_empty(bookmark_page) ? + bookmark_page : "NONE"); + } + } + response = ' '; + if (LYSelectPopups) { + HANDLE_LYOPTIONS; + } + break; + + case 'B': /* Change the bookmark page location. */ + /* + * Anonymous users should not be allowed to change the bookmark + * page. + */ + if (!no_bookmark) { + if (LYMultiBookmarks != MBM_OFF) { + edit_bookmarks(); + signal(SIGINT, terminate_options); + goto draw_options; + } + if (non_empty(bookmark_page)) { + BStrCopy0(my_data, bookmark_page); + } else { /* clear the NONE */ + LYmove(L_HOME, C_DEFAULT); + LYclrtoeol(); + BStrCopy0(my_data, ""); + } + _statusline(ACCEPT_DATA); + LYmove(L_HOME, C_DEFAULT); + lynx_start_bold(); + ch = LYgetBString(&my_data, FALSE, 0, NORECALL); + lynx_stop_bold(); + LYmove(L_HOME, C_DEFAULT); + BStrAlloc(my_data, my_data->len + LY_MAXPATH); /* lengthen */ + if (term_options || + ch == -1 || isBEmpty(my_data)) { + LYaddstr(non_empty(bookmark_page) ? + bookmark_page : "NONE"); + } else if (!LYPathOffHomeOK(my_data->str, (size_t) my_data->len)) { + LYaddstr(non_empty(bookmark_page) ? + bookmark_page : "NONE"); + LYclrtoeol(); + _statusline(USE_PATH_OFF_HOME); + response = ' '; + break; + } else { + StrAllocCopy(bookmark_page, my_data->str); + StrAllocCopy(MBM_A_subbookmark[0], bookmark_page); + LYaddstr(bookmark_page); + } + LYclrtoeol(); + if (ch == -1) { + HTInfoMsg(CANCELLED); + HTInfoMsg(""); + } else { + _statusline(VALUE_ACCEPTED); + } + } else { /* anonymous */ + _statusline(BOOKMARK_CHANGE_DISALLOWED); + } + response = ' '; + break; + + case 'F': /* Change ftp directory sorting. */ + if (!LYSelectPopups) { + HTfileSortMethod = LYChooseEnum(HTfileSortMethod, + L_FTPSTYPE, -1, + fileSort_choices); + } else { + HTfileSortMethod = LYChoosePopup(HTfileSortMethod, + L_FTPSTYPE, -1, + fileSort_choices, + 4, FALSE, FALSE); +#if defined(VMS) || defined(USE_SLANG) + LYmove(L_FTPSTYPE, COL_OPTION_VALUES); + LYclrtoeol(); + LYaddstr(fileSort_choices[HTfileSortMethod]); +#endif /* VMS || USE_SLANG */ + } + response = ' '; + if (LYSelectPopups) { + HANDLE_LYOPTIONS; + } + break; + + case 'P': /* Change personal mail address for From headers. */ + if (non_empty(personal_mail_address)) { + BStrCopy0(my_data, personal_mail_address); + } else { /* clear the NONE */ + LYmove(L_MAIL_ADDRESS, COL_OPTION_VALUES); + LYaddstr(" "); + BStrCopy0(my_data, ""); + } + _statusline(ACCEPT_DATA); + LYmove(L_MAIL_ADDRESS, COL_OPTION_VALUES); + lynx_start_bold(); + ch = LYgetBString(&my_data, FALSE, 0, NORECALL); + lynx_stop_bold(); + LYmove(L_MAIL_ADDRESS, COL_OPTION_VALUES); + if (term_options || ch == -1) { + LYaddstr((personal_mail_address && + *personal_mail_address) ? + personal_mail_address : "NONE"); + } else if (isBEmpty(my_data)) { + FREE(personal_mail_address); + LYaddstr("NONE"); + } else { + StrAllocCopy(personal_mail_address, my_data->str); + LYaddstr(personal_mail_address); + } + LYclrtoeol(); + if (ch == -1) { + HTInfoMsg(CANCELLED); + HTInfoMsg(""); + } else { + _statusline(VALUE_ACCEPTED); + } + response = ' '; + break; + + case 'S': /* Change case sensitivity for searches. */ + LYcase_sensitive = LYChooseBoolean(LYcase_sensitive, + L_SSEARCH, -1, + caseless_choices); + response = ' '; + break; + + case '\001': /* Change assume_charset setting. */ + if (use_assume_charset) { + int i, curval; + const char **assume_list; + assume_list = typecallocn(const char *, (unsigned) + (LYNumCharsets + 1)); + + if (!assume_list) { + outofmem(__FILE__, "options"); + } + for (i = 0; i < LYNumCharsets; i++) { + assume_list[i] = LYCharSet_UC[i].MIMEname; + } + curval = UCLYhndl_for_unspec; + if (curval == current_char_set && UCAssume_MIMEcharset) { + curval = UCGetLYhndl_byMIME(UCAssume_MIMEcharset); + } + if (curval < 0) + curval = LYRawMode ? current_char_set : 0; + if (!LYSelectPopups) { +#ifndef ALL_CHARSETS_IN_O_MENU_SCREEN + UCLYhndl_for_unspec = + assumed_doc_charset_map[(LYChooseEnum(charset_subsets[curval].assumed_idx, + L_ASSUME_CHARSET, -1, + assumed_charset_choices) + ? 1 + : 0)]; +#else + UCLYhndl_for_unspec = + LYChooseEnum(curval, + L_ASSUME_CHARSET, -1, + assume_list); +#endif + } else { +#ifndef ALL_CHARSETS_IN_O_MENU_SCREEN + UCLYhndl_for_unspec = + assumed_doc_charset_map[(LYChoosePopup(charset_subsets[curval].assumed_idx, + L_ASSUME_CHARSET, -1, + assumed_charset_choices, + 0, + FALSE, + FALSE) + ? 1 + : 0)]; +#else + UCLYhndl_for_unspec = + LYChoosePopup(curval, + L_ASSUME_CHARSET, -1, + assume_list, + 0, FALSE, FALSE); +#endif +#if defined(VMS) || defined(USE_SLANG) + LYmove(L_ASSUME_CHARSET, COL_OPTION_VALUES); + LYclrtoeol(); + if (UCLYhndl_for_unspec >= 0) + LYaddstr(LYCharSet_UC[UCLYhndl_for_unspec].MIMEname); +#endif /* VMS || USE_SLANG */ + } + + /* + * Set the raw 8-bit or CJK mode defaults and character set if + * changed. - FM + */ + if (CurrentAssumeCharSet != UCLYhndl_for_unspec || + UCLYhndl_for_unspec != curval) { + if (UCLYhndl_for_unspec != CurrentAssumeCharSet) { + StrAllocCopy(UCAssume_MIMEcharset, + LYCharSet_UC[UCLYhndl_for_unspec].MIMEname); + } + if (HTCJK != JAPANESE) + LYRawMode = (BOOLEAN) (UCLYhndl_for_unspec == current_char_set); + HTMLSetUseDefaultRawMode(current_char_set, LYRawMode); + HTMLSetCharacterHandling(current_char_set); + CurrentAssumeCharSet = UCLYhndl_for_unspec; + CurrentRawMode = LYRawMode; +#if !defined(VMS) && !defined(USE_SLANG) + if (!LYSelectPopups) +#endif /* !VMS && !USE_SLANG */ + { + LYmove(L_RAWMODE + 1, COL_OPTION_VALUES); + LYclrtoeol(); + ShowBool(LYRawMode); + } + } + FREE(assume_list); + response = ' '; + if (LYSelectPopups) { + HANDLE_LYOPTIONS; + } + } else { + _statusline(NEED_ADVANCED_USER_MODE); + AddValueAccepted = FALSE; + } + break; + + case 'C': /* Change display charset setting. */ + if (!LYSelectPopups) { +#ifndef ALL_CHARSETS_IN_O_MENU_SCREEN + displayed_display_charset_idx = LYChooseEnum(displayed_display_charset_idx, + L_Charset, -1, + display_charset_choices); + current_char_set = display_charset_map[displayed_display_charset_idx]; +#else + current_char_set = LYChooseEnum(current_char_set, + L_Charset, -1, + LYchar_set_names); +#endif + } else { +#ifndef ALL_CHARSETS_IN_O_MENU_SCREEN + displayed_display_charset_idx = LYChoosePopup(displayed_display_charset_idx, + L_Charset, -1, + display_charset_choices, + 0, FALSE, FALSE); + current_char_set = display_charset_map[displayed_display_charset_idx]; +#else + current_char_set = LYChoosePopup(current_char_set, + L_Charset, -1, + LYchar_set_names, + 0, FALSE, FALSE); +#endif + +#if defined(VMS) || defined(USE_SLANG) + LYmove(L_Charset, COL_OPTION_VALUES); + LYclrtoeol(); + LYaddstr(LYchar_set_names[current_char_set]); +#endif /* VMS || USE_SLANG */ + } + /* + * Set the raw 8-bit or CJK mode defaults and character set if + * changed. - FM + */ + if (CurrentCharSet != current_char_set) { + LYUseDefaultRawMode = TRUE; + HTMLUseCharacterSet(current_char_set); + CurrentCharSet = current_char_set; + CurrentRawMode = LYRawMode; +#if !defined(VMS) && !defined(USE_SLANG) + if (!LYSelectPopups) +#endif /* !VMS && !USE_SLANG */ + { + LYmove(L_Rawmode, COL_OPTION_VALUES); + LYclrtoeol(); + ShowBool(LYRawMode); + } +#ifdef CAN_SWITCH_DISPLAY_CHARSET + /* Deduce whether the user wants autoswitch: */ + switch_display_charsets = + (current_char_set == auto_display_charset + || current_char_set == auto_other_display_charset); +#endif + } + response = ' '; + if (LYSelectPopups) { + HANDLE_LYOPTIONS; + } + break; + + case 'O': /* Change raw mode setting. */ + LYRawMode = LYChooseBoolean(LYRawMode, L_Rawmode, -1, bool_choices); + /* + * Set the LYUseDefaultRawMode value and character handling if + * LYRawMode was changed. - FM + */ + if (CurrentRawMode != LYRawMode) { + HTMLSetUseDefaultRawMode(current_char_set, LYRawMode); + HTMLSetCharacterHandling(current_char_set); + CurrentRawMode = LYRawMode; + } + response = ' '; + break; + + case 'G': /* Change language preference. */ + if (non_empty(language)) { + BStrCopy0(my_data, language); + } else { /* clear the NONE */ + LYmove(L_LANGUAGE, COL_OPTION_VALUES); + LYaddstr(" "); + BStrCopy0(my_data, ""); + } + _statusline(ACCEPT_DATA); + LYmove(L_LANGUAGE, COL_OPTION_VALUES); + lynx_start_bold(); + ch = LYgetBString(&my_data, FALSE, 0, NORECALL); + lynx_stop_bold(); + LYmove(L_LANGUAGE, COL_OPTION_VALUES); + if (term_options || ch == -1) { + LYaddstr(non_empty(language) ? + language : "NONE"); + } else if (isBEmpty(my_data)) { + FREE(language); + LYaddstr("NONE"); + } else { + StrAllocCopy(language, my_data->str); + LYaddstr(language); + } + LYclrtoeol(); + if (ch == -1) { + HTInfoMsg(CANCELLED); + HTInfoMsg(""); + } else { + _statusline(VALUE_ACCEPTED); + } + response = ' '; + break; + + case 'H': /* Change charset preference. */ + if (non_empty(pref_charset)) { + BStrCopy0(my_data, pref_charset); + } else { /* clear the NONE */ + LYmove(L_PREF_CHARSET, COL_OPTION_VALUES); + LYaddstr(" "); + BStrCopy0(my_data, ""); + } + _statusline(ACCEPT_DATA); + LYmove(L_PREF_CHARSET, COL_OPTION_VALUES); + lynx_start_bold(); + ch = LYgetBString(&my_data, FALSE, 0, NORECALL); + lynx_stop_bold(); + LYmove(L_PREF_CHARSET, COL_OPTION_VALUES); + if (term_options || ch == -1) { + LYaddstr(non_empty(pref_charset) ? + pref_charset : "NONE"); + } else if (isBEmpty(my_data)) { + FREE(pref_charset); + LYaddstr("NONE"); + } else { + StrAllocCopy(pref_charset, my_data->str); + LYaddstr(pref_charset); + } + LYclrtoeol(); + if (ch == -1) { + HTInfoMsg(CANCELLED); + HTInfoMsg(""); + } else { + _statusline(VALUE_ACCEPTED); + } + response = ' '; + break; + + case 'V': /* Change VI keys setting. */ + vi_keys = LYChooseBoolean(vi_keys, + L_Bool_A, C_VIKEYS, + bool_choices); + if (vi_keys) { + set_vi_keys(); + } else { + reset_vi_keys(); + } + response = ' '; + break; + + case 'M': /* Change emacs keys setting. */ + emacs_keys = LYChooseBoolean(emacs_keys, + L_Bool_A, C_EMACSKEYS, + bool_choices); + if (emacs_keys) { + set_emacs_keys(); + } else { + reset_emacs_keys(); + } + response = ' '; + break; + + case 'W': /* Change show dotfiles setting. */ + if (no_dotfiles) { + _statusline(DOTFILE_ACCESS_DISABLED); + } else { + show_dotfiles = LYChooseBoolean(show_dotfiles, + L_Bool_A, + C_SHOW_DOTFILES, + bool_choices); + } + response = ' '; + break; + + case 'T': /* Change select popups setting. */ + LYSelectPopups = LYChooseBoolean(LYSelectPopups, + L_Bool_B, + C_SELECT_POPUPS, + bool_choices); + response = ' '; + break; + +#if defined(USE_SLANG) || defined(COLOR_CURSES) + case '&': /* Change show color setting. */ + if (no_option_save) { +#if defined(COLOR_CURSES) + if (!has_colors()) { + char *terminal = LYGetEnv("TERM"); + + if (terminal) + HTUserMsg2(COLOR_TOGGLE_DISABLED_FOR_TERM, + terminal); + else + HTUserMsg(COLOR_TOGGLE_DISABLED); + break; + } +#endif + LYShowColor = LYChooseEnum((LYShowColor - 1), + L_Color, + C_COLOR, + bool_choices); + if (LYShowColor == 0) { + LYShowColor = SHOW_COLOR_OFF; + } else { + LYShowColor = SHOW_COLOR_ON; + } + } else { /* !no_option_save */ + BOOLEAN again = FALSE; + int chosen; + + /* + * Copy strings into choice array. + */ + choices[0] = NULL; + StrAllocCopy(choices[0], "NEVER "); + choices[1] = NULL; + StrAllocCopy(choices[1], "OFF "); + choices[2] = NULL; + StrAllocCopy(choices[2], "ON "); + choices[3] = NULL; +#if defined(COLOR_CURSES) + if (!has_colors()) + StrAllocCopy(choices[3], "Always try"); + else +#endif + StrAllocCopy(choices[3], "ALWAYS "); + choices[4] = NULL; + do { + if (!LYSelectPopups) { + chosen = LYChooseEnum(LYChosenShowColor, + L_Color, + C_COLOR, + choices); + } else { + chosen = LYChoosePopup(LYChosenShowColor, + L_Color, + C_COLOR, + choices, 4, FALSE, FALSE); + } +#if defined(COLOR_CURSES) + again = (BOOLEAN) (chosen == SHOW_COLOR_ON && !has_colors()); + if (again) { + char *terminal = LYGetEnv("TERM"); + + if (terminal) + HTUserMsg2(COLOR_TOGGLE_DISABLED_FOR_TERM, + terminal); + else + HTUserMsg(COLOR_TOGGLE_DISABLED); + } +#endif + } while (again); + LYChosenShowColor = chosen; +#if defined(VMS) + if (LYSelectPopups) { + LYmove(L_Color, C_COLOR); + LYclrtoeol(); + LYaddstr(choices[LYChosenShowColor]); + } +#endif /* VMS */ +#if defined(COLOR_CURSES) + if (has_colors()) +#endif + LYShowColor = chosen; + FREE(choices[0]); + FREE(choices[1]); + FREE(choices[2]); + FREE(choices[3]); + } + if (CurrentShowColor != LYShowColor) { + lynx_force_repaint(); + } + CurrentShowColor = LYShowColor; +#ifdef USE_SLANG + SLtt_Use_Ansi_Colors = (LYShowColor > SHOW_COLOR_OFF ? TRUE : FALSE); +#endif + response = ' '; + if (LYSelectPopups && !no_option_save) { + HANDLE_LYOPTIONS; + } + break; +#endif /* USE_SLANG or COLOR_CURSES */ + + case '@': /* Change show cursor setting. */ + LYShowCursor = LYChooseBoolean(LYShowCursor, + L_Bool_B, + C_SHOW_CURSOR, + bool_choices); + response = ' '; + break; + + case 'K': /* Change keypad mode. */ + if (!LYSelectPopups) { + keypad_mode = LYChooseEnum(keypad_mode, + L_Keypad, -1, + keypad_choices); + } else { + keypad_mode = LYChoosePopup(keypad_mode, + L_Keypad, -1, + keypad_choices, + 3, FALSE, FALSE); +#if defined(VMS) || defined(USE_SLANG) + LYmove(L_Keypad, COL_OPTION_VALUES); + LYclrtoeol(); + LYaddstr(keypad_choices[keypad_mode]); +#endif /* VMS || USE_SLANG */ + } + if (keypad_mode == NUMBERS_AS_ARROWS) { + set_numbers_as_arrows(); + } else { + reset_numbers_as_arrows(); + } + response = ' '; + if (LYSelectPopups) { + HANDLE_LYOPTIONS; + } + break; + + case 'N': /* Change line editor key bindings. */ + if (!LYSelectPopups) { + current_lineedit = LYChooseEnum(current_lineedit, + L_Lineed, -1, + LYEditorNames); + } else { + current_lineedit = LYChoosePopup(current_lineedit, + L_Lineed, -1, + LYEditorNames, + 0, FALSE, FALSE); +#if defined(VMS) || defined(USE_SLANG) + LYmove(L_Lineed, COL_OPTION_VALUES); + LYclrtoeol(); + LYaddstr(LYEditorNames[current_lineedit]); +#endif /* VMS || USE_SLANG */ + } + response = ' '; + if (LYSelectPopups) { + HANDLE_LYOPTIONS; + } + break; + +#ifdef EXP_KEYBOARD_LAYOUT + case 'Y': /* Change keyboard layout */ + if (!LYSelectPopups) { + current_layout = LYChooseEnum(current_layout, + L_Layout, -1, + LYKbLayoutNames); + } else { + current_layout = LYChoosePopup(current_layout, + L_Layout, -1, + LYKbLayoutNames, + 0, FALSE, FALSE); +#if defined(VMS) || defined(USE_SLANG) + LYmove(L_Layout, COL_OPTION_VALUES); + LYclrtoeol(); + LYaddstr(LYKbLayoutNames[current_layout]); +#endif /* VMS || USE_SLANG */ + } + response = ' '; + if (LYSelectPopups) { + HANDLE_LYOPTIONS; + } + break; +#endif /* EXP_KEYBOARD_LAYOUT */ + +#ifdef DIRED_SUPPORT + case 'I': /* Change local directory sorting. */ + if (!LYSelectPopups) { + dir_list_style = LYChooseEnum(dir_list_style, + L_Dired, -1, + dirList_choices); + } else { + dir_list_style = LYChoosePopup(dir_list_style, + L_Dired, -1, + dirList_choices, + 3, FALSE, FALSE); +#if defined(VMS) || defined(USE_SLANG) + LYmove(L_Dired, COL_OPTION_VALUES); + LYclrtoeol(); + LYaddstr(dirList_choices[dir_list_style]); +#endif /* VMS || USE_SLANG */ + } + response = ' '; + if (LYSelectPopups) { + HANDLE_LYOPTIONS; + } + break; +#endif /* DIRED_SUPPORT */ + + case 'U': /* Change user mode. */ + if (!LYSelectPopups) { + user_mode = LYChooseEnum(user_mode, + L_User_Mode, -1, + userMode_choices); + use_assume_charset = (BOOLEAN) (user_mode == ADVANCED_MODE); + } else { + user_mode = LYChoosePopup(user_mode, + L_User_Mode, -1, + userMode_choices, + 3, FALSE, FALSE); + use_assume_charset = (BOOLEAN) (user_mode == ADVANCED_MODE); +#if defined(VMS) || defined(USE_SLANG) + if (use_assume_charset == old_use_assume_charset) { + LYmove(L_User_Mode, COL_OPTION_VALUES); + LYclrtoeol(); + LYaddstr(userMode_choices[user_mode]); + } +#endif /* VMS || USE_SLANG */ + } + LYSetDisplayLines(); + response = ' '; + if (LYSelectPopups) { + HANDLE_LYOPTIONS; + } + break; + + case '!': + if (!LYSelectPopups) { + verbose_img = LYChooseBoolean(verbose_img, + L_VERBOSE_IMAGES, + C_VERBOSE_IMAGES, + bool_choices); + } else { + verbose_img = (BOOLEAN) LYChoosePopup(verbose_img, + L_VERBOSE_IMAGES, + C_VERBOSE_IMAGES, + bool_choices, + 2, FALSE, FALSE); + } + response = ' '; + if (LYSelectPopups) { + HANDLE_LYOPTIONS; + } + break; + + case 'A': /* Change user agent string. */ + if (!no_useragent) { + if (non_empty(LYUserAgent)) { + BStrCopy0(my_data, LYUserAgent); + } else { /* clear the NONE */ + LYmove(L_HOME, COL_OPTION_VALUES); + LYaddstr(" "); + BStrCopy0(my_data, ""); + } + _statusline(ACCEPT_DATA_OR_DEFAULT); + LYmove(L_User_Agent, COL_OPTION_VALUES); + lynx_start_bold(); + ch = LYgetBString(&my_data, FALSE, 0, NORECALL); + lynx_stop_bold(); + LYmove(L_User_Agent, COL_OPTION_VALUES); + if (term_options || ch == -1) { + LYaddstr((LYUserAgent && + *LYUserAgent) ? + LYUserAgent : "NONE"); + } else if (isBEmpty(my_data)) { + StrAllocCopy(LYUserAgent, LYUserAgentDefault); + LYaddstr((LYUserAgent && + *LYUserAgent) ? + LYUserAgent : "NONE"); + } else { + StrAllocCopy(LYUserAgent, my_data->str); + LYaddstr(LYUserAgent); + } + LYclrtoeol(); + if (ch == -1) { + HTInfoMsg(CANCELLED); + HTInfoMsg(""); + } else if (!LYCheckUserAgent()) { + _statusline(UA_PLEASE_USE_LYNX); + } else { + _statusline(VALUE_ACCEPTED); + } + } else { /* disallowed */ + _statusline(UA_CHANGE_DISABLED); + } + response = ' '; + break; + +#if defined(ENABLE_OPTS_CHANGE_EXEC) && (defined(EXEC_LINKS) || defined(EXEC_SCRIPTS)) + case 'X': /* Change local exec restriction. */ + if (exec_frozen && !LYSelectPopups) { + _statusline(CHANGE_OF_SETTING_DISALLOWED); + response = ' '; + break; + } +#ifndef NEVER_ALLOW_REMOTE_EXEC + if (local_exec) { + itmp = 2; + } else +#endif /* !NEVER_ALLOW_REMOTE_EXEC */ + { + if (local_exec_on_local_files) { + itmp = 1; + } else { + itmp = 0; + } + } + if (!LYSelectPopups) { + itmp = LYChooseEnum(itmp, + L_Exec, -1, + exec_choices); + } else { + itmp = LYChoosePopup(itmp, + L_Exec, -1, + exec_choices, + 0, (exec_frozen ? TRUE : FALSE), + FALSE); +#if defined(VMS) || defined(USE_SLANG) + LYmove(L_Exec, COL_OPTION_VALUES); + LYclrtoeol(); + LYaddstr(exec_choices[itmp]); +#endif /* VMS || USE_SLANG */ + } + if (!exec_frozen) { + switch (itmp) { + case 0: + local_exec = FALSE; + local_exec_on_local_files = FALSE; + break; + case 1: + local_exec = FALSE; + local_exec_on_local_files = TRUE; + break; +#ifndef NEVER_ALLOW_REMOTE_EXEC + case 2: + local_exec = TRUE; + local_exec_on_local_files = FALSE; + break; +#endif /* !NEVER_ALLOW_REMOTE_EXEC */ + } /* end switch */ + } + response = ' '; + if (LYSelectPopups) { + HANDLE_LYOPTIONS; + } + break; +#endif /* ENABLE_OPTS_CHANGE_EXEC */ + + case '>': /* Save current options to RC file. */ + if (!no_option_save) { + HTInfoMsg(SAVING_OPTIONS); + LYrcShowColor = LYChosenShowColor; + if (save_rc(NULL)) { + HTInfoMsg(OPTIONS_SAVED); + } else { + HTAlert(OPTIONS_NOT_SAVED); + } + } else { + HTInfoMsg(R_TO_RETURN_TO_LYNX); + /* + * Change response so that we don't exit the options menu. + */ + response = ' '; + } + break; + + case 'R': /* Return to document (quit options menu). */ + break; + + default: + if (!no_option_save) { + HTInfoMsg(SAVE_OR_R_TO_RETURN_TO_LYNX); + } else { + HTInfoMsg(R_TO_RETURN_TO_LYNX); + } + } /* end switch */ + } /* end while */ + + term_options = FALSE; + LYStatusLine = -1; /* let user_mode have some of the screen */ + signal(SIGINT, cleanup_sig); + BStrFree(my_data); + return; +} + +static int widest_choice(STRING2PTR choices) +{ + int n, width = 0; + + for (n = 0; choices[n] != NULL; ++n) { + int len = (int) strlen(choices[n]); + + if (width < len) + width = len; + } + return width; +} + +static void show_choice(const char *choice, + int width) +{ + int len = 0; + + if (choice != 0) { + len = (int) strlen(choice); + + LYaddstr(choice); + } + while (len++ < width) + LYaddch(' '); +} + +/* + * Take a status code, prompt the user for a new status, and return it. + */ +static int boolean_choice(int cur_choice, + int line, + int column, + STRING2PTR choices) +{ + int response = 0; + int cmd = 0; + int number = 0; + int col = (column >= 0 ? column : COL_OPTION_VALUES); + int orig_choice = cur_choice; + int width = widest_choice(choices); + + /* + * Get the number of choices and then make number zero-based. + */ + for (number = 0; choices[number] != NULL; number++) ; /* empty loop body */ + number--; + + /* + * Update the statusline. + */ + _statusline(ANY_KEY_CHANGE_RET_ACCEPT); + + /* + * Highlight the current choice. + */ + LYmove(line, col); + lynx_start_reverse(); + show_choice(choices[cur_choice], width); + if (LYShowCursor) + LYmove(line, (col - 1)); + LYrefresh(); + + /* + * Get the keyboard entry, and leave the cursor at the choice, to indicate + * that it can be changed, until the user accepts the current choice. + */ + term_options = FALSE; + while (1) { + LYmove(line, col); + if (term_options == FALSE) { + response = LYgetch_single(); + } + if (term_options || LYCharIsINTERRUPT_NO_letter(response)) { + /* + * Control-C or Control-G. + */ + response = '\n'; + term_options = TRUE; + cur_choice = orig_choice; + } +#ifdef VMS + if (HadVMSInterrupt) { + HadVMSInterrupt = FALSE; + response = '\n'; + term_options = TRUE; + cur_choice = orig_choice; + } +#endif /* VMS */ + if ((response != '\n' && response != '\r') && + (cmd = LKC_TO_LAC(keymap, response)) != LYK_ACTIVATE) { + switch (cmd) { + case LYK_HOME: + cur_choice = 0; + break; + + case LYK_END: + cur_choice = number; + break; + + case LYK_REFRESH: + lynx_force_repaint(); + LYrefresh(); + break; + + case LYK_QUIT: + case LYK_ABORT: + case LYK_PREV_DOC: + cur_choice = orig_choice; + term_options = TRUE; + break; + + case LYK_PREV_PAGE: + case LYK_UP_HALF: + case LYK_UP_TWO: + case LYK_PREV_LINK: + case LYK_LPOS_PREV_LINK: + case LYK_FASTBACKW_LINK: + case LYK_UP_LINK: + case LYK_LEFT_LINK: + if (cur_choice == 0) + cur_choice = number; /* go back to end */ + else + cur_choice--; + break; + + case LYK_1: + case LYK_2: + case LYK_3: + case LYK_4: + case LYK_5: + case LYK_6: + case LYK_7: + case LYK_8: + case LYK_9: + if ((cmd - LYK_1 + 1) <= number) { + cur_choice = cmd - LYK_1 + 1; + break; + } /* else fall through! */ + default: + if (cur_choice == number) + cur_choice = 0; /* go over the top and around */ + else + cur_choice++; + } /* end of switch */ + show_choice(choices[cur_choice], width); + if (LYShowCursor) + LYmove(line, (col - 1)); + LYrefresh(); + } else { + /* + * Unhighlight choice. + */ + LYmove(line, col); + lynx_stop_reverse(); + show_choice(choices[cur_choice], width); + + if (term_options) { + term_options = FALSE; + HTInfoMsg(CANCELLED); + HTInfoMsg(""); + } else { + _statusline(VALUE_ACCEPTED); + } + return cur_choice; + } + } +} +#endif /* !NO_OPTION_MENU */ + +static void terminate_options(int sig GCC_UNUSED) +{ + term_options = TRUE; + /* + * Reassert the AST. + */ + signal(SIGINT, terminate_options); +#ifdef VMS + /* + * Refresh the screen to get rid of the "interrupt" message. + */ + if (!dump_output_immediately) { + lynx_force_repaint(); + LYrefresh(); + } +#endif /* VMS */ +} + +/* + * Multi-Bookmark On-Line editing support. - FMG & FM + */ +void edit_bookmarks(void) +{ + int response = 0, def_response = 0; + int MBM_current = 1; + +#define MULTI_OFFSET 8 + int a; /* misc counter */ + bstring *my_data = NULL; + + /* + * We need (MBM_V_MAXFILES + MULTI_OFFSET) lines to display the whole list + * at once. Otherwise break it up into two segments. We know it won't be + * less than that because 'o'ptions needs 23-24 at LEAST. + */ + term_options = FALSE; + signal(SIGINT, terminate_options); + + draw_bookmark_list: + /* + * Display menu of bookmarks. NOTE that we avoid printw()'s to increase + * the chances that any non-ASCII or multibyte/CJK characters will be + * handled properly. - FM + */ +#if defined(FANCY_CURSES) || defined (USE_SLANG) + if (enable_scrollback) { + LYclear(); + } else { + LYerase(); + } +#else + LYclear(); +#endif /* FANCY_CURSES || USE_SLANG */ + LYmove(0, 5); + lynx_start_h1_color(); + if (LYlines < (MBM_V_MAXFILES + MULTI_OFFSET)) { + char *ehead_buffer = 0; + + HTSprintf0(&ehead_buffer, MULTIBOOKMARKS_EHEAD_MASK, MBM_current); + LYaddstr(ehead_buffer); + FREE(ehead_buffer); + } else { + LYaddstr(MULTIBOOKMARKS_EHEAD); + } + lynx_stop_h1_color(); + + if (LYlines < (MBM_V_MAXFILES + MULTI_OFFSET)) { + for (a = ((MBM_V_MAXFILES / 2 + 1) * (MBM_current - 1)); + a <= (MBM_current * MBM_V_MAXFILES / 2); a++) { + LYmove((3 + a) - ((MBM_V_MAXFILES / 2 + 1) * (MBM_current - 1)), 5); + LYaddch(UCH(LYindex2MBM(a))); + LYaddstr(" : "); + if (MBM_A_subdescript[a]) + LYaddstr(MBM_A_subdescript[a]); + LYmove((3 + a) - ((MBM_V_MAXFILES / 2 + 1) * (MBM_current - 1)), 35); + LYaddstr("| "); + if (MBM_A_subbookmark[a]) { + LYaddstr(MBM_A_subbookmark[a]); + } + } + } else { + for (a = 0; a <= MBM_V_MAXFILES; a++) { + LYmove(3 + a, 5); + LYaddch(UCH(LYindex2MBM(a))); + LYaddstr(" : "); + if (MBM_A_subdescript[a]) + LYaddstr(MBM_A_subdescript[a]); + LYmove(3 + a, 35); + LYaddstr("| "); + if (MBM_A_subbookmark[a]) { + LYaddstr(MBM_A_subbookmark[a]); + } + } + } + + /* + * Only needed when we have 2 screens. + */ + if (LYlines < MBM_V_MAXFILES + MULTI_OFFSET) { + LYmove((LYlines - 4), 0); + LYaddstr("'"); + lynx_start_bold(); + LYaddstr("["); + lynx_stop_bold(); + LYaddstr("' "); + LYaddstr(PREVIOUS); + LYaddstr(", '"); + lynx_start_bold(); + LYaddstr("]"); + lynx_stop_bold(); + LYaddstr("' "); + LYaddstr(NEXT_SCREEN); + } + + LYmove((LYlines - 3), 0); + if (!no_option_save) { + LYaddstr("'"); + lynx_start_bold(); + LYaddstr(">"); + lynx_stop_bold(); + LYaddstr("'"); + LYaddstr(TO_SAVE_SEGMENT); + } + LYaddstr(OR_SEGMENT); + LYaddstr("'"); + lynx_start_bold(); + LYaddstr("^G"); + lynx_stop_bold(); + LYaddstr("'"); + LYaddstr(TO_RETURN_SEGMENT); + + while (!term_options && + !LYisNonAlnumKeyname(response, LYK_PREV_DOC) && + !LYCharIsINTERRUPT_NO_letter(response) && response != '>') { + + LYmove((LYlines - 2), 0); + lynx_start_prompt_color(); + LYaddstr(MULTIBOOKMARKS_LETTER); + lynx_stop_prompt_color(); + + LYrefresh(); + response = (def_response ? def_response : LYgetch_single()); + def_response = 0; + + /* + * Check for a cancel. + */ + if (term_options || LYCharIsINTERRUPT_NO_letter(response) || + LYisNonAlnumKeyname(response, LYK_PREV_DOC)) + continue; + + /* + * Check for a save. + */ + if (response == '>') { + if (!no_option_save) { + HTInfoMsg(SAVING_OPTIONS); + if (save_rc(NULL)) + HTInfoMsg(OPTIONS_SAVED); + else + HTAlert(OPTIONS_NOT_SAVED); + } else { + HTInfoMsg(R_TO_RETURN_TO_LYNX); + /* + * Change response so that we don't exit the options menu. + */ + response = ' '; + } + continue; + } + + /* + * Check for a refresh. + */ + if (LYisNonAlnumKeyname(response, LYK_REFRESH)) { + lynx_force_repaint(); + continue; + } + + /* + * Move between the screens - if we can't show it all at once. + */ + if ((response == ']' || + LYisNonAlnumKeyname(response, LYK_NEXT_PAGE)) && + LYlines < (MBM_V_MAXFILES + MULTI_OFFSET)) { + MBM_current++; + if (MBM_current >= 3) + MBM_current = 1; + goto draw_bookmark_list; + } + if ((response == '[' || + LYisNonAlnumKeyname(response, LYK_PREV_PAGE)) && + LYlines < (MBM_V_MAXFILES + MULTI_OFFSET)) { + MBM_current--; + if (MBM_current <= 0) + MBM_current = 2; + goto draw_bookmark_list; + } + + /* + * Instead of using 26 case statements, we set up a scan through the + * letters and edit the lines that way. + */ + for (a = 0; a <= MBM_V_MAXFILES; a++) { + if (LYMBM2index(response) == a) { + if (LYlines < (MBM_V_MAXFILES + MULTI_OFFSET)) { + if (MBM_current == 1 && a > (MBM_V_MAXFILES / 2)) { + MBM_current = 2; + def_response = response; + goto draw_bookmark_list; + } + if (MBM_current == 2 && a < (MBM_V_MAXFILES / 2)) { + MBM_current = 1; + def_response = response; + goto draw_bookmark_list; + } + } + _statusline(ACCEPT_DATA); + + if (a > 0) { + lynx_start_bold(); + if (LYlines < (MBM_V_MAXFILES + MULTI_OFFSET)) + LYmove((3 + a) + - ((MBM_V_MAXFILES / 2 + 1) * (MBM_current - 1)), + 9); + else + LYmove((3 + a), 9); + BStrCopy0(my_data, + (!MBM_A_subdescript[a] ? + "" : MBM_A_subdescript[a])); + (void) LYgetBString(&my_data, FALSE, 0, NORECALL); + lynx_stop_bold(); + + if (isBEmpty(my_data)) { + FREE(MBM_A_subdescript[a]); + } else { + StrAllocCopy(MBM_A_subdescript[a], my_data->str); + } + if (LYlines < (MBM_V_MAXFILES + MULTI_OFFSET)) + LYmove((3 + a) + - ((MBM_V_MAXFILES / 2 + 1) + * (MBM_current - 1)), + 5); + else + LYmove((3 + a), 5); + LYaddch(UCH(LYindex2MBM(a))); + LYaddstr(" : "); + if (MBM_A_subdescript[a]) + LYaddstr(MBM_A_subdescript[a]); + LYclrtoeol(); + LYrefresh(); + } + + if (LYlines < (MBM_V_MAXFILES + MULTI_OFFSET)) + LYmove((3 + a) + - ((MBM_V_MAXFILES / 2 + 1) + * (MBM_current - 1)), + 35); + else + LYmove((3 + a), 35); + LYaddstr("| "); + + lynx_start_bold(); + BStrCopy0(my_data, NonNull(MBM_A_subbookmark[a])); + (void) LYgetBString(&my_data, FALSE, 0, NORECALL); + lynx_stop_bold(); + + if (isBEmpty(my_data)) { + if (a == 0) + StrAllocCopy(MBM_A_subbookmark[a], bookmark_page); + else + FREE(MBM_A_subbookmark[a]); + } else { + BStrAlloc(my_data, my_data->len + LY_MAXPATH); + if (!LYPathOffHomeOK(my_data->str, (size_t) my_data->len)) { + LYMBM_statusline(USE_PATH_OFF_HOME); + LYSleepAlert(); + } else { + StrAllocCopy(MBM_A_subbookmark[a], my_data->str); + if (a == 0) { + StrAllocCopy(bookmark_page, MBM_A_subbookmark[a]); + } + } + } + if (LYlines < (MBM_V_MAXFILES + MULTI_OFFSET)) + LYmove((3 + a) + - ((MBM_V_MAXFILES / 2 + 1) + * (MBM_current - 1)), + 35); + else + LYmove((3 + a), 35); + LYaddstr("| "); + if (MBM_A_subbookmark[a]) + LYaddstr(MBM_A_subbookmark[a]); + LYclrtoeol(); + LYParkCursor(); + break; + } + } /* end for */ + } /* end while */ + + BStrFree(my_data); + term_options = FALSE; + signal(SIGINT, cleanup_sig); +} + +#if defined(USE_CURSES_PADS) || !defined(NO_OPTION_MENU) || (defined(USE_MOUSE) && (defined(NCURSES) || defined(PDCURSES))) + +/* + * This function offers the choices for values of an option via a popup window + * which functions like that for selection of options in a form. - FM + * + * Also used for mouse popups with ncurses; this is indicated by for_mouse. + */ +int popup_choice(int cur_choice, + int line, + int column, + STRING2PTR choices, + int i_length, + int disabled, + int for_mouse) +{ + if (column < 0) + column = (COL_OPTION_VALUES - 1); + + term_options = FALSE; + cur_choice = LYhandlePopupList(cur_choice, + line, + column, + (STRING2PTR) choices, + -1, + i_length, + disabled, + for_mouse); + switch (cur_choice) { + case LYK_QUIT: + case LYK_ABORT: + case LYK_PREV_DOC: + term_options = TRUE; + if (!for_mouse) { + HTUserMsg(CANCELLED); + } + break; + } + + if (disabled || term_options) { + _statusline(""); + } else if (!for_mouse) { + _statusline(VALUE_ACCEPTED); + } + return (cur_choice); +} + +#endif /* !NO_OPTION_MENU */ +#ifndef NO_OPTION_FORMS + +/* + * I'm paranoid about mistyping strings. Also, this way they get combined + * so we don't have to worry about the intelligence of the compiler. + * We don't need to burn memory like it's cheap. We're better than that. + */ +#define SELECTED(flag) (flag) ? selected_string : "" +#define DISABLED(flag) (flag) ? disabled_string : "" + +typedef struct { + int value; + const char *LongName; + const char *HtmlName; +} OptValues; + +#define END_OPTIONS {0, 0, 0} + +#define HasOptValues(table) (((table) != NULL) && ((table)->LongName != NULL)) + +typedef struct { + char *tag; + char *value; +} PostPair; + +static const char selected_string[] = "selected"; +static const char disabled_string[] = "disabled"; +static const char on_string[] = N_("ON"); +static const char off_string[] = N_("OFF"); +static const char never_string[] = N_("NEVER"); +static const char always_string[] = N_("ALWAYS"); +static OptValues bool_values[] = +{ + {FALSE, N_("OFF"), "OFF"}, + {TRUE, N_("ON"), "ON"}, + END_OPTIONS +}; + +static const char *secure_string = "secure"; +static char *secure_value = NULL; +static const char *save_options_string = "save_options"; + +/* + * Personal Preferences + */ +static const char *cookies_string = RC_SET_COOKIES; +static const char *cookies_ignore_all_string = N_("ignore"); +static const char *cookies_up_to_user_string = N_("ask user"); +static const char *cookies_accept_all_string = N_("accept all"); +static const char *x_display_string = RC_DISPLAY; +static const char *editor_string = RC_FILE_EDITOR; +static const char *emacs_keys_string = RC_EMACS_KEYS; + +#if defined(ENABLE_OPTS_CHANGE_EXEC) && (defined(EXEC_LINKS) || defined(EXEC_SCRIPTS)) +#define EXEC_ALWAYS 2 +#define EXEC_LOCAL 1 +#define EXEC_NEVER 0 +static const char *exec_links_string = RC_RUN_ALL_EXECUTION_LINKS; +static OptValues exec_links_values[] = +{ + {EXEC_NEVER, N_("ALWAYS OFF"), "ALWAYS OFF"}, + {EXEC_LOCAL, N_("FOR LOCAL FILES ONLY"), "FOR LOCAL FILES ONLY"}, +#ifndef NEVER_ALLOW_REMOTE_EXEC + {EXEC_ALWAYS, N_("ALWAYS ON"), "ALWAYS ON"}, +#endif + END_OPTIONS +}; +#endif /* ENABLE_OPTS_CHANGE_EXEC */ + +#ifdef EXP_KEYBOARD_LAYOUT +static const char *kblayout_string = RC_KBLAYOUT; +#endif +static const char *keypad_mode_string = RC_KEYPAD_MODE; +static OptValues keypad_mode_values[] = +{ + {NUMBERS_AS_ARROWS, N_("Numbers act as arrows"), + "number_arrows"}, + {LINKS_ARE_NUMBERED, N_("Links are numbered"), + "links_numbered"}, + {LINKS_AND_FIELDS_ARE_NUMBERED, + N_("Links and form fields are numbered"), + "links_and_forms"}, + {FIELDS_ARE_NUMBERED, + N_("Form fields are numbered"), + "forms_numbered"}, + END_OPTIONS +}; +static const char *lineedit_mode_string = RC_LINEEDIT_MODE; +static const char *mail_address_string = RC_PERSONAL_MAIL_ADDRESS; +static const char *personal_name_string = RC_PERSONAL_MAIL_NAME; +static const char *search_type_string = RC_CASE_SENSITIVE_SEARCHING; + +#ifndef DISABLE_FTP +static const char *anonftp_password_string = RC_ANONFTP_PASSWORD; +#endif + +static OptValues search_type_values[] = +{ + {FALSE, N_("Case insensitive"), "case_insensitive"}, + {TRUE, N_("Case sensitive"), "case_sensitive"}, + END_OPTIONS +}; + +#if defined(USE_SLANG) || defined(COLOR_CURSES) +static const char *show_color_string = RC_SHOW_COLOR; +static OptValues show_color_values[] = +{ + {SHOW_COLOR_NEVER, never_string, never_string}, + {SHOW_COLOR_OFF, off_string, off_string}, + {SHOW_COLOR_ON, on_string, on_string}, + {SHOW_COLOR_ALWAYS, always_string, always_string}, + END_OPTIONS +}; +#endif + +#ifdef USE_COLOR_STYLE +static const char *color_style_string = RC_COLOR_STYLE; +static OptValues *color_style_values; +static HTList *color_style_list; +#endif + +#ifdef USE_DEFAULT_COLORS +static const char *default_colors_string = RC_DEFAULT_COLORS; +#endif + +static const char *show_cursor_string = RC_SHOW_CURSOR; + +static const char *underline_links_string = RC_UNDERLINE_LINKS; + +#ifdef USE_SCROLLBAR +static const char *show_scrollbar_string = RC_SCROLLBAR; +#endif + +static const char prompt_dft_string[] = N_("prompt normally"); +static const char prompt_yes_string[] = N_("force yes-response"); +static const char prompt_no_string[] = N_("force no-response"); +static OptValues prompt_values[] = +{ + {FORCE_PROMPT_DFT, prompt_dft_string, prompt_dft_string}, + {FORCE_PROMPT_YES, prompt_yes_string, prompt_yes_string}, + {FORCE_PROMPT_NO, prompt_no_string, prompt_no_string}, + END_OPTIONS +}; +static const char *cookie_prompt_string = RC_FORCE_COOKIE_PROMPT; + +static const char RFC_2109_string[] = N_("RFC 2109"); +static const char RFC_2965_string[] = N_("RFC 2965"); +static const char RFC_6265_string[] = N_("RFC 6265"); +static OptValues cookies_values[] = +{ + {COOKIES_RFC_2109, RFC_2109_string, RFC_2109_string}, + {COOKIES_RFC_2965, RFC_2965_string, RFC_2965_string}, + {COOKIES_RFC_6265, RFC_6265_string, RFC_6265_string}, + END_OPTIONS +}; +static const char *cookie_version_string = RC_COOKIE_VERSION; + +#ifdef USE_SSL +static const char *ssl_prompt_string = RC_FORCE_SSL_PROMPT; +#endif + +static const char *user_mode_string = RC_USER_MODE; +static OptValues user_mode_values[] = +{ + {NOVICE_MODE, N_("Novice"), "Novice"}, + {INTERMEDIATE_MODE, N_("Intermediate"), "Intermediate"}, + {ADVANCED_MODE, N_("Advanced"), "Advanced"}, + {MINIMAL_MODE, N_("Minimal"), "Minimal"}, + END_OPTIONS +}; + +static const char *vi_keys_string = RC_VI_KEYS; + +static const char *visited_links_string = RC_VISITED_LINKS; +static OptValues visited_links_values[] = +{ + {VISITED_LINKS_AS_FIRST_V, N_("By First Visit"), "first_visited"}, + {VISITED_LINKS_AS_FIRST_V | VISITED_LINKS_REVERSE, + N_("By First Visit Reversed"), "first_visited_reversed"}, + {VISITED_LINKS_AS_TREE, N_("As Visit Tree"), "visit_tree"}, + {VISITED_LINKS_AS_LATEST, N_("By Last Visit"), "last_visited"}, + {VISITED_LINKS_AS_LATEST | VISITED_LINKS_REVERSE, + N_("By Last Visit Reversed"), "last_visited_reversed"}, + END_OPTIONS +}; + +/* + * Document Layout + */ +static const char *DTD_recovery_string = RC_TAGSOUP; +static OptValues DTD_type_values[] = +{ + /* Old_DTD variable */ + {TRUE, N_("relaxed (TagSoup mode)"), "tagsoup"}, + {FALSE, N_("strict (SortaSGML mode)"), "sortasgml"}, + END_OPTIONS +}; + +static const char *bad_html_string = RC_BAD_HTML; +static OptValues bad_html_values[] = +{ + {BAD_HTML_IGNORE, N_("Ignore"), "ignore"}, + {BAD_HTML_TRACE, N_("Add to trace-file"), "trace"}, + {BAD_HTML_MESSAGE, N_("Add to LYNXMESSAGES"), "message"}, + {BAD_HTML_WARN, N_("Warn, point to trace-file"), "warn"}, + END_OPTIONS +}; + +static const char *select_popups_string = RC_SELECT_POPUPS; +static const char *images_string = "images"; +static const char *images_ignore_all_string = N_("ignore"); +static const char *images_use_label_string = N_("as labels"); +static const char *images_use_links_string = N_("as links"); + +static const char *verbose_images_string = RC_VERBOSE_IMAGES; +static OptValues verbose_images_type_values[] = +{ + /* verbose_img variable */ + {FALSE, N_("OFF"), "OFF"}, + {TRUE, N_("show filename"), "ON"}, + END_OPTIONS +}; + +static const char *collapse_br_tags_string = RC_COLLAPSE_BR_TAGS; +static OptValues collapse_br_tags_values[] = +{ + /* LYCollapseBRs variable */ + {FALSE, N_("OFF"), "OFF"}, + {TRUE, N_("collapse"), "ON"}, + END_OPTIONS +}; + +static const char *trim_blank_lines_string = RC_TRIM_BLANK_LINES; +static OptValues trim_blank_lines_values[] = +{ + /* LYtrimBlankLines variable */ + {FALSE, N_("OFF"), "OFF"}, + {TRUE, N_("trim-lines"), "ON"}, + END_OPTIONS +}; + +/* + * Bookmark Options + */ +static const char *mbm_string = RC_MULTI_BOOKMARK; +static OptValues mbm_values[] = +{ + {MBM_OFF, N_("OFF"), "OFF"}, + {MBM_STANDARD, N_("STANDARD"), "STANDARD"}, + {MBM_ADVANCED, N_("ADVANCED"), "ADVANCED"}, + END_OPTIONS +}; + +static const char *single_bookmark_string = RC_BOOKMARK_FILE; + +#ifdef USE_SESSIONS +static const char *auto_session_string = RC_AUTO_SESSION; +static const char *single_session_string = RC_SESSION_FILE; +#endif + +/* + * Character Set Options + */ +static const char *assume_char_set_string = RC_ASSUME_CHARSET; +static const char *display_char_set_string = RC_CHARACTER_SET; +static const char *raw_mode_string = RC_RAW_MODE; + +#ifdef USE_IDN2 +static const char *idna_mode_string = RC_IDNA_MODE; +static OptValues idna_values[] = +{ + {LYidna2003, N_("IDNA 2003"), "idna2003"}, + {LYidna2008, N_("IDNA 2008"), "idna2008"}, + {LYidnaTR46, N_("IDNA TR46"), "idnaTR46"}, + {LYidnaCompat, N_("IDNA Compatible"), "idnaCompat"}, + END_OPTIONS +}; +#endif + +#ifdef USE_LOCALE_CHARSET +static const char *locale_charset_string = RC_LOCALE_CHARSET; +#endif + +static const char *html5_charsets_string = RC_HTML5_CHARSETS; + +/* + * File Management Options + */ +static const char *show_dotfiles_string = RC_SHOW_DOTFILES; +static const char *no_pause_string = RC_NO_PAUSE; + +#ifdef DIRED_SUPPORT +static const char *dired_list_string = RC_DIR_LIST_STYLE; +static OptValues dired_list_values[] = +{ + {DIRS_FIRST, N_("Directories first"), "dired_dir"}, + {FILES_FIRST, N_("Files first"), "dired_files"}, + {MIXED_STYLE, N_("Mixed style"), "dired_mixed"}, + END_OPTIONS +}; + +#ifdef LONG_LIST +static const char *dired_sort_string = RC_DIR_LIST_ORDER; +static OptValues dired_sort_values[] = +{ + {ORDER_BY_NAME, N_("By Name"), "dired_by_name"}, + {ORDER_BY_TYPE, N_("By Type"), "dired_by_type"}, + {ORDER_BY_SIZE, N_("By Size"), "dired_by_size"}, + {ORDER_BY_DATE, N_("By Date"), "dired_by_date"}, + {ORDER_BY_MODE, N_("By Mode"), "dired_by_mode"}, +#ifndef NO_GROUPS + {ORDER_BY_USER, N_("By User"), "dired_by_user"}, + {ORDER_BY_GROUP, N_("By Group"), "dired_by_group"}, +#endif + END_OPTIONS +}; +#endif /* LONG_LIST */ +#endif /* DIRED_SUPPORT */ + +#ifndef DISABLE_FTP +static const char *passive_ftp_string = RC_FTP_PASSIVE; + +static const char *ftp_sort_string = RC_FILE_SORTING_METHOD; +static OptValues ftp_sort_values[] = +{ + {FILE_BY_NAME, N_("By Name"), "ftp_by_name"}, + {FILE_BY_TYPE, N_("By Type"), "ftp_by_type"}, + {FILE_BY_SIZE, N_("By Size"), "ftp_by_size"}, + {FILE_BY_DATE, N_("By Date"), "ftp_by_date"}, + END_OPTIONS +}; +#endif + +#ifdef USE_READPROGRESS +static const char *show_rate_string = RC_SHOW_KB_RATE; +static OptValues rate_values[] = +{ + {rateOFF, N_("Do not show rate"), "rate_off"}, + {rateBYTES, N_("Show %s/sec rate"), "rate_bytes"}, + {rateKB, N_("Show %s/sec rate"), "rate_kb"}, +#ifdef USE_READPROGRESS + {rateEtaBYTES, N_("Show %s/sec, ETA"), "rate_eta_bytes"}, + {rateEtaKB, N_("Show %s/sec, ETA"), "rate_eta_kb"}, + {rateEtaBYTES2, N_("Show %s/sec (2-digits), ETA"), "rate_eta_bytes2"}, + {rateEtaKB2, N_("Show %s/sec (2-digits), ETA"), "rate_eta_kb2"}, +#endif +#ifdef USE_PROGRESSBAR + {rateBAR, N_("Show progressbar"), "rate_bar"}, +#endif + END_OPTIONS +}; +#endif /* USE_READPROGRESS */ + +static const char *preferred_content_string = RC_PREFERRED_CONTENT_TYPE; +static OptValues content_values[] = +{ + {contentBINARY, STR_BINARY, STR_BINARY}, + {contentTEXT, STR_PLAINTEXT, STR_PLAINTEXT}, + {contentHTML, STR_HTML, STR_HTML}, + END_OPTIONS +}; + +/* + * Presentation (MIME) types used in "Accept". + */ +static const char *preferred_media_string = RC_PREFERRED_MEDIA_TYPES; +static OptValues media_values[] = +{ + {mediaOpt1, N_("Accept lynx's internal types"), "media_opt1"}, + {mediaOpt2, N_("Also accept lynx.cfg's types"), "media_opt2"}, + {mediaOpt3, N_("Also accept user's types"), "media_opt3"}, + {mediaOpt4, N_("Also accept system's types"), "media_opt4"}, + {mediaALL, N_("Accept all types"), "media_all"}, + END_OPTIONS +}; + +static const char *preferred_encoding_string = RC_PREFERRED_ENCODING; +static OptValues encoding_values[] = +{ + {encodingNONE, N_("None"), "encoding_none"}, +#if defined(USE_ZLIB) || defined(GZIP_PATH) + {encodingGZIP, N_("gzip"), "encoding_gzip"}, + {encodingDEFLATE, N_("deflate"), "encoding_deflate"}, +#endif +#if defined(USE_ZLIB) || defined(COMPRESS_PATH) + {encodingCOMPRESS, N_("compress"), "encoding_compress"}, +#endif +#if defined(USE_BZLIB) || defined(BZIP2_PATH) + {encodingBZIP2, N_("bzip2"), "encoding_bzip2"}, +#endif +#if defined(USE_BROTLI) || defined(BROTLI_PATH) + {encodingBROTLI, N_("brotli"), "encoding_brotli"}, +#endif + {encodingALL, N_("All"), "encoding_all"}, + END_OPTIONS +}; + +/* + * Headers transferred to remote server + */ +static const char *http_protocol_string = RC_HTTP_PROTOCOL; +static OptValues http_protocol_values[] = +{ + {HTTP_1_0, N_("HTTP 1.0"), "HTTP_1_0"}, + {HTTP_1_1, N_("HTTP 1.1"), "HTTP_1_1"}, + END_OPTIONS +}; + +static const char *preferred_doc_char_string = RC_PREFERRED_CHARSET; +static const char *preferred_doc_lang_string = RC_PREFERRED_LANGUAGE; +static const char *send_user_agent_string = RC_SEND_USERAGENT; +static const char *user_agent_string = RC_USERAGENT; + +static const char *ssl_client_certificate_file = RC_SSL_CLIENT_CERT_FILE; +static const char *ssl_client_key_file = RC_SSL_CLIENT_KEY_FILE; + +#define PutHeader(fp, Name) \ + fprintf(fp, "\n%s%s\n", MARGIN_STR, LYEntifyTitle(&buffer, Name)); + +#define PutCheckBox(fp, Name, Value, disable) \ + fprintf(fp,\ + "\n",\ + Name, Value ? "checked" : "", disable_all?disabled_string:disable) + +#define PutTextInput(fp, Name, Value, Size, disable) \ + fprintf(fp,\ + "\n",\ + (int) Size, Name, Value, disable_all?disabled_string:disable) + +#define PutOption(fp, flag, html, name) \ + fprintf(fp,"