From 510ed32cfbffa6148018869f5ade416505a450b3 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Mon, 15 Apr 2024 22:21:21 +0200 Subject: Adding upstream version 2.9.0rel.0. Signed-off-by: Daniel Baumann --- src/AttrList.h | 62 + src/DefaultStyle.c | 504 ++ src/GridText.c | 15054 ++++++++++++++++++++++++++++++++++++++ src/GridText.h | 302 + src/HTAlert.c | 1201 +++ src/HTAlert.h | 168 + src/HTFWriter.c | 1516 ++++ src/HTFont.h | 50 + src/HTForms.h | 174 + src/HTInit.c | 1503 ++++ src/HTML.c | 8198 +++++++++++++++++++++ src/HTML.h | 283 + src/HTNestedList.h | 44 + src/HTSaveToFile.h | 29 + src/LYBookmark.c | 1169 +++ src/LYBookmark.h | 25 + src/LYCgi.c | 757 ++ src/LYCgi.h | 16 + src/LYCharSets.c | 1157 +++ src/LYCharSets.h | 154 + src/LYCharUtils.c | 3419 +++++++++ src/LYCharUtils.h | 109 + src/LYCharVals.h | 34 + src/LYClean.c | 203 + src/LYClean.h | 25 + src/LYCookie.c | 2898 ++++++++ src/LYCookie.h | 59 + src/LYCurses.c | 3307 +++++++++ src/LYCurses.h | 865 +++ src/LYDownload.c | 591 ++ src/LYDownload.h | 21 + src/LYEdit.c | 298 + src/LYEdit.h | 18 + src/LYEditmap.c | 1931 +++++ src/LYExtern.c | 443 ++ src/LYExtern.h | 16 + src/LYForms.c | 1086 +++ src/LYGCurses.h | 246 + src/LYGetFile.c | 1581 ++++ src/LYGetFile.h | 38 + src/LYGlobalDefs.h | 740 ++ src/LYHash.c | 144 + src/LYHash.h | 67 + src/LYHistory.c | 1163 +++ src/LYHistory.h | 39 + src/LYIcon.rc | 34 + src/LYJump.c | 504 ++ src/LYJump.h | 36 + src/LYJustify.h | 83 + src/LYKeymap.c | 1545 ++++ src/LYKeymap.h | 307 + src/LYLeaks.c | 1169 +++ src/LYList.c | 374 + src/LYList.h | 16 + src/LYLocal.c | 2685 +++++++ src/LYLocal.h | 36 + src/LYMail.c | 1774 +++++ src/LYMail.h | 88 + src/LYMain.c | 4567 ++++++++++++ src/LYMainLoop.c | 8208 +++++++++++++++++++++ src/LYMainLoop.h | 34 + src/LYMap.c | 646 ++ src/LYMap.h | 29 + src/LYNews.c | 509 ++ src/LYNews.h | 19 + src/LYOptions.c | 4365 +++++++++++ src/LYOptions.h | 46 + src/LYPrettySrc.c | 427 ++ src/LYPrettySrc.h | 92 + src/LYPrint.c | 1461 ++++ src/LYPrint.h | 20 + src/LYReadCFG.c | 2682 +++++++ src/LYReadCFG.h | 75 + src/LYSearch.c | 379 + src/LYSearch.h | 33 + src/LYSession.c | 267 + src/LYSession.h | 16 + src/LYShowInfo.c | 485 ++ src/LYShowInfo.h | 21 + src/LYSignal.h | 31 + src/LYStrings.c | 6217 ++++++++++++++++ src/LYStrings.h | 402 + src/LYStructs.h | 190 + src/LYStyle.c | 970 +++ src/LYStyle.h | 88 + src/LYTraversal.c | 182 + src/LYTraversal.h | 23 + src/LYUpload.c | 221 + src/LYUpload.h | 17 + src/LYUtils.c | 8038 ++++++++++++++++++++ src/LYUtils.h | 578 ++ src/LYVMSdef.h | 18 + src/LYebcdic.c | 48 + src/LYexit.c | 185 + src/LYmktime.c | 364 + src/LYrcFile.c | 1140 +++ src/LYrcFile.h | 318 + src/TRSTable.c | 2012 +++++ src/TRSTable.h | 50 + src/UCAuto.c | 816 +++ src/UCAuto.h | 14 + src/UCAux.c | 798 ++ src/UCdomap.c | 2524 +++++++ src/UCdomap.h | 178 + src/Xsystem.c | 589 ++ src/chrtrans/README.format | 138 + src/chrtrans/README.tables | 76 + src/chrtrans/UCkd.h | 54 + src/chrtrans/build-chrtrans.com | 142 + src/chrtrans/build-header.com | 37 + src/chrtrans/caselower.h | 738 ++ src/chrtrans/cp1250_uni.tbl | 172 + src/chrtrans/cp1251_uni.tbl | 161 + src/chrtrans/cp1252_uni.tbl | 177 + src/chrtrans/cp1253_uni.tbl | 161 + src/chrtrans/cp1255_uni.tbl | 161 + src/chrtrans/cp1256_uni.tbl | 161 + src/chrtrans/cp1257_uni.tbl | 162 + src/chrtrans/cp437_uni.tbl | 181 + src/chrtrans/cp737_uni.tbl | 172 + src/chrtrans/cp775_uni.tbl | 159 + src/chrtrans/cp850_uni.tbl | 177 + src/chrtrans/cp852_uni.tbl | 170 + src/chrtrans/cp857_uni.tbl | 159 + src/chrtrans/cp862_uni.tbl | 160 + src/chrtrans/cp864_uni.tbl | 160 + src/chrtrans/cp866_uni.tbl | 159 + src/chrtrans/cp866u_uni.tbl | 157 + src/chrtrans/cp869_uni.tbl | 160 + src/chrtrans/def7_uni.tbl | 2951 ++++++++ src/chrtrans/dmcs_uni.tbl | 233 + src/chrtrans/entities.h | 1414 ++++ src/chrtrans/hp_uni.tbl | 212 + src/chrtrans/iso01_uni.tbl | 334 + src/chrtrans/iso02_uni.tbl | 265 + src/chrtrans/iso03_uni.tbl | 255 + src/chrtrans/iso04_uni.tbl | 252 + src/chrtrans/iso05_uni.tbl | 259 + src/chrtrans/iso06_uni.tbl | 208 + src/chrtrans/iso07_uni.tbl | 275 + src/chrtrans/iso08_uni.tbl | 229 + src/chrtrans/iso09_uni.tbl | 266 + src/chrtrans/iso10_uni.tbl | 153 + src/chrtrans/iso13_uni.tbl | 114 + src/chrtrans/iso14_uni.tbl | 114 + src/chrtrans/iso15_uni.tbl | 216 + src/chrtrans/iso16_uni.tbl | 120 + src/chrtrans/jcuken_kb.h | 22 + src/chrtrans/koi8r_uni.tbl | 147 + src/chrtrans/koi8u_uni.tbl | 154 + src/chrtrans/mac_uni.tbl | 284 + src/chrtrans/make-msc.bat | 6 + src/chrtrans/makefile.bcb | 123 + src/chrtrans/makefile.dos | 137 + src/chrtrans/makefile.in | 201 + src/chrtrans/makefile.msc | 139 + src/chrtrans/makehdrs.bat | 51 + src/chrtrans/makeuctb.c | 914 +++ src/chrtrans/makew32.bat | 13 + src/chrtrans/mnem2_suni.tbl | 1865 +++++ src/chrtrans/mnem_suni.tbl | 1861 +++++ src/chrtrans/next_uni.tbl | 185 + src/chrtrans/pt154_uni.tbl | 174 + src/chrtrans/rfc_suni.tbl | 1958 +++++ src/chrtrans/rot13_kb.h | 22 + src/chrtrans/utf8_uni.tbl | 35 + src/chrtrans/viscii_uni.tbl | 300 + src/chrtrans/yawerty_kb.h | 22 + src/cmu_tcp.opt | 1 + src/decc.opt | 2 + src/descrip.mms | 172 + src/gnuc.opt | 3 + src/makefile.dos | 115 + src/makefile.dsl | 105 + src/makefile.in | 242 + src/makefile.wsl | 68 + src/mktime.c | 71 + src/multinet.opt | 1 + src/multinet_ucx.opt | 1 + src/parsdate.c | 2425 ++++++ src/parsdate.h | 21 + src/parsdate.y | 991 +++ src/socketshr_tcp.opt | 1 + src/strstr.c | 60 + src/structdump.h | 164 + src/tcpipolb.opt | 1 + src/tcpipshr.opt | 1 + src/tcpwareolb.opt | 1 + src/tcpwareshr.opt | 1 + src/tidy_tls.c | 707 ++ src/ucxolb.opt | 1 + src/ucxshr.opt | 1 + src/vaxc.opt | 2 + src/wcwidth.c | 709 ++ src/wcwidth.h | 47 + src/win_tcp.opt | 1 + 196 files changed, 137302 insertions(+) create mode 100644 src/AttrList.h create mode 100644 src/DefaultStyle.c create mode 100644 src/GridText.c create mode 100644 src/GridText.h create mode 100644 src/HTAlert.c create mode 100644 src/HTAlert.h create mode 100644 src/HTFWriter.c create mode 100644 src/HTFont.h create mode 100644 src/HTForms.h create mode 100644 src/HTInit.c create mode 100644 src/HTML.c create mode 100644 src/HTML.h create mode 100644 src/HTNestedList.h create mode 100644 src/HTSaveToFile.h create mode 100644 src/LYBookmark.c create mode 100644 src/LYBookmark.h create mode 100644 src/LYCgi.c create mode 100644 src/LYCgi.h create mode 100644 src/LYCharSets.c create mode 100644 src/LYCharSets.h create mode 100644 src/LYCharUtils.c create mode 100644 src/LYCharUtils.h create mode 100644 src/LYCharVals.h create mode 100644 src/LYClean.c create mode 100644 src/LYClean.h create mode 100644 src/LYCookie.c create mode 100644 src/LYCookie.h create mode 100644 src/LYCurses.c create mode 100644 src/LYCurses.h create mode 100644 src/LYDownload.c create mode 100644 src/LYDownload.h create mode 100644 src/LYEdit.c create mode 100644 src/LYEdit.h create mode 100644 src/LYEditmap.c create mode 100644 src/LYExtern.c create mode 100644 src/LYExtern.h create mode 100644 src/LYForms.c create mode 100644 src/LYGCurses.h create mode 100644 src/LYGetFile.c create mode 100644 src/LYGetFile.h create mode 100644 src/LYGlobalDefs.h create mode 100644 src/LYHash.c create mode 100644 src/LYHash.h create mode 100644 src/LYHistory.c create mode 100644 src/LYHistory.h create mode 100644 src/LYIcon.rc create mode 100644 src/LYJump.c create mode 100644 src/LYJump.h create mode 100644 src/LYJustify.h create mode 100644 src/LYKeymap.c create mode 100644 src/LYKeymap.h create mode 100644 src/LYLeaks.c create mode 100644 src/LYList.c create mode 100644 src/LYList.h create mode 100644 src/LYLocal.c create mode 100644 src/LYLocal.h create mode 100644 src/LYMail.c create mode 100644 src/LYMail.h create mode 100644 src/LYMain.c create mode 100644 src/LYMainLoop.c create mode 100644 src/LYMainLoop.h create mode 100644 src/LYMap.c create mode 100644 src/LYMap.h create mode 100644 src/LYNews.c create mode 100644 src/LYNews.h create mode 100644 src/LYOptions.c create mode 100644 src/LYOptions.h create mode 100644 src/LYPrettySrc.c create mode 100644 src/LYPrettySrc.h create mode 100644 src/LYPrint.c create mode 100644 src/LYPrint.h create mode 100644 src/LYReadCFG.c create mode 100644 src/LYReadCFG.h create mode 100644 src/LYSearch.c create mode 100644 src/LYSearch.h create mode 100644 src/LYSession.c create mode 100644 src/LYSession.h create mode 100644 src/LYShowInfo.c create mode 100644 src/LYShowInfo.h create mode 100644 src/LYSignal.h create mode 100644 src/LYStrings.c create mode 100644 src/LYStrings.h create mode 100644 src/LYStructs.h create mode 100644 src/LYStyle.c create mode 100644 src/LYStyle.h create mode 100644 src/LYTraversal.c create mode 100644 src/LYTraversal.h create mode 100644 src/LYUpload.c create mode 100644 src/LYUpload.h create mode 100644 src/LYUtils.c create mode 100644 src/LYUtils.h create mode 100644 src/LYVMSdef.h create mode 100644 src/LYebcdic.c create mode 100644 src/LYexit.c create mode 100644 src/LYmktime.c create mode 100644 src/LYrcFile.c create mode 100644 src/LYrcFile.h create mode 100644 src/TRSTable.c create mode 100644 src/TRSTable.h create mode 100644 src/UCAuto.c create mode 100644 src/UCAuto.h create mode 100644 src/UCAux.c create mode 100644 src/UCdomap.c create mode 100644 src/UCdomap.h create mode 100644 src/Xsystem.c create mode 100644 src/chrtrans/README.format create mode 100644 src/chrtrans/README.tables create mode 100644 src/chrtrans/UCkd.h create mode 100644 src/chrtrans/build-chrtrans.com create mode 100644 src/chrtrans/build-header.com create mode 100644 src/chrtrans/caselower.h create mode 100644 src/chrtrans/cp1250_uni.tbl create mode 100644 src/chrtrans/cp1251_uni.tbl create mode 100644 src/chrtrans/cp1252_uni.tbl create mode 100644 src/chrtrans/cp1253_uni.tbl create mode 100644 src/chrtrans/cp1255_uni.tbl create mode 100644 src/chrtrans/cp1256_uni.tbl create mode 100644 src/chrtrans/cp1257_uni.tbl create mode 100644 src/chrtrans/cp437_uni.tbl create mode 100644 src/chrtrans/cp737_uni.tbl create mode 100644 src/chrtrans/cp775_uni.tbl create mode 100644 src/chrtrans/cp850_uni.tbl create mode 100644 src/chrtrans/cp852_uni.tbl create mode 100644 src/chrtrans/cp857_uni.tbl create mode 100644 src/chrtrans/cp862_uni.tbl create mode 100644 src/chrtrans/cp864_uni.tbl create mode 100644 src/chrtrans/cp866_uni.tbl create mode 100644 src/chrtrans/cp866u_uni.tbl create mode 100644 src/chrtrans/cp869_uni.tbl create mode 100644 src/chrtrans/def7_uni.tbl create mode 100644 src/chrtrans/dmcs_uni.tbl create mode 100644 src/chrtrans/entities.h create mode 100644 src/chrtrans/hp_uni.tbl create mode 100644 src/chrtrans/iso01_uni.tbl create mode 100644 src/chrtrans/iso02_uni.tbl create mode 100644 src/chrtrans/iso03_uni.tbl create mode 100644 src/chrtrans/iso04_uni.tbl create mode 100644 src/chrtrans/iso05_uni.tbl create mode 100644 src/chrtrans/iso06_uni.tbl create mode 100644 src/chrtrans/iso07_uni.tbl create mode 100644 src/chrtrans/iso08_uni.tbl create mode 100644 src/chrtrans/iso09_uni.tbl create mode 100644 src/chrtrans/iso10_uni.tbl create mode 100644 src/chrtrans/iso13_uni.tbl create mode 100644 src/chrtrans/iso14_uni.tbl create mode 100644 src/chrtrans/iso15_uni.tbl create mode 100644 src/chrtrans/iso16_uni.tbl create mode 100644 src/chrtrans/jcuken_kb.h create mode 100644 src/chrtrans/koi8r_uni.tbl create mode 100644 src/chrtrans/koi8u_uni.tbl create mode 100644 src/chrtrans/mac_uni.tbl create mode 100644 src/chrtrans/make-msc.bat create mode 100644 src/chrtrans/makefile.bcb create mode 100644 src/chrtrans/makefile.dos create mode 100644 src/chrtrans/makefile.in create mode 100644 src/chrtrans/makefile.msc create mode 100644 src/chrtrans/makehdrs.bat create mode 100644 src/chrtrans/makeuctb.c create mode 100644 src/chrtrans/makew32.bat create mode 100644 src/chrtrans/mnem2_suni.tbl create mode 100644 src/chrtrans/mnem_suni.tbl create mode 100644 src/chrtrans/next_uni.tbl create mode 100644 src/chrtrans/pt154_uni.tbl create mode 100644 src/chrtrans/rfc_suni.tbl create mode 100644 src/chrtrans/rot13_kb.h create mode 100644 src/chrtrans/utf8_uni.tbl create mode 100644 src/chrtrans/viscii_uni.tbl create mode 100644 src/chrtrans/yawerty_kb.h create mode 100644 src/cmu_tcp.opt create mode 100644 src/decc.opt create mode 100644 src/descrip.mms create mode 100644 src/gnuc.opt create mode 100644 src/makefile.dos create mode 100644 src/makefile.dsl create mode 100644 src/makefile.in create mode 100644 src/makefile.wsl create mode 100644 src/mktime.c create mode 100644 src/multinet.opt create mode 100644 src/multinet_ucx.opt create mode 100644 src/parsdate.c create mode 100644 src/parsdate.h create mode 100644 src/parsdate.y create mode 100644 src/socketshr_tcp.opt create mode 100644 src/strstr.c create mode 100644 src/structdump.h create mode 100644 src/tcpipolb.opt create mode 100644 src/tcpipshr.opt create mode 100644 src/tcpwareolb.opt create mode 100644 src/tcpwareshr.opt create mode 100644 src/tidy_tls.c create mode 100644 src/ucxolb.opt create mode 100644 src/ucxshr.opt create mode 100644 src/vaxc.opt create mode 100644 src/wcwidth.c create mode 100644 src/wcwidth.h create mode 100644 src/win_tcp.opt (limited to 'src') 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..806a053 --- /dev/null +++ b/src/GridText.c @@ -0,0 +1,15054 @@ +/* + * $LynxId: GridText.c,v 1.347 2024/01/15 19:11:55 Gisle.Vanem 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, cur_shift; + 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)); + + cur_shift = 0; + /* + * 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--; + cur_shift++; + 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..06a669a --- /dev/null +++ b/src/HTFWriter.c @@ -0,0 +1,1516 @@ +/* + * $LynxId: HTFWriter.c,v 1.125 2023/11/05 23:46:45 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 + +/* 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 (LYCopyFile(in_name, copied) == 0) { + char expanded[LY_MAXPATH]; + char *command = NULL; + + if ((program = HTGetProgramPath(ppUNCOMPRESS)) != 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 + && me->input_format == HTAtom_for("application/x-gzip") + && !strcmp(me->anchor->content_encoding, "gzip")) { + decompress_gzip(me); + } +#ifdef VMS + 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, "

    \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); + fprintf(fp, "
    \n"); + } + 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; + + 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..94b7a04 --- /dev/null +++ b/src/LYCharSets.c @@ -0,0 +1,1157 @@ +/* + * $LynxId: LYCharSets.c,v 1.71 2021/06/29 22:01:12 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. + */ + DisplayCharsetMatchLocale = TRUE; /* old-style */ + return; + + } else if (strncasecomp(LYCharSet_UC[i].MIMEname, "cp", 2) || + strncasecomp(LYCharSet_UC[i].MIMEname, "windows", 7)) { + /* + * Assume dos/windows displays usually on remote terminal, hence it + * rarely matches locale. (In fact, MS Windows codepoints locale are + * never seen on UNIX). + */ + match = FALSE; + } else { + match = TRUE; /* guess, but see below */ + +#if !defined(LOCALE) + if (LYCharSet_UC[i].enc != UCT_ENC_UTF8) + /* + * Leave true for utf-8 display - the code doesn't deal very well + * with this case. - kw + */ + match = FALSE; +#else + if (UCForce8bitTOUPPER) { + /* + * Force disable locale (from lynx.cfg) + */ + match = FALSE; + } +#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..55be205 --- /dev/null +++ b/src/LYMainLoop.c @@ -0,0 +1,8208 @@ +/* + * $LynxId: LYMainLoop.c,v 1.254 2024/01/15 19:10:04 Gisle.Vanem 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 && curdoc.link >= 0) { + show_main_statusline(links[curdoc.link], + ((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; +#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.type & WWW_LINK_TYPE)) { +#else + } else if (lynx_mode == FORMS_LYNX_MODE && nlinks > 0 && + !((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) { + /* + * 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,"