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

+Software freely available

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

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

This is part of the +CERN WWW distribution conditions.

Declaration to this effect signed by +the CERN directors of Administration (H. Weber) and Research (W. +Hoogland), May 1993. diff --git a/WWW/Library/Implementation/HTAABrow.c b/WWW/Library/Implementation/HTAABrow.c new file mode 100644 index 0000000..c963acd --- /dev/null +++ b/WWW/Library/Implementation/HTAABrow.c @@ -0,0 +1,1354 @@ +/* + * $LynxId: HTAABrow.c,v 1.43 2018/05/11 22:54:19 tom Exp $ + * + * MODULE HTAABrow.c + * BROWSER SIDE ACCESS AUTHORIZATION MODULE + * + * Contains the code for keeping track on server hostnames, + * port numbers, scheme names, usernames, passwords + * (and servers' public keys). + * + * IMPORTANT: + * Routines in this module use dynamic allocation, but free + * automatically all the memory reserved by them. + * + * Therefore the caller never has to (and never should) + * free() any object returned by these functions. + * + * Therefore also all the strings returned by this package + * are only valid until the next call to the same function + * is made. This approach is selected, because of the nature + * of access authorization: no string returned by the package + * needs to be valid longer than until the next call. + * + * This also makes it easy to plug the AA package in: + * you don't have to ponder whether to free() something + * here or is it done somewhere else (because it is always + * done somewhere else). + * + * The strings that the package needs to store are copied + * so the original strings given as parameters to AA + * functions may be freed or modified with no side effects. + * + * The AA package does not free() anything else than what + * it has itself allocated. + * + * AUTHORS: + * AL Ari Luotonen luotonen@dxcern.cern.ch + * + * HISTORY: + * Oct 17 AL Made corrections suggested by marca: + * Added if (!realm->username) return NULL; + * Changed some ""s to NULLs. + * Now doing calloc() to init uuencode source; + * otherwise HTUU_encode() reads uninitialized memory + * every now and then (not a real bug but not pretty). + * Corrected the formula for uuencode destination size. + * + * 28 Apr 1997 AJL Do Proxy Authorisation. + * + * BUGS: + * + * + */ + +#include +#include +#include /* URL parsing function */ +#include /* HTList object */ +#include /* HTConfirm(), HTPrompt() */ +#include /* AA common to both sides */ +#include /* Assoc list */ +#include /* Are we using an HTTP gateway? */ +#include /* Implemented here */ +#include /* Uuencoding and uudecoding */ + +#include + +/* + * Local datatype definitions + * + * HTAAServer contains all the information about one server. + */ +typedef struct { + + char *hostname; /* Host's name */ + int portnumber; /* Port number */ + BOOL IsProxy; /* Is it a proxy? */ + HTList *setups; /* List of protection setups + on this server; i.e., valid + authentication schemes and + templates when to use them. + This is actually a list of + HTAASetup objects. */ + HTList *realms; /* Information about passwords */ +} HTAAServer; + +/* + * HTAASetup contains information about one server's one + * protected tree of documents. + */ +typedef struct { + HTAAServer *server; /* Which server serves this tree */ + char *ctemplate; /* Template for this tree */ + HTList *valid_schemes; /* Valid authentic.schemes */ + HTAssocList **scheme_specifics; /* Scheme specific params */ + BOOL retry; /* Failed last time -- reprompt (or whatever) */ +} HTAASetup; + +/* + * Information about usernames and passwords in + * Basic and Pubkey authentication schemes; + */ +typedef struct { + char *realmname; /* Password domain name */ + char *username; /* Username in that domain */ + char *password; /* Corresponding password */ +} HTAARealm; + +/* + * To free off all globals. - FM + */ +static void free_HTAAGlobals(void); +static BOOL free_HTAAGlobalsSet = FALSE; +static char *HTAA_composeAuthResult = NULL; +static char *compose_auth_stringResult = NULL; /* Uuencoded presentation */ + +/* + * Module-wide global variables + */ +static HTList *server_table = NULL; /* Browser's info about servers */ +static char *secret_key = NULL; /* Browser's latest secret key */ +static HTAASetup *current_setup = NULL; /* The server setup we are currently */ + + /* talking to */ +static char *current_hostname = NULL; /* The server's name and portnumber */ +static int current_portnumber = 80; /* where we are currently trying to */ + + /* connect. */ +static char *current_docname = NULL; /* The document's name we are */ + + /* trying to access. */ +static char *HTAAForwardAuth = NULL; /* Authorization: line to forward */ + + /* (used by gateway httpds) */ +static HTAASetup *proxy_setup = NULL; /* Same as above, but for Proxy -AJL */ +static char *proxy_hostname = NULL; +static char *proxy_docname = NULL; +static int proxy_portnumber = 80; + +/*** HTAAForwardAuth for enabling gateway-httpds to forward Authorization ***/ + +void HTAAForwardAuth_set(const char *scheme_name, + const char *scheme_specifics) +{ + size_t len = (20 + + (scheme_name ? strlen(scheme_name) : 0) + + (scheme_specifics ? strlen(scheme_specifics) : 0)); + + FREE(HTAAForwardAuth); + if ((HTAAForwardAuth = typecallocn(char, len)) == 0) + outofmem(__FILE__, "HTAAForwardAuth_set"); + + strcpy(HTAAForwardAuth, "Authorization: "); + if (scheme_name) { + strcat(HTAAForwardAuth, scheme_name); + strcat(HTAAForwardAuth, " "); + if (scheme_specifics) { + strcat(HTAAForwardAuth, scheme_specifics); + } + } +} + +void HTAAForwardAuth_reset(void) +{ + FREE(HTAAForwardAuth); +} + +/**************************** HTAAServer ***********************************/ + +static void HTAASetup_delete(HTAASetup * killme); /* Forward */ + +/* static HTAAServer_new() + * ALLOCATE A NEW NODE TO HOLD SERVER INFO + * AND ADD IT TO THE LIST OF SERVERS + * ON ENTRY: + * hostname is the name of the host that the server + * is running in. + * portnumber is the portnumber which the server listens. + * IsProxy should be TRUE if this is a proxy. + * + * ON EXIT: + * returns the newly-allocated node with all the strings + * duplicated. + * Strings will be automatically freed by + * the function HTAAServer_delete(), which also + * frees the node itself. + */ +static HTAAServer *HTAAServer_new(const char *hostname, + int portnumber, + int IsProxy) +{ + HTAAServer *server; + + if ((server = typecalloc(HTAAServer)) == 0) + outofmem(__FILE__, "HTAAServer_new"); + + server->hostname = NULL; + server->portnumber = (portnumber > 0 ? portnumber : 80); + server->IsProxy = (BOOLEAN) IsProxy; + server->setups = HTList_new(); + server->realms = HTList_new(); + + if (hostname) + StrAllocCopy(server->hostname, hostname); + + if (!server_table) + server_table = HTList_new(); + + HTList_addObject(server_table, (void *) server); + + return server; +} + +/* static HTAAServer_delete() + * + * DELETE THE ENTRY FOR THE SERVER FROM THE HOST TABLE, + * AND FREE THE MEMORY USED BY IT. + * + * ON ENTRY: + * killme points to the HTAAServer to be freed. + * + * ON EXIT: + * returns nothing. + */ +static void HTAAServer_delete(HTAAServer *killme) +{ + int n, i; + HTAASetup *setup; + HTAARealm *realm; + HTList *cur; + + if (killme) { + if (killme->setups != NULL) { + n = HTList_count(killme->setups); + for (i = (n - 1); i >= 0; i--) { + if ((setup = (HTAASetup *) HTList_objectAt(killme->setups, + i)) != NULL) { + HTAASetup_delete(setup); + setup = NULL; + } + } + HTList_delete(killme->setups); + killme->setups = NULL; + } + + cur = killme->realms; + while (NULL != (realm = (HTAARealm *) HTList_nextObject(cur))) { + FREE(realm->realmname); + FREE(realm->username); + FREE(realm->password); + FREE(realm); + } + HTList_delete(killme->realms); + killme->realms = NULL; + + FREE(killme->hostname); + + HTList_removeObject(server_table, (void *) killme); + FREE(killme); + } +} + +/* static HTAAServer_lookup() + * LOOK UP SERVER BY HOSTNAME AND PORTNUMBER + * ON ENTRY: + * hostname obvious. + * portnumber if non-positive defaults to 80. + * IsProxy should be TRUE if this is a proxy. + * + * Looks up the server in the module-global server_table. + * + * ON EXIT: + * returns pointer to a HTAAServer structure + * representing the looked-up server. + * NULL, if not found. + */ +static HTAAServer *HTAAServer_lookup(const char *hostname, + int portnumber, + int IsProxy) +{ + if (hostname) { + HTList *cur = server_table; + HTAAServer *server; + + if (portnumber <= 0) + portnumber = 80; + + while (NULL != (server = (HTAAServer *) HTList_nextObject(cur))) { + if (server->portnumber == portnumber && + 0 == strcmp(server->hostname, hostname) && + server->IsProxy == IsProxy) + return server; + } + } + return NULL; /* NULL parameter, or not found */ +} + +/*************************** HTAASetup *******************************/ + +/* static HTAASetup_lookup() + * FIGURE OUT WHICH AUTHENTICATION SETUP THE SERVER + * IS USING FOR A GIVEN FILE ON A GIVEN HOST AND PORT + * + * ON ENTRY: + * hostname is the name of the server host machine. + * portnumber is the port that the server is running in. + * docname is the (URL-)pathname of the document we + * are trying to access. + * IsProxy should be TRUE if this is a proxy. + * + * This function goes through the information known about + * all the setups of the server, and finds out if the given + * filename resides in one of the protected directories. + * + * ON EXIT: + * returns NULL if no match. + * Otherwise, a HTAASetup structure representing + * the protected server setup on the corresponding + * document tree. + * + */ +static HTAASetup *HTAASetup_lookup(const char *hostname, + int portnumber, + const char *docname, + int IsProxy) +{ + HTAAServer *server; + HTAASetup *setup; + + if (portnumber <= 0) + portnumber = 80; + + if (hostname && docname && *hostname && *docname && + NULL != (server = HTAAServer_lookup(hostname, + portnumber, + IsProxy))) { + + HTList *cur = server->setups; + + CTRACE((tfp, "%s %s (%s:%d:%s)\n", + "HTAASetup_lookup: resolving setup for", + (IsProxy ? "proxy" : "server"), + hostname, portnumber, docname)); + + while (NULL != (setup = (HTAASetup *) HTList_nextObject(cur))) { + if (HTAA_templateMatch(setup->ctemplate, docname)) { + CTRACE((tfp, "%s `%s' %s `%s'\n", + "HTAASetup_lookup:", docname, + "matched template", setup->ctemplate)); + return setup; + } else { + CTRACE((tfp, "%s `%s' %s `%s'\n", + "HTAASetup_lookup:", docname, + "did NOT match template", setup->ctemplate)); + } + } /* while setups remain */ + } + /* if valid parameters and server found */ + CTRACE((tfp, "%s `%s' %s\n", + "HTAASetup_lookup: No template matched", + NONNULL(docname), + "(so probably not protected)")); + + return NULL; /* NULL in parameters, or not found */ +} + +/* static HTAASetup_new() + * CREATE A NEW SETUP NODE + * ON ENTRY: + * server is a pointer to a HTAAServer structure + * to which this setup belongs. + * ctemplate documents matching this template + * are protected according to this setup. + * valid_schemes a list containing all valid authentication + * schemes for this setup. + * If NULL, all schemes are disallowed. + * scheme_specifics is an array of assoc lists, which + * contain scheme specific parameters given + * by server in Authenticate: fields. + * If NULL, all scheme specifics are + * set to NULL. + * ON EXIT: + * returns a new HTAASetup node, and also adds it as + * part of the HTAAServer given as parameter. + */ +static HTAASetup *HTAASetup_new(HTAAServer *server, char *ctemplate, + HTList *valid_schemes, + HTAssocList **scheme_specifics) +{ + HTAASetup *setup; + + if (!server || isEmpty(ctemplate)) + return NULL; + + if ((setup = typecalloc(HTAASetup)) == 0) + outofmem(__FILE__, "HTAASetup_new"); + + setup->retry = NO; + setup->server = server; + setup->ctemplate = NULL; + if (ctemplate) + StrAllocCopy(setup->ctemplate, ctemplate); + setup->valid_schemes = valid_schemes; + setup->scheme_specifics = scheme_specifics; + + HTList_addObject(server->setups, (void *) setup); + + return setup; +} + +/* static HTAASetup_delete() + * FREE A HTAASetup STRUCTURE + * ON ENTRY: + * killme is a pointer to the structure to free(). + * + * ON EXIT: + * returns nothing. + */ +static void HTAASetup_delete(HTAASetup * killme) +{ + int scheme; + + if (killme) { + FREE(killme->ctemplate); + if (killme->valid_schemes) { + HTList_delete(killme->valid_schemes); + killme->valid_schemes = NULL; + } + for (scheme = 0; scheme < HTAA_MAX_SCHEMES; scheme++) + if (killme->scheme_specifics[scheme]) + HTAssocList_delete(killme->scheme_specifics[scheme]); + FREE(killme->scheme_specifics); + FREE(killme); + } +} + +/* static HTAASetup_updateSpecifics() + * COPY SCHEME SPECIFIC PARAMETERS + * TO HTAASetup STRUCTURE + * ON ENTRY: + * setup destination setup structure. + * specifics string array containing scheme + * specific parameters for each scheme. + * If NULL, all the scheme specific + * parameters are set to NULL. + * + * ON EXIT: + * returns nothing. + */ +static void HTAASetup_updateSpecifics(HTAASetup * setup, HTAssocList **specifics) +{ + int scheme; + + if (setup) { + if (setup->scheme_specifics) { + for (scheme = 0; scheme < HTAA_MAX_SCHEMES; scheme++) { + if (setup->scheme_specifics[scheme]) + HTAssocList_delete(setup->scheme_specifics[scheme]); + } + FREE(setup->scheme_specifics); + } + setup->scheme_specifics = specifics; + } +} + +/*************************** HTAARealm **********************************/ + +/* static HTAARealm_lookup() + * LOOKUP HTAARealm STRUCTURE BY REALM NAME + * ON ENTRY: + * realm_table a list of realm objects. + * realmname is the name of realm to look for. + * + * ON EXIT: + * returns the realm. NULL, if not found. + */ +static HTAARealm *HTAARealm_lookup(HTList *realm_table, + const char *realmname) +{ + if (realm_table && realmname) { + HTList *cur = realm_table; + HTAARealm *realm; + + while (NULL != (realm = (HTAARealm *) HTList_nextObject(cur))) { + if (0 == strcmp(realm->realmname, realmname)) + return realm; + } + } + return NULL; /* No table, NULL param, or not found */ +} + +/* static HTAARealm_new() + * CREATE A NODE CONTAINING USERNAME AND + * PASSWORD USED FOR THE GIVEN REALM. + * IF REALM ALREADY EXISTS, CHANGE + * USERNAME/PASSWORD. + * ON ENTRY: + * realm_table a list of realms to where to add + * the new one, too. + * realmname is the name of the password domain. + * username and + * password are what you can expect them to be. + * + * ON EXIT: + * returns the created realm. + */ +static HTAARealm *HTAARealm_new(HTList *realm_table, + const char *realmname, + const char *username, + const char *password) +{ + HTAARealm *realm; + + realm = HTAARealm_lookup(realm_table, realmname); + + if (!realm) { + if ((realm = typecalloc(HTAARealm)) == 0) + outofmem(__FILE__, "HTAARealm_new"); + + realm->realmname = NULL; + realm->username = NULL; + realm->password = NULL; + StrAllocCopy(realm->realmname, realmname); + if (realm_table) + HTList_addObject(realm_table, (void *) realm); + } + if (username) + StrAllocCopy(realm->username, username); + if (password) + StrAllocCopy(realm->password, password); + + return realm; +} + +BOOL HTAA_HaveUserinfo(const char *hostname) +{ + int gen_delims = 0; + BOOL result = FALSE; + char *my_info = NULL; + + if (StrAllocCopy(my_info, hostname) != NULL) { + char *at_sign = HTSkipToAt(my_info, &gen_delims); + + free(my_info); + if (at_sign != NULL && gen_delims == 0) + result = TRUE; + } + return result; +} + +/* + * If there is userinfo in the hostname string, update the realm to use that + * information. The command-line "-auth" option will override this. + */ +static void fill_in_userinfo(HTAARealm *realm, const char *hostname) +{ + int gen_delims = 0; + char *my_info = NULL; + char *at_sign = HTSkipToAt(StrAllocCopy(my_info, hostname), &gen_delims); + + if (at_sign != NULL && gen_delims == 0) { + char *colon; + + *at_sign = '\0'; + if ((colon = StrChr(my_info, ':')) != 0) { + *colon++ = '\0'; + } + if (non_empty(my_info)) { + char *msg; + BOOL prior = non_empty(realm->username); + + if (prior && strcmp(realm->username, my_info)) { + msg = 0; + HTSprintf0(&msg, + gettext("username for realm %s changed from %s to %s"), + realm->realmname, + realm->username, + my_info); + HTAlert(msg); + free(msg); + FREE(realm->username); + StrAllocCopy(realm->username, my_info); + } else if (!prior) { + StrAllocCopy(realm->username, my_info); + } + if (non_empty(colon)) { + prior = non_empty(realm->password); + if (prior && strcmp(realm->password, colon)) { + msg = 0; + HTSprintf0(&msg, + gettext("password for realm %s user %s changed"), + realm->realmname, + realm->username); + HTAlert(msg); + free(msg); + FREE(realm->password); + StrAllocCopy(realm->password, colon); + } else if (!prior) { + StrAllocCopy(realm->password, colon); + } + } + } + } + free(my_info); +} + +/***************** Basic and Pubkey Authentication ************************/ + +/* static compose_auth_string() + * + * COMPOSE Basic OR Pubkey AUTHENTICATION STRING; + * PROMPTS FOR USERNAME AND PASSWORD IF NEEDED + * + * ON ENTRY: + * hostname may include user- and password information + * scheme is either HTAA_BASIC or HTAA_PUBKEY. + * setup is the current server setup. + * IsProxy should be TRUE if this is a proxy. + * + * ON EXIT: + * returns a newly composed authorization string, + * (with, of course, a newly generated secret + * key and fresh timestamp, if Pubkey-scheme + * is being used). + * NULL, if something fails. + * NOTE: + * Like throughout the entire AA package, no string or structure + * returned by AA package needs to (or should) be freed. + * + */ +static char *compose_auth_string(const char *hostname, + HTAAScheme scheme, + HTAASetup * setup, + int IsProxy) +{ + char *cleartext = NULL; /* Cleartext presentation */ + char *ciphertext = NULL; /* Encrypted presentation */ + size_t len; + char *msg = NULL; + char *username = NULL; + char *password = NULL; + char *realmname = NULL; + char *theHost = NULL; + char *proxiedHost = NULL; + char *thePort = NULL; + HTAARealm *realm; + const char *i_net_addr = "0.0.0.0"; /* Change... @@@@ */ + const char *timestamp = "42"; /* ... these @@@@ */ + + FREE(compose_auth_stringResult); /* From previous call */ + + if ((scheme != HTAA_BASIC && scheme != HTAA_PUBKEY) || + !(setup && + setup->scheme_specifics && + setup->scheme_specifics[scheme] && + setup->server && + setup->server->realms)) + return NULL; + + realmname = HTAssocList_lookup(setup->scheme_specifics[scheme], "realm"); + if (!realmname) + return NULL; + + realm = HTAARealm_lookup(setup->server->realms, realmname); + setup->retry |= HTAA_HaveUserinfo(hostname); + + if (!(realm && + non_empty(realm->username) && + non_empty(realm->password)) || setup->retry) { + if (!realm) { + CTRACE((tfp, "%s `%s' %s\n", + "compose_auth_string: realm:", realmname, + "not found -- creating")); + realm = HTAARealm_new(setup->server->realms, + realmname, NULL, NULL); + } + fill_in_userinfo(realm, hostname); + /* + * The template should be either the '*' global for everything on the + * server (always true for proxy authorization setups), or a path for + * the start of a protected limb, with no host field, but we'll check + * for a host anyway in case a WWW-Protection-Template header set an + * absolute URL instead of a path. If we do get a host from this, it + * will include the port. - FM + */ + if ((!IsProxy) && using_proxy && setup->ctemplate) { + proxiedHost = HTParse(setup->ctemplate, "", PARSE_HOST); + if (proxiedHost && *proxiedHost != '\0') { + theHost = proxiedHost; + } + } + /* + * If we didn't get a host field from the template, set up the host + * name and port from the setup->server elements. - FM + */ + if (!theHost) + theHost = setup->server->hostname; + if (setup->server->portnumber > 0 && + setup->server->portnumber != 80) { + HTSprintf0(&thePort, ":%d", setup->server->portnumber); + } + + HTSprintf0(&msg, gettext("Username for '%s' at %s '%s%s':"), + realm->realmname, + (IsProxy ? "proxy" : "server"), + (theHost ? theHost : "??"), + NonNull(thePort)); + FREE(proxiedHost); + FREE(thePort); + if (non_empty(realm->username)) { + StrAllocCopy(username, realm->username); + } + if (non_empty(realm->password)) { + StrAllocCopy(password, realm->password); + } + HTPromptUsernameAndPassword(msg, &username, &password, IsProxy); + + FREE(msg); + FREE(realm->username); + FREE(realm->password); + + realm->username = username; + realm->password = password; + + if (!realm->username || !realm->password) { + /* + * Signals to retry. - FM + */ + return NULL; + } else if (*realm->username == '\0') { + /* + * Signals to abort. - FM + */ + StrAllocCopy(compose_auth_stringResult, ""); + return compose_auth_stringResult; + } + } + + len = (strlen(NonNull(realm->username)) + + strlen(NonNull(realm->password)) + 3); + + if (scheme == HTAA_PUBKEY) { +#ifdef PUBKEY + /* Generate new secret key */ + StrAllocCopy(secret_key, HTAA_generateRandomKey()); +#endif /* PUBKEY */ + /* Room for secret key, timestamp and inet address */ + len += strlen(NonNull(secret_key)) + 30; + } else { + FREE(secret_key); + } + + if ((cleartext = typecallocn(char, len)) == 0) + outofmem(__FILE__, "compose_auth_string"); + + if (realm->username) + strcpy(cleartext, realm->username); + else + *cleartext = '\0'; + + strcat(cleartext, ":"); + + if (realm->password) + strcat(cleartext, realm->password); + + if (scheme == HTAA_PUBKEY) { + strcat(cleartext, ":"); + strcat(cleartext, i_net_addr); + strcat(cleartext, ":"); + strcat(cleartext, timestamp); + strcat(cleartext, ":"); + if (secret_key) + strcat(cleartext, secret_key); + + if (!((ciphertext = typecallocn(char, 2 * len)) && + (compose_auth_stringResult = typecallocn(char, 3 * len)))) + outofmem(__FILE__, "compose_auth_string"); + +#ifdef PUBKEY + HTPK_encrypt(cleartext, ciphertext, server->public_key); + HTUU_encode((unsigned char *) ciphertext, strlen(ciphertext), + compose_auth_stringResult); +#endif /* PUBKEY */ + FREE(cleartext); + FREE(ciphertext); + } else { /* scheme == HTAA_BASIC */ + if (!(compose_auth_stringResult = + typecallocn(char, (4 * ((len + 2) / 3)) + 1))) + outofmem(__FILE__, "compose_auth_string"); + + HTUU_encode((unsigned char *) cleartext, strlen(cleartext), + compose_auth_stringResult); + FREE(cleartext); + } + return compose_auth_stringResult; +} + +/* BROWSER static HTAA_selectScheme() + * SELECT THE AUTHENTICATION SCHEME TO USE + * ON ENTRY: + * setup is the server setup structure which can + * be used to make the decision about the + * used scheme. + * + * When new authentication methods are added to library + * this function makes the decision about which one to + * use at a given time. This can be done by inspecting + * environment variables etc. + * + * Currently only searches for the first valid scheme, + * and if nothing found suggests Basic scheme; + * + * ON EXIT: + * returns the authentication scheme to use. + */ +static HTAAScheme HTAA_selectScheme(HTAASetup * setup) +{ + int scheme; + + if (setup && setup->valid_schemes) { + for (scheme = HTAA_BASIC; scheme < HTAA_MAX_SCHEMES; scheme++) { + void *object = (void *) (intptr_t) scheme; + + if (-1 < HTList_indexOf(setup->valid_schemes, object)) + return (HTAAScheme) scheme; + } + } + return HTAA_BASIC; +} + +/* + * Purpose: Free off all module globals. + * Arguments: void + * Return Value: void + * Remarks/Portability/Dependencies/Restrictions: + * To be used at program exit. + * Revision History: + * 06-19-96 created - FM + */ +static void free_HTAAGlobals(void) +{ + HTAAServer *server; + int n, i; + + if (server_table != NULL) { + n = HTList_count(server_table); + for (i = (n - 1); i >= 0; i--) { + if ((server = (HTAAServer *) HTList_objectAt(server_table, + i)) != NULL) { + HTAAServer_delete(server); + server = NULL; + } + } + HTList_delete(server_table); + server_table = NULL; + } + + HTAAForwardAuth_reset(); + FREE(HTAA_composeAuthResult); + FREE(current_hostname); + FREE(current_docname); + FREE(proxy_hostname); + FREE(proxy_docname); + FREE(compose_auth_stringResult); + FREE(secret_key); +} + +/* BROWSER PUBLIC HTAA_composeAuth() + * + * SELECT THE AUTHENTICATION SCHEME AND + * COMPOSE THE ENTIRE AUTHORIZATION HEADER LINE + * IF WE ALREADY KNOW THAT THE HOST REQUIRES AUTHENTICATION + * + * ON ENTRY: + * hostname is the hostname of the server. + * portnumber is the portnumber in which the server runs. + * docname is the pathname of the document (as in URL) + * IsProxy should be TRUE if this is a proxy. + * + * ON EXIT: + * returns NULL, if no authorization seems to be needed, or + * if it is the entire Authorization: line, e.g. + * + * "Authorization: Basic username:password" + * + * As usual, this string is automatically freed. + */ +char *HTAA_composeAuth(const char *hostname, + const int portnumber, + const char *docname, + int IsProxy) +{ + char *auth_string; + BOOL retry; + HTAAScheme scheme; + size_t len; + + /* + * Setup atexit() freeing if not done already. - FM + */ + if (!free_HTAAGlobalsSet) { +#ifdef LY_FIND_LEAKS + atexit(free_HTAAGlobals); +#endif + free_HTAAGlobalsSet = TRUE; + } + + /* + * Make gateway httpds pass authorization field as it was received. (This + * still doesn't really work because Authenticate: headers from remote + * server are not forwarded to client yet so it cannot really know that it + * should send authorization; I will not implement it yet because I feel we + * will soon change radically the way requests are represented to allow + * multithreading on server-side. Life is hard.) + */ + if (HTAAForwardAuth) { + CTRACE((tfp, "HTAA_composeAuth: %s\n", + "Forwarding received authorization")); + StrAllocCopy(HTAA_composeAuthResult, HTAAForwardAuth); + HTAAForwardAuth_reset(); /* Just a precaution */ + return HTAA_composeAuthResult; + } + + FREE(HTAA_composeAuthResult); /* From previous call */ + + if (IsProxy) { + /* + * Proxy Authorization required. - AJL + */ + + CTRACE((tfp, "Composing Proxy Authorization for %s:%d/%s\n", + hostname, portnumber, docname)); + + if (proxy_portnumber != portnumber || + !proxy_hostname || !proxy_docname || + !hostname || !docname || + 0 != strcmp(proxy_hostname, hostname) || + 0 != strcmp(proxy_docname, docname)) { + + retry = NO; + + proxy_portnumber = portnumber; + + if (hostname) + StrAllocCopy(proxy_hostname, hostname); + else + FREE(proxy_hostname); + + if (docname) + StrAllocCopy(proxy_docname, docname); + else + FREE(proxy_docname); + } else { + retry = YES; + } + + if (!proxy_setup || !retry) + proxy_setup = HTAASetup_lookup(hostname, portnumber, + docname, IsProxy); + + if (!proxy_setup) + return NULL; + + switch (scheme = HTAA_selectScheme(proxy_setup)) { + case HTAA_BASIC: + case HTAA_PUBKEY: + auth_string = compose_auth_string(hostname, scheme, proxy_setup, IsProxy); + break; + case HTAA_KERBEROS_V4: + /* OTHER AUTHENTICATION ROUTINES ARE CALLED HERE */ + default: + { + char *msg = NULL; + + HTSprintf0(&msg, "%s `%s'", + gettext("This client doesn't know how to compose proxy authorization information for scheme"), + HTAAScheme_name(scheme)); + HTAlert(msg); + FREE(msg); + auth_string = NULL; + } + } /* switch scheme */ + + proxy_setup->retry = NO; + + if (!auth_string) + /* + * Signal a failure. - FM + */ + return NULL; /* Added by marca. */ + if (*auth_string == '\0') { + /* + * Signal an abort. - FM + */ + StrAllocCopy(HTAA_composeAuthResult, ""); + return (HTAA_composeAuthResult); + } + len = strlen(auth_string) + strlen(HTAAScheme_name(scheme)) + 26; + if ((HTAA_composeAuthResult = typecallocn(char, len)) == 0) + outofmem(__FILE__, "HTAA_composeAuth"); + + strcpy(HTAA_composeAuthResult, "Proxy-Authorization: "); + + } else { + /* + * Normal WWW authorization. + */ + CTRACE((tfp, "Composing Authorization for %s:%d/%s\n", + hostname, portnumber, docname)); + + if (current_portnumber != portnumber || + !current_hostname || !current_docname || + !hostname || !docname || + 0 != strcmp(current_hostname, hostname) || + 0 != strcmp(current_docname, docname)) { + + retry = NO; + + current_portnumber = portnumber; + + if (hostname) + StrAllocCopy(current_hostname, hostname); + else + FREE(current_hostname); + + if (docname) + StrAllocCopy(current_docname, docname); + else + FREE(current_docname); + } else { + retry = YES; + } + + if (!current_setup || !retry) + current_setup = HTAASetup_lookup(hostname, portnumber, + docname, IsProxy); + + if (!current_setup) + return NULL; + + switch (scheme = HTAA_selectScheme(current_setup)) { + case HTAA_BASIC: + case HTAA_PUBKEY: + auth_string = compose_auth_string(hostname, scheme, current_setup, IsProxy); + break; + case HTAA_KERBEROS_V4: + /* OTHER AUTHENTICATION ROUTINES ARE CALLED HERE */ + default: + { + char *msg = 0; + + HTSprintf0(&msg, "%s `%s'", + gettext("This client doesn't know how to compose authorization information for scheme"), + HTAAScheme_name(scheme)); + HTAlert(msg); + FREE(msg); + auth_string = NULL; + } + } /* switch scheme */ + + current_setup->retry = NO; + + if (!auth_string) + /* + * Signal a failure. - FM + */ + return NULL; /* Added by marca. */ + if (*auth_string == '\0') { + /* + * Signal an abort. - FM + */ + StrAllocCopy(HTAA_composeAuthResult, ""); + return (HTAA_composeAuthResult); + } + + len = strlen(auth_string) + strlen(HTAAScheme_name(scheme)) + 20; + if ((HTAA_composeAuthResult = typecallocn(char, len)) == 0) + outofmem(__FILE__, "HTAA_composeAuth"); + + strcpy(HTAA_composeAuthResult, "Authorization: "); + } + + strcat(HTAA_composeAuthResult, HTAAScheme_name(scheme)); + strcat(HTAA_composeAuthResult, " "); + strcat(HTAA_composeAuthResult, auth_string); + return HTAA_composeAuthResult; +} + +/* BROWSER PUBLIC HTAA_shouldRetryWithAuth() + * + * DETERMINES IF WE SHOULD RETRY THE SERVER + * WITH AUTHORIZATION + * (OR IF ALREADY RETRIED, WITH A DIFFERENT + * USERNAME AND/OR PASSWORD (IF MISSPELLED)) + * ON ENTRY: + * start_of_headers is the first block already read from socket, + * but status line skipped; i.e., points to the + * start of the header section. + * length is the remaining length of the first block. + * soc is the socket to read the rest of server reply. + * IsProxy should be TRUE if this is a proxy. + * + * This function should only be called when + * server has replied with a 401 (Unauthorized) + * status code. + * ON EXIT: + * returns YES, if connection should be retried. + * The node containing all the necessary + * information is + * * either constructed if it does not exist + * * or password is reset to NULL to indicate + * that username and password should be + * reprompted when composing Authorization: + * field (in function HTAA_composeAuth()). + * NO, otherwise. + */ +BOOL HTAA_shouldRetryWithAuth(char *start_of_headers, + size_t length, + int soc, + int IsProxy) +{ + HTAAScheme scheme; + char *line = NULL; + int num_schemes = 0; + HTList *valid_schemes = HTList_new(); + HTAssocList **scheme_specifics = NULL; + char *ctemplate = NULL; + char *temp = NULL; + BOOL result = NO; + + /* + * Setup atexit() freeing if not done already. - FM + */ + if (!free_HTAAGlobalsSet) { +#ifdef LY_FIND_LEAKS + atexit(free_HTAAGlobals); +#endif + free_HTAAGlobalsSet = TRUE; + } + + /* + * Read server reply header lines + */ + CTRACE((tfp, "Server reply header lines:\n")); + + HTAA_setupReader(start_of_headers, length, soc); + while (NULL != (line = HTAA_getUnfoldedLine()) && *line != '\0') { + CTRACE((tfp, "%s\n", line)); + + if (StrChr(line, ':')) { /* Valid header line */ + + char *p = line; + char *fieldname = HTNextField(&p); + char *arg1 = HTNextField(&p); + char *args = p; + + if ((IsProxy && + 0 == strcasecomp(fieldname, "Proxy-Authenticate:")) || + (!IsProxy && + 0 == strcasecomp(fieldname, "WWW-Authenticate:"))) { + if (isEmpty(arg1) || isEmpty(args)) { + HTSprintf0(&temp, gettext("Invalid header '%s%s%s%s%s'"), line, + (non_empty(arg1) ? " " : ""), + NonNull(arg1), + (non_empty(args) ? " " : ""), + NonNull(args)); + HTAlert(temp); + FREE(temp); + } else if (HTAA_UNKNOWN != (scheme = HTAAScheme_enum(arg1))) { + HTList_addObject(valid_schemes, (void *) scheme); + if (!scheme_specifics) { + int i; + + scheme_specifics = + typecallocn(HTAssocList *, HTAA_MAX_SCHEMES); + + if (!scheme_specifics) + outofmem(__FILE__, "HTAA_shouldRetryWithAuth"); + + for (i = 0; i < HTAA_MAX_SCHEMES; i++) + scheme_specifics[i] = NULL; + } + scheme_specifics[scheme] = HTAA_parseArgList(args); + num_schemes++; + } else { + CTRACE((tfp, "Unknown scheme `%s' %s\n", + NONNULL(arg1), + (IsProxy ? + "in Proxy-Authenticate: field" : + "in WWW-Authenticate: field"))); + } + } + + else if (!IsProxy && + 0 == strcasecomp(fieldname, "WWW-Protection-Template:")) { + CTRACE((tfp, "Protection template set to `%s'\n", arg1)); + StrAllocCopy(ctemplate, arg1); + } + + } else { + CTRACE((tfp, "Invalid header line `%s' ignored\n", line)); + } + + FREE(line); + } /* while header lines remain */ + FREE(line); + + /* + * So should we retry with authorization? + */ + if (IsProxy) { + if (num_schemes == 0) { + /* + * No proxy authorization valid + */ + proxy_setup = NULL; + result = NO; + } + /* + * Doing it for proxy. -AJL + */ + else if (proxy_setup && proxy_setup->server) { + /* + * We have already tried with proxy authorization. Either we don't + * have access or username or password was misspelled. + * + * Update scheme-specific parameters (in case they have expired by + * chance). + */ + HTAASetup_updateSpecifics(proxy_setup, scheme_specifics); + + if (NO == HTConfirm(AUTH_FAILED_PROMPT)) { + proxy_setup = NULL; + result = NO; + } else { + /* + * Re-ask username+password (if misspelled). + */ + HTList_delete(valid_schemes); + proxy_setup->retry = YES; + result = YES; + } + } else { + /* + * proxy_setup == NULL, i.e., we have a first connection to a + * protected server or the server serves a wider set of documents + * than we expected so far. + */ + HTAAServer *server = HTAAServer_lookup(proxy_hostname, + proxy_portnumber, + IsProxy); + + if (!server) { + server = HTAAServer_new(proxy_hostname, + proxy_portnumber, + IsProxy); + } + if (!ctemplate) /* Proxy matches everything -AJL */ + StrAllocCopy(ctemplate, "*"); + proxy_setup = HTAASetup_new(server, + ctemplate, + valid_schemes, + scheme_specifics); + FREE(ctemplate); + + HTAlert(gettext("Proxy authorization required -- retrying")); + result = YES; + } + } + /* + * Normal WWW authorization. + */ + else if (num_schemes == 0) { + /* + * No authorization valid. + */ + current_setup = NULL; + result = NO; + } else if (current_setup && current_setup->server) { + /* + * So we have already tried with WWW authorization. Either we don't + * have access or username or password was misspelled. + * + * Update scheme-specific parameters (in case they have expired by + * chance). + */ + HTAASetup_updateSpecifics(current_setup, scheme_specifics); + + if (NO == HTConfirm(AUTH_FAILED_PROMPT)) { + current_setup = NULL; + result = NO; + } else { + /* + * Re-ask username+password (if misspelled). + */ + current_setup->retry = YES; + result = YES; + } + } else { + /* + * current_setup == NULL, i.e., we have a first connection to a + * protected server or the server serves a wider set of documents than + * we expected so far. + */ + HTAAServer *server = HTAAServer_lookup(current_hostname, + current_portnumber, + IsProxy); + + if (!server) { + server = HTAAServer_new(current_hostname, + current_portnumber, + IsProxy); + } + if (!ctemplate) + ctemplate = HTAA_makeProtectionTemplate(current_docname); + current_setup = HTAASetup_new(server, + ctemplate, + valid_schemes, + scheme_specifics); + FREE(ctemplate); + + HTAlert(gettext("Access without authorization denied -- retrying")); + result = YES; + } + + if (result == NO) { + HTList_delete(valid_schemes); + } + return result; +} + +/* + * This function clears all authorization information by + * invoking the free_HTAAGlobals() function, which normally + * is invoked at exit. It allows a browser command to do + * this at any time, for example, if the user is leaving + * the terminal for a period of time, but does not want + * to end the current session. - FM + */ +void HTClearHTTPAuthInfo(void) +{ + /* + * Need code to check cached documents against the protection templates, + * and do something to ensure that any protected documents no longer can be + * accessed without a new retrieval. - FM + */ + + /* + * Now free all of the authorization info, and reset the + * free_HTAAGlobalsSet flag. - FM + */ + free_HTAAGlobals(); + free_HTAAGlobalsSet = FALSE; +} diff --git a/WWW/Library/Implementation/HTAABrow.h b/WWW/Library/Implementation/HTAABrow.h new file mode 100644 index 0000000..f151deb --- /dev/null +++ b/WWW/Library/Implementation/HTAABrow.h @@ -0,0 +1,142 @@ +/* + * $LynxId: HTAABrow.h,v 1.17 2016/11/24 23:32:22 tom Exp $ + * + * BROWSER SIDE ACCESS AUTHORIZATION MODULE + + This module is the browser side interface to Access Authorization (AA) package. It + contains code only for browser. + + Important to know about memory allocation: + + Routines in this module use dynamic allocation, but free automatically all the memory + reserved by them. + + Therefore the caller never has to (and never should) free() any object returned by + these functions. + + Therefore also all the strings returned by this package are only valid until the next + call to the same function is made. This approach is selected, because of the nature of + access authorization: no string returned by the package needs to be valid longer than + until the next call. + + This also makes it easy to plug the AA package in: you don't have to ponder whether to + free()something here or is it done somewhere else (because it is always done somewhere + else). + + The strings that the package needs to store are copied so the original strings given as + parameters to AA functions may be freed or modified with no side effects. + + Also note:The AA package does not free() anything else than what it has itself + allocated. + + */ + +#ifndef HTAABROW_H +#define HTAABROW_H + +#include /* Common parts of AA */ + +#ifdef __cplusplus +extern "C" { +#endif +/* + Routines for Browser Side Recording of AA Info + + Most of the browser-side AA is done by the following two functions (which are called + from file HTTP.c so the browsers using libwww only need to be linked with the new + library and not be changed at all): + + HTAA_composeAuth() composes the Authorization: line contents, if the AA package + thinks that the given document is protected. Otherwise this function returns NULL. + This function also calls the functions HTPrompt(),HTPromptPassword() and HTConfirm() + to get the username, password and some confirmation from the user. + + HTAA_shouldRetryWithAuth() determines whether to retry the request with AA or with a + new AA (in case username or password was misspelled). + + */ +/* PUBLIC HTAA_composeAuth() + * + * COMPOSE THE ENTIRE AUTHORIZATION HEADER LINE IF WE + * ALREADY KNOW, THAT THE HOST MIGHT REQUIRE AUTHORIZATION + * + * ON ENTRY: + * hostname is the hostname of the server. + * portnumber is the portnumber in which the server runs. + * docname is the pathname of the document (as in URL) + * + * ON EXIT: + * returns NULL, if no authorization seems to be needed, or + * if it is the entire Authorization: line, e.g. + * + * "Authorization: basic username:password" + * + * As usual, this string is automatically freed. + */ + extern char *HTAA_composeAuth(const char *hostname, + const int portnumber, + const char *docname, + int IsProxy); + +/* BROWSER PUBLIC HTAA_shouldRetryWithAuth() + * + * DETERMINES IF WE SHOULD RETRY THE SERVER + * WITH AUTHORIZATION + * (OR IF ALREADY RETRIED, WITH A DIFFERENT + * USERNAME AND/OR PASSWORD (IF MISSPELLED)) + * ON ENTRY: + * start_of_headers is the first block already read from socket, + * but status line skipped; i.e., points to the + * start of the header section. + * length is the remaining length of the first block. + * soc is the socket to read the rest of server reply. + * + * This function should only be called when + * server has replied with a 401 (Unauthorized) + * status code. + * ON EXIT: + * returns YES, if connection should be retried. + * The node containing all the necessary + * information is + * * either constructed if it does not exist + * * or password is reset to NULL to indicate + * that username and password should be + * reprompted when composing Authorization: + * field (in function HTAA_composeAuth()). + * NO, otherwise. + */ + extern BOOL HTAA_shouldRetryWithAuth(char *start_of_headers, + size_t length, + int soc, + int IsProxy); + +/* + * Function to allow clearing of all Authorization info + * via a browser command. - FM + */ + extern void HTClearHTTPAuthInfo(void); + +/* + * Check if a hostname-string contains user information. + */ + extern BOOL HTAA_HaveUserinfo(const char *hostname); + +/* + +Enabling Gateway httpds to Forward Authorization + + These functions should only be called from daemon code, and HTAAForwardAuth_reset() + must be called before the next request is handled to make sure that authorization + string isn't cached in daemon so that other people can access private files using + somebody else's previous authorization information. + + */ + + extern void HTAAForwardAuth_set(const char *scheme_name, + const char *scheme_specifics); + extern void HTAAForwardAuth_reset(void); + +#ifdef __cplusplus +} +#endif +#endif /* NOT HTAABROW_H */ diff --git a/WWW/Library/Implementation/HTAAProt.c b/WWW/Library/Implementation/HTAAProt.c new file mode 100644 index 0000000..d5117f1 --- /dev/null +++ b/WWW/Library/Implementation/HTAAProt.c @@ -0,0 +1,738 @@ +/* + * $LynxId: HTAAProt.c,v 1.34 2016/11/24 15:29:50 tom Exp $ + * + * MODULE HTAAProt.c + * PROTECTION FILE PARSING MODULE + * + * AUTHORS: + * AL Ari Luotonen luotonen@dxcern.cern.ch + * MD Mark Donszelmann duns@vxdeop.cern.ch + * + * HISTORY: + * 20 Oct 93 AL Now finds uid/gid for nobody/nogroup by name + * (doesn't use default 65534 right away). + * Also understands negative uids/gids. + * 14 Nov 93 MD Added VMS compatibility + * + * BUGS: + * + * + */ + +#include + +#ifndef VMS +#ifndef NOUSERS +#include /* Unix password file routine: getpwnam() */ +#include /* Unix group file routine: getgrnam() */ +#endif /* NOUSERS */ +#endif /* not VMS */ + +#include +#include /* Lexical analysor */ +#include /* Implemented here */ + +#include +#include + +#define NOBODY 65534 /* -2 in 16-bit environment */ +#define NONESUCH 65533 /* -3 in 16-bit environment */ + +/* + * Protection setup caching + */ +typedef struct { + char *prot_filename; + HTAAProt *prot; +} HTAAProtCache; + +static HTList *prot_cache = NULL; /* Protection setup cache. */ +static HTAAProt *default_prot = NULL; /* Default protection. */ +static HTAAProt *current_prot = NULL; /* Current protection mode */ + + /* which is set up by callbacks */ + /* from the rule system when */ + /* a "protect" rule is matched. */ + +#ifndef NOUSERS +/* static isNumber() + * DOES A CHARACTER STRING REPRESENT A NUMBER + */ +static BOOL isNumber(const char *s) +{ + const char *cur = s; + + if (isEmpty(s)) + return NO; + + if (*cur == '-') + cur++; /* Allow initial minus sign in a number */ + + while (*cur) { + if (*cur < '0' || *cur > '9') + return NO; + cur++; + } + return YES; +} + +/* PUBLIC HTAA_getUid() + * GET THE USER ID TO CHANGE THE PROCESS UID TO + * ON ENTRY: + * No arguments. + * + * ON EXIT: + * returns the uid number to give to setuid() system call. + * Default is 65534 (nobody). + */ +int HTAA_getUid(void) +{ + int uid; + + if (current_prot && current_prot->uid_name) { + if (isNumber(current_prot->uid_name)) { + uid = atoi(current_prot->uid_name); + if ((*HTAA_UidToName(uid)) != '\0') { + return uid; + } + } else { /* User name (not a number) */ + if ((uid = HTAA_NameToUid(current_prot->uid_name)) != NONESUCH) { + return uid; + } + } + } + /* + * Ok, then let's get uid for nobody. + */ + if ((uid = HTAA_NameToUid("nobody")) != NONESUCH) { + return uid; + } + /* + * Ok, then use default. + */ + return NOBODY; /* nobody */ +} + +/* PUBLIC HTAA_getGid() + * GET THE GROUP ID TO CHANGE THE PROCESS GID TO + * ON ENTRY: + * No arguments. + * + * ON EXIT: + * returns the uid number to give to setgid() system call. + * Default is 65534 (nogroup). + */ +int HTAA_getGid(void) +{ + int gid; + + if (current_prot && current_prot->gid_name) { + if (isNumber(current_prot->gid_name)) { + gid = atoi(current_prot->gid_name); + if (*HTAA_GidToName(gid) != '\0') { + return gid; + } + } else { /* Group name (not number) */ + if ((gid = HTAA_NameToGid(current_prot->gid_name)) != NONESUCH) { + return gid; + } + } + } + /* + * Ok, then let's get gid for nogroup. + */ + if ((gid = HTAA_NameToGid("nogroup")) != NONESUCH) { + return gid; + } + /* + * Ok, then use default. + */ + return NOBODY; /* nogroup */ +} +#endif /* !NOUSERS */ + +/* static HTAA_setIds() + * SET UID AND GID (AS NAMES OR NUMBERS) + * TO HTAAProt STRUCTURE + * ON ENTRY: + * prot destination. + * ids is a string like "james.www" or "1422.69" etc. + * giving uid and gid. + * + * ON EXIT: + * returns nothing. + */ +static void HTAA_setIds(HTAAProt *prot, const char *ids) +{ + if (ids) { + char *local_copy = NULL; + char *point; + + StrAllocCopy(local_copy, ids); + point = StrChr(local_copy, '.'); + if (point) { + *(point++) = (char) 0; + StrAllocCopy(prot->gid_name, point); + } else { + StrAllocCopy(prot->gid_name, "nogroup"); + } + StrAllocCopy(prot->uid_name, local_copy); + FREE(local_copy); + } else { + StrAllocCopy(prot->uid_name, "nobody"); + StrAllocCopy(prot->gid_name, "nogroup"); + } +} + +/* static HTAA_parseProtFile() + * PARSE A PROTECTION SETUP FILE AND + * PUT THE RESULT IN A HTAAProt STRUCTURE + * ON ENTRY: + * prot destination structure. + * fp open protection file. + * + * ON EXIT: + * returns nothing. + */ +static void HTAA_parseProtFile(HTAAProt *prot, FILE *fp) +{ + if (prot && fp) { + LexItem lex_item; + char *fieldname = NULL; + + while (LEX_EOF != (lex_item = lex(fp))) { + + while (lex_item == LEX_REC_SEP) /* Ignore empty lines */ + lex_item = lex(fp); + + if (lex_item == LEX_EOF) /* End of file */ + break; + + if (lex_item == LEX_ALPH_STR) { /* Valid setup record */ + + StrAllocCopy(fieldname, HTlex_buffer); + + if (LEX_FIELD_SEP != (lex_item = lex(fp))) + unlex(lex_item); /* If someone wants to use colon */ + /* after field name it's ok, but */ + /* not required. Here we read it. */ + + if (0 == strncasecomp(fieldname, "Auth", 4)) { + lex_item = lex(fp); + while (lex_item == LEX_ALPH_STR) { + HTAAScheme scheme = HTAAScheme_enum(HTlex_buffer); + + if (scheme != HTAA_UNKNOWN) { + if (!prot->valid_schemes) + prot->valid_schemes = HTList_new(); + HTList_addObject(prot->valid_schemes, (void *) scheme); + CTRACE((tfp, "%s %s `%s'\n", + "HTAA_parseProtFile: valid", + "authentication scheme:", + HTAAScheme_name(scheme))); + } else { + CTRACE((tfp, "%s %s `%s'\n", + "HTAA_parseProtFile: unknown", + "authentication scheme:", + HTlex_buffer)); + } + + if (LEX_ITEM_SEP != (lex_item = lex(fp))) + break; + /* + * Here lex_item == LEX_ITEM_SEP; after item separator + * it is ok to have one or more newlines (LEX_REC_SEP) + * and they are ignored (continuation line). + */ + do { + lex_item = lex(fp); + } while (lex_item == LEX_REC_SEP); + } /* while items in list */ + } + /* if "Authenticate" */ + else if (0 == strncasecomp(fieldname, "mask", 4)) { + prot->mask_group = HTAA_parseGroupDef(fp); + lex_item = LEX_REC_SEP; /*groupdef parser read this already */ + if (TRACE) { + if (prot->mask_group) { + fprintf(tfp, + "HTAA_parseProtFile: Mask group:\n"); + HTAA_printGroupDef(prot->mask_group); + } else + fprintf(tfp, + "HTAA_parseProtFile: Mask group syntax error\n"); + } + } + /* if "Mask" */ + else { /* Just a name-value pair, put it to assoclist */ + + if (LEX_ALPH_STR == (lex_item = lex(fp))) { + if (!prot->values) + prot->values = HTAssocList_new(); + HTAssocList_add(prot->values, fieldname, HTlex_buffer); + lex_item = lex(fp); /* Read record separator */ + CTRACE((tfp, "%s `%s' bound to value `%s'\n", + "HTAA_parseProtFile: Name", + fieldname, HTlex_buffer)); + } + } /* else name-value pair */ + + } + /* if valid field */ + if (lex_item != LEX_EOF && lex_item != LEX_REC_SEP) { + CTRACE((tfp, "%s %s %d (that line ignored)\n", + "HTAA_parseProtFile: Syntax error", + "in protection setup file at line", + HTlex_line)); + do { + lex_item = lex(fp); + } while (lex_item != LEX_EOF && lex_item != LEX_REC_SEP); + } /* if syntax error */ + } /* while not end-of-file */ + FREE(fieldname); + } /* if valid parameters */ +} + +/* static HTAAProt_new() + * ALLOCATE A NEW HTAAProt STRUCTURE AND + * INITIALIZE IT FROM PROTECTION SETUP FILE + * ON ENTRY: + * cur_docname current filename after rule translations. + * prot_filename protection setup file name. + * If NULL, not an error. + * ids Uid and gid names or numbers, + * examples: + * james ( <=> james.nogroup) + * .www ( <=> nobody.www) + * james.www + * james.69 + * 1422.69 + * 1422.www + * + * May be NULL, defaults to nobody.nogroup. + * Should be NULL, if prot_file is NULL. + * + * ON EXIT: + * returns returns a new and initialized protection + * setup structure. + * If setup file is already read in (found + * in cache), only sets uid_name and gid + * fields, and returns that. + */ +static HTAAProt *HTAAProt_new(const char *cur_docname, + const char *prot_filename, + const char *ids) +{ + HTList *cur = prot_cache; + HTAAProtCache *cache_item = NULL; + HTAAProt *prot; + FILE *fp; + + if (!prot_cache) + prot_cache = HTList_new(); + + while (NULL != (cache_item = (HTAAProtCache *) HTList_nextObject(cur))) { + if (!strcmp(cache_item->prot_filename, prot_filename)) + break; + } + if (cache_item) { + prot = cache_item->prot; + CTRACE((tfp, "%s `%s' already in cache\n", + "HTAAProt_new: Protection file", prot_filename)); + } else { + CTRACE((tfp, "HTAAProt_new: Loading protection file `%s'\n", + prot_filename)); + + if ((prot = typecalloc(HTAAProt)) == 0) + outofmem(__FILE__, "HTAAProt_new"); + + prot->ctemplate = NULL; + prot->filename = NULL; + prot->uid_name = NULL; + prot->gid_name = NULL; + prot->valid_schemes = HTList_new(); + prot->mask_group = NULL; /* Masking disabled by defaults */ + prot->values = HTAssocList_new(); + + if (prot_filename && NULL != (fp = fopen(prot_filename, TXT_R))) { + HTAA_parseProtFile(prot, fp); + fclose(fp); + if ((cache_item = typecalloc(HTAAProtCache)) == 0) + outofmem(__FILE__, "HTAAProt_new"); + + cache_item->prot = prot; + cache_item->prot_filename = NULL; + StrAllocCopy(cache_item->prot_filename, prot_filename); + HTList_addObject(prot_cache, (void *) cache_item); + } else { + CTRACE((tfp, "HTAAProt_new: %s `%s'\n", + "Unable to open protection setup file", + NONNULL(prot_filename))); + } + } + + if (cur_docname) + StrAllocCopy(prot->filename, cur_docname); + HTAA_setIds(prot, ids); + + return prot; +} + +/* PUBLIC HTAA_setDefaultProtection() + * SET THE DEFAULT PROTECTION MODE + * (called by rule system when a + * "defprot" rule is matched) + * ON ENTRY: + * cur_docname is the current result of rule translations. + * prot_filename is the protection setup file (second argument + * for "defprot" rule, optional) + * ids contains user and group names separated by + * a dot, corresponding to the uid + * gid under which the server should run, + * default is "nobody.nogroup" (third argument + * for "defprot" rule, optional; can be given + * only if protection setup file is also given). + * + * ON EXIT: + * returns nothing. + * Sets the module-wide variable default_prot. + */ +void HTAA_setDefaultProtection(const char *cur_docname, + const char *prot_filename, + const char *ids) +{ + default_prot = NULL; /* Not free()'d because this is in cache */ + + if (prot_filename) { + default_prot = HTAAProt_new(cur_docname, prot_filename, ids); + } else { + CTRACE((tfp, "%s %s\n", + "HTAA_setDefaultProtection: ERROR: Protection file", + "not specified (obligatory for DefProt rule)!!\n")); + } +} + +/* PUBLIC HTAA_setCurrentProtection() + * SET THE CURRENT PROTECTION MODE + * (called by rule system when a + * "protect" rule is matched) + * ON ENTRY: + * cur_docname is the current result of rule translations. + * prot_filename is the protection setup file (second argument + * for "protect" rule, optional) + * ids contains user and group names separated by + * a dot, corresponding to the uid + * gid under which the server should run, + * default is "nobody.nogroup" (third argument + * for "protect" rule, optional; can be given + * only if protection setup file is also given). + * + * ON EXIT: + * returns nothing. + * Sets the module-wide variable current_prot. + */ +void HTAA_setCurrentProtection(const char *cur_docname, + const char *prot_filename, + const char *ids) +{ + current_prot = NULL; /* Not free()'d because this is in cache */ + + if (prot_filename) { + current_prot = HTAAProt_new(cur_docname, prot_filename, ids); + } else { + if (default_prot) { + current_prot = default_prot; + HTAA_setIds(current_prot, ids); + CTRACE((tfp, "%s %s %s\n", + "HTAA_setCurrentProtection: Protection file", + "not specified for Protect rule", + "-- using default protection")); + } else { + CTRACE((tfp, "%s %s %s\n", + "HTAA_setCurrentProtection: ERROR: Protection", + "file not specified for Protect rule, and", + "default protection is not set!!")); + } + } +} + +/* PUBLIC HTAA_getCurrentProtection() + * GET CURRENT PROTECTION SETUP STRUCTURE + * (this is set up by callbacks made from + * the rule system when matching "protect" + * (and "defprot") rules) + * ON ENTRY: + * HTTranslate() must have been called before calling + * this function. + * + * ON EXIT: + * returns a HTAAProt structure representing the + * protection setup of the HTTranslate()'d file. + * This must not be free()'d. + */ +HTAAProt *HTAA_getCurrentProtection(void) +{ + return current_prot; +} + +/* PUBLIC HTAA_getDefaultProtection() + * GET DEFAULT PROTECTION SETUP STRUCTURE + * AND SET IT TO CURRENT PROTECTION + * (this is set up by callbacks made from + * the rule system when matching "defprot" + * rules) + * ON ENTRY: + * HTTranslate() must have been called before calling + * this function. + * + * ON EXIT: + * returns a HTAAProt structure representing the + * default protection setup of the HTTranslate()'d + * file (if HTAA_getCurrentProtection() returned + * NULL, i.e., if there is no "protect" rule + * but ACL exists, and we need to know default + * protection settings). + * This must not be free()'d. + * IMPORTANT: + * As a side-effect this tells the protection system that + * the file is in fact protected and sets the current + * protection mode to default. + */ +HTAAProt *HTAA_getDefaultProtection(void) +{ + if (!current_prot) { + current_prot = default_prot; + default_prot = NULL; + } + return current_prot; +} + +/* SERVER INTERNAL HTAA_clearProtections() + * CLEAR DOCUMENT PROTECTION MODE + * (ALSO DEFAULT PROTECTION) + * (called by the rule system) + * ON ENTRY: + * No arguments. + * + * ON EXIT: + * returns nothing. + * Frees the memory used by protection information. + */ +void HTAA_clearProtections(void) +{ + current_prot = NULL; /* These are not freed because */ + default_prot = NULL; /* they are actually in cache. */ +} + +typedef struct { + char *name; + int user; +} USER_DATA; + +#ifndef NOUSERS +static HTList *known_grp = NULL; +static HTList *known_pwd = NULL; +static BOOL uidgid_cache_inited = NO; +#endif + +#ifdef LY_FIND_LEAKS +static void clear_uidgid_cache(void) +{ +#ifndef NOUSERS + USER_DATA *data; + + if (known_grp) { + while ((data = HTList_removeLastObject(known_grp)) != NULL) { + FREE(data->name); + FREE(data); + } + FREE(known_grp); + } + if (known_pwd) { + while ((data = HTList_removeLastObject(known_pwd)) != NULL) { + FREE(data->name); + FREE(data); + } + FREE(known_pwd); + } +#endif +} +#endif /* LY_FIND_LEAKS */ + +#ifndef NOUSERS +static void save_gid_info(const char *name, int user) +{ + USER_DATA *data = typecalloc(USER_DATA); + + if (!data) + return; + if (!known_grp) { + known_grp = HTList_new(); + if (!uidgid_cache_inited) { +#ifdef LY_FIND_LEAKS + atexit(clear_uidgid_cache); +#endif + uidgid_cache_inited = YES; + } + } + StrAllocCopy(data->name, name); + data->user = user; + HTList_addObject(known_grp, data); +} +#endif /* NOUSERS */ + +#ifndef NOUSERS +static void save_uid_info(const char *name, int user) +{ + USER_DATA *data = typecalloc(USER_DATA); + + if (!data) + return; + if (!known_pwd) { + known_pwd = HTList_new(); + if (!uidgid_cache_inited) { +#ifdef LY_FIND_LEAKS + atexit(clear_uidgid_cache); +#endif + uidgid_cache_inited = YES; + } + } + StrAllocCopy(data->name, name); + data->user = user; + HTList_addObject(known_pwd, data); +} +#endif /* !NOUSERS */ + +/* PUBLIC HTAA_UidToName + * GET THE USER NAME + * ON ENTRY: + * The user-id + * + * ON EXIT: + * returns the user name, or an empty string if not found. + */ +const char *HTAA_UidToName(int uid GCC_UNUSED) +{ +#ifndef NOUSERS + struct passwd *pw; + HTList *me = known_pwd; + + while (HTList_nextObject(me)) { + USER_DATA *data = (USER_DATA *) (me->object); + + if (uid == data->user) + return data->name; + } + + if ((pw = getpwuid((uid_t) uid)) != 0 + && pw->pw_name != 0) { + CTRACE((tfp, "%s(%d) returned (%s:%d:...)\n", + "HTAA_UidToName: getpwuid", + uid, + pw->pw_name, (int) pw->pw_uid)); + save_uid_info(pw->pw_name, (int) pw->pw_uid); + return pw->pw_name; + } +#endif + return ""; +} + +/* PUBLIC HTAA_NameToUid + * GET THE USER ID + * ON ENTRY: + * The user-name + * + * ON EXIT: + * returns the user id, or NONESUCH if not found. + */ +int HTAA_NameToUid(const char *name GCC_UNUSED) +{ +#ifndef NOUSERS + struct passwd *pw; + HTList *me = known_pwd; + + while (HTList_nextObject(me)) { + USER_DATA *data = (USER_DATA *) (me->object); + + if (!strcmp(name, data->name)) + return data->user; + } + + if ((pw = getpwnam(name)) != 0) { + CTRACE((tfp, "%s(%s) returned (%s:%d:...)\n", + "HTAA_NameToUid: getpwnam", + name, + pw->pw_name, (int) pw->pw_uid)); + save_uid_info(pw->pw_name, (int) pw->pw_uid); + return (int) pw->pw_uid; + } +#endif + return NONESUCH; +} + +/* PUBLIC HTAA_GidToName + * GET THE GROUP NAME + * ON ENTRY: + * The group-id + * + * ON EXIT: + * returns the group name, or an empty string if not found. + */ +const char *HTAA_GidToName(int gid GCC_UNUSED) +{ +#ifndef NOUSERS + struct group *gr; + HTList *me = known_grp; + + while (HTList_nextObject(me)) { + USER_DATA *data = (USER_DATA *) (me->object); + + if (gid == data->user) + return data->name; + } + + if ((gr = getgrgid((gid_t) gid)) != 0 + && gr->gr_name != 0) { + CTRACE((tfp, "%s(%d) returned (%s:%d:...)\n", + "HTAA_GidToName: getgrgid", + gid, + gr->gr_name, (int) gr->gr_gid)); + save_gid_info(gr->gr_name, (int) gr->gr_gid); + return gr->gr_name; + } +#endif + return ""; +} + +/* PUBLIC HTAA_NameToGid + * GET THE GROUP ID + * ON ENTRY: + * The group-name + * + * ON EXIT: + * returns the group id, or NONESUCH if not found. + */ +int HTAA_NameToGid(const char *name GCC_UNUSED) +{ +#ifndef NOUSERS + struct group *gr; + HTList *me = known_grp; + + while (HTList_nextObject(me)) { + USER_DATA *data = (USER_DATA *) (me->object); + + if (!strcmp(name, data->name)) + return data->user; + } + + if ((gr = getgrnam(name)) != 0) { + CTRACE((tfp, "%s(%s) returned (%s:%d:...)\n", + "HTAA_NameToGid: getgrnam", + name, + gr->gr_name, (int) gr->gr_gid)); + save_gid_info(gr->gr_name, (int) gr->gr_gid); + return (int) gr->gr_gid; + } +#endif + return NONESUCH; +} diff --git a/WWW/Library/Implementation/HTAAProt.h b/WWW/Library/Implementation/HTAAProt.h new file mode 100644 index 0000000..22e3d92 --- /dev/null +++ b/WWW/Library/Implementation/HTAAProt.h @@ -0,0 +1,226 @@ +/* PROTECTION SETUP FILE + + */ + +#ifndef HTAAPROT_H +#define HTAAPROT_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif +/* + +Server's Representation of Document (Tree) Protections + + */ typedef struct { + char *ctemplate; /* Template for this protection */ + char *filename; /* Current document file */ + char *uid_name; /* Effective uid (name of it) */ + char *gid_name; /* Effective gid (name of it) */ + GroupDef *mask_group; /* Allowed users and IP addresses */ + HTList *valid_schemes; /* Valid authentication schemes */ + HTAssocList *values; /* Association list for scheme specific */ + /* parameters. */ + } HTAAProt; + +/* + +Callbacks for rule system + + The following three functioncs are called by the rule system: + + HTAA_clearProtections() when starting to translate a filename + + HTAA_setDefaultProtection() when "defprot" rule is matched + + HTAA_setCurrentProtection() when "protect" rule is matched + + Protection setup files are cached by these functions. + + */ + +/* PUBLIC HTAA_setDefaultProtection() + * SET THE DEFAULT PROTECTION MODE + * (called by rule system when a + * "defprot" rule is matched) + * ON ENTRY: + * cur_docname is the current result of rule translations. + * prot_filename is the protection setup file (second argument + * for "defprot" rule, optional) + * eff_ids contains user and group names separated by + * a dot, corresponding to the effective uid + * gid under which the server should run, + * default is "nobody.nogroup" (third argument + * for "defprot" rule, optional; can be given + * only if protection setup file is also given). + * + * ON EXIT: + * returns nothing. + * Sets the module-wide variable default_prot. + */ + extern void HTAA_setDefaultProtection(const char *cur_docname, + const char *prot_filename, + const char *eff_ids); + +/* PUBLIC HTAA_setCurrentProtection() + * SET THE CURRENT PROTECTION MODE + * (called by rule system when a + * "protect" rule is matched) + * ON ENTRY: + * cur_docname is the current result of rule translations. + * prot_filename is the protection setup file (second argument + * for "protect" rule, optional) + * eff_ids contains user and group names separated by + * a dot, corresponding to the effective uid + * gid under which the server should run, + * default is "nobody.nogroup" (third argument + * for "protect" rule, optional; can be given + * only if protection setup file is also given). + * + * ON EXIT: + * returns nothing. + * Sets the module-wide variable current_prot. + */ + extern void HTAA_setCurrentProtection(const char *cur_docname, + const char *prot_filename, + const char *eff_ids); + +/* SERVER INTERNAL HTAA_clearProtections() + * CLEAR DOCUMENT PROTECTION MODE + * (ALSO DEFAULT PROTECTION) + * (called by the rule system) + * ON ENTRY: + * No arguments. + * + * ON EXIT: + * returns nothing. + * Frees the memory used by protection information. + */ + extern void HTAA_clearProtections(void); + +/* + +Getting Protection Settings + + HTAA_getCurrentProtection() returns the current protection mode (if there was a + "protect" rule). NULL, if no "protect" rule has been matched. + + HTAA_getDefaultProtection() sets the current protection mode to what it was set to + by "defprot" rule and also returns it (therefore after this call also + HTAA_getCurrentProtection() returns the same structure. + + */ + +/* PUBLIC HTAA_getCurrentProtection() + * GET CURRENT PROTECTION SETUP STRUCTURE + * (this is set up by callbacks made from + * the rule system when matching "protect" + * (and "defprot") rules) + * ON ENTRY: + * HTTranslate() must have been called before calling + * this function. + * + * ON EXIT: + * returns a HTAAProt structure representing the + * protection setup of the HTTranslate()'d file. + * This must not be free()'d. + */ + extern HTAAProt *HTAA_getCurrentProtection(void); + +/* PUBLIC HTAA_getDefaultProtection() + * GET DEFAULT PROTECTION SETUP STRUCTURE + * (this is set up by callbacks made from + * the rule system when matching "defprot" + * rules) + * ON ENTRY: + * HTTranslate() must have been called before calling + * this function. + * + * ON EXIT: + * returns a HTAAProt structure representing the + * default protection setup of the HTTranslate()'d + * file (if HTAA_getCurrentProtection() returned + * NULL, i.e., if there is no "protect" rule + * but ACL exists, and we need to know default + * protection settings). + * This must not be free()'d. + */ + extern HTAAProt *HTAA_getDefaultProtection(void); + +/* + +Get User and Group IDs to Which Set to + + */ + +#ifndef NOUSERS +/* PUBLIC HTAA_getUid() + * GET THE USER ID TO CHANGE THE PROCESS UID TO + * ON ENTRY: + * No arguments. + * + * ON EXIT: + * returns the uid number to give to setuid() system call. + * Default is 65534 (nobody). + */ + extern int HTAA_getUid(void); + +/* PUBLIC HTAA_getGid() + * GET THE GROUP ID TO CHANGE THE PROCESS GID TO + * ON ENTRY: + * No arguments. + * + * ON EXIT: + * returns the uid number to give to setgid() system call. + * Default is 65534 (nogroup). + */ + extern int HTAA_getGid(void); +#endif /* !NOUSERS */ + +/* PUBLIC HTAA_UidToName + * GET THE USER NAME + * ON ENTRY: + * The user-id + * + * ON EXIT: + * returns the user name + */ + extern const char *HTAA_UidToName(int uid); + +/* PUBLIC HTAA_NameToUid + * GET THE USER ID + * ON ENTRY: + * The user-name + * + * ON EXIT: + * returns the user id + */ + extern int HTAA_NameToUid(const char *name); + +/* PUBLIC HTAA_GidToName + * GET THE GROUP NAME + * ON ENTRY: + * The group-id + * + * ON EXIT: + * returns the group name + */ + extern const char *HTAA_GidToName(int gid); + +/* PUBLIC HTAA_NameToGid + * GET THE GROUP ID + * ON ENTRY: + * The group-name + * + * ON EXIT: + * returns the group id + */ + extern int HTAA_NameToGid(const char *name); + +#ifdef __cplusplus +} +#endif +#endif /* not HTAAPROT_H */ diff --git a/WWW/Library/Implementation/HTAAUtil.c b/WWW/Library/Implementation/HTAAUtil.c new file mode 100644 index 0000000..1be26f9 --- /dev/null +++ b/WWW/Library/Implementation/HTAAUtil.c @@ -0,0 +1,605 @@ +/* + * $LynxId: HTAAUtil.c,v 1.36 2016/11/24 15:29:50 tom Exp $ + * + * MODULE HTAAUtil.c + * COMMON PARTS OF ACCESS AUTHORIZATION MODULE + * FOR BOTH SERVER AND BROWSER + * + * IMPORTANT: + * Routines in this module use dynamic allocation, but free + * automatically all the memory reserved by them. + * + * Therefore the caller never has to (and never should) + * free() any object returned by these functions. + * + * Therefore also all the strings returned by this package + * are only valid until the next call to the same function + * is made. This approach is selected, because of the nature + * of access authorization: no string returned by the package + * needs to be valid longer than until the next call. + * + * This also makes it easy to plug the AA package in: + * you don't have to ponder whether to free() something + * here or is it done somewhere else (because it is always + * done somewhere else). + * + * The strings that the package needs to store are copied + * so the original strings given as parameters to AA + * functions may be freed or modified with no side effects. + * + * The AA package does not free() anything else than what + * it has itself allocated. + * + * AA (Access Authorization) package means modules which + * names start with HTAA. + * + * AUTHORS: + * AL Ari Luotonen luotonen@dxcern.cern.ch + * MD Mark Donszelmann duns@vxdeop.cern.ch + * + * HISTORY: + * 8 Nov 93 MD (VMS only) Added case insensitive comparison in HTAA_templateCaseMatch + * + * + * BUGS: + * + * + */ + +#include + +#include /* Implemented here */ +#include /* Assoc list */ +#include +#include + +#include +#include +#include + +/* PUBLIC HTAAScheme_enum() + * TRANSLATE SCHEME NAME INTO + * A SCHEME ENUMERATION + * + * ON ENTRY: + * name is a string representing the scheme name. + * + * ON EXIT: + * returns the enumerated constant for that scheme. + */ +HTAAScheme HTAAScheme_enum(const char *name) +{ + char *upcased = NULL; + + if (!name) + return HTAA_UNKNOWN; + + StrAllocCopy(upcased, name); + LYUpperCase(upcased); + + if (!StrNCmp(upcased, "NONE", 4)) { + FREE(upcased); + return HTAA_NONE; + } else if (!StrNCmp(upcased, "BASIC", 5)) { + FREE(upcased); + return HTAA_BASIC; + } else if (!StrNCmp(upcased, "PUBKEY", 6)) { + FREE(upcased); + return HTAA_PUBKEY; + } else if (!StrNCmp(upcased, "KERBEROSV4", 10)) { + FREE(upcased); + return HTAA_KERBEROS_V4; + } else if (!StrNCmp(upcased, "KERBEROSV5", 10)) { + FREE(upcased); + return HTAA_KERBEROS_V5; + } else { + FREE(upcased); + return HTAA_UNKNOWN; + } +} + +/* PUBLIC HTAAScheme_name() + * GET THE NAME OF A GIVEN SCHEME + * ON ENTRY: + * scheme is one of the scheme enum values: + * HTAA_NONE, HTAA_BASIC, HTAA_PUBKEY, ... + * + * ON EXIT: + * returns the name of the scheme, i.e. + * "None", "Basic", "Pubkey", ... + */ +const char *HTAAScheme_name(HTAAScheme scheme) +{ + switch (scheme) { + case HTAA_NONE: + return "None"; + case HTAA_BASIC: + return "Basic"; + case HTAA_PUBKEY: + return "Pubkey"; + case HTAA_KERBEROS_V4: + return "KerberosV4"; + case HTAA_KERBEROS_V5: + return "KerberosV5"; + case HTAA_UNKNOWN: + return "UNKNOWN"; + default: + return "THIS-IS-A-BUG"; + } +} + +/* PUBLIC HTAAMethod_enum() + * TRANSLATE METHOD NAME INTO AN ENUMERATED VALUE + * ON ENTRY: + * name is the method name to translate. + * + * ON EXIT: + * returns HTAAMethod enumerated value corresponding + * to the given name. + */ +HTAAMethod HTAAMethod_enum(const char *name) +{ + if (!name) + return METHOD_UNKNOWN; + + if (0 == strcasecomp(name, "GET")) + return METHOD_GET; + else if (0 == strcasecomp(name, "PUT")) + return METHOD_PUT; + else + return METHOD_UNKNOWN; +} + +/* PUBLIC HTAAMethod_name() + * GET THE NAME OF A GIVEN METHOD + * ON ENTRY: + * method is one of the method enum values: + * METHOD_GET, METHOD_PUT, ... + * + * ON EXIT: + * returns the name of the scheme, i.e. + * "GET", "PUT", ... + */ +const char *HTAAMethod_name(HTAAMethod method) +{ + switch (method) { + case METHOD_GET: + return "GET"; + case METHOD_PUT: + return "PUT"; + case METHOD_UNKNOWN: + return "UNKNOWN"; + default: + return "THIS-IS-A-BUG"; + } +} + +/* PUBLIC HTAAMethod_inList() + * IS A METHOD IN A LIST OF METHOD NAMES + * ON ENTRY: + * method is the method to look for. + * list is a list of method names. + * + * ON EXIT: + * returns YES, if method was found. + * NO, if not found. + */ +BOOL HTAAMethod_inList(HTAAMethod method, HTList *list) +{ + HTList *cur = list; + char *item; + + while (NULL != (item = (char *) HTList_nextObject(cur))) { + CTRACE((tfp, " %s", item)); + if (method == HTAAMethod_enum(item)) + return YES; + } + + return NO; /* Not found */ +} + +/* PUBLIC HTAA_templateMatch() + * STRING COMPARISON FUNCTION FOR FILE NAMES + * WITH ONE WILDCARD * IN THE TEMPLATE + * NOTE: + * This is essentially the same code as in HTRules.c, but it + * cannot be used because it is embedded in between other code. + * (In fact, HTRules.c should use this routine, but then this + * routine would have to be more sophisticated... why is life + * sometimes so hard...) + * + * ON ENTRY: + * ctemplate is a template string to match the file name + * against, may contain a single wildcard + * character * which matches zero or more + * arbitrary characters. + * filename is the filename (or pathname) to be matched + * against the template. + * + * ON EXIT: + * returns YES, if filename matches the template. + * NO, otherwise. + */ +BOOL HTAA_templateMatch(const char *ctemplate, + const char *filename) +{ + const char *p = ctemplate; + const char *q = filename; + int m; + + for (; *p && *q && *p == *q; p++, q++) /* Find first mismatch */ + ; /* do nothing else */ + + if (!*p && !*q) + return YES; /* Equally long equal strings */ + else if ('*' == *p) { /* Wildcard */ + p++; /* Skip wildcard character */ + m = (int) (strlen(q) - strlen(p)); /* Amount to match to wildcard */ + if (m < 0) + return NO; /* No match, filename too short */ + else { /* Skip the matched characters and compare */ + if (strcmp(p, q + m)) + return NO; /* Tail mismatch */ + else + return YES; /* Tail match */ + } + /* if wildcard */ + } else + return NO; /* Length or character mismatch */ +} + +/* PUBLIC HTAA_templateCaseMatch() + * STRING COMPARISON FUNCTION FOR FILE NAMES + * WITH ONE WILDCARD * IN THE TEMPLATE (Case Insensitive) + * NOTE: + * This is essentially the same code as in HTAA_templateMatch, but + * it compares case insensitive (for VMS). Reason for this routine + * is that HTAA_templateMatch gets called from several places, also + * there where a case sensitive match is needed, so one cannot just + * change the HTAA_templateMatch routine for VMS. + * + * ON ENTRY: + * template is a template string to match the file name + * against, may contain a single wildcard + * character * which matches zero or more + * arbitrary characters. + * filename is the filename (or pathname) to be matched + * against the template. + * + * ON EXIT: + * returns YES, if filename matches the template. + * NO, otherwise. + */ +BOOL HTAA_templateCaseMatch(const char *ctemplate, + const char *filename) +{ + const char *p = ctemplate; + const char *q = filename; + int m; + + /* Find first mismatch */ + for (; *p && *q && TOUPPER(*p) == TOUPPER(*q); p++, q++) ; /* do nothing else */ + + if (!*p && !*q) + return YES; /* Equally long equal strings */ + else if ('*' == *p) { /* Wildcard */ + p++; /* Skip wildcard character */ + m = (int) (strlen(q) - strlen(p)); /* Amount to match to wildcard */ + if (m < 0) + return NO; /* No match, filename too short */ + else { /* Skip the matched characters and compare */ + if (strcasecomp(p, q + m)) + return NO; /* Tail mismatch */ + else + return YES; /* Tail match */ + } + /* if wildcard */ + } else + return NO; /* Length or character mismatch */ +} + +/* PUBLIC HTAA_makeProtectionTemplate() + * CREATE A PROTECTION TEMPLATE FOR THE FILES + * IN THE SAME DIRECTORY AS THE GIVEN FILE + * (Used by server if there is no fancier way for + * it to tell the client, and by browser if server + * didn't send WWW-ProtectionTemplate: field) + * ON ENTRY: + * docname is the document pathname (from URL). + * + * ON EXIT: + * returns a template matching docname, and other files + * files in that directory. + * + * E.g. /foo/bar/x.html => /foo/bar/ * + * ^ + * Space only to prevent it from + * being a comment marker here, + * there really isn't any space. + */ +char *HTAA_makeProtectionTemplate(const char *docname) +{ + char *ctemplate = NULL; + char *slash = NULL; + + if (docname) { + StrAllocCopy(ctemplate, docname); + slash = strrchr(ctemplate, '/'); + if (slash) + slash++; + else + slash = ctemplate; + *slash = '\0'; + StrAllocCat(ctemplate, "*"); + } else + StrAllocCopy(ctemplate, "*"); + + CTRACE((tfp, "make_template: made template `%s' for file `%s'\n", + ctemplate, docname)); + + return ctemplate; +} + +/* + * Skip leading whitespace from *s forward + */ +#define SKIPWS(s) while (*s==' ' || *s=='\t') s++; + +/* + * Kill trailing whitespace starting from *(s-1) backwards + */ +#define KILLWS(s) {char *c=s-1; while (*c==' ' || *c=='\t') *(c--)='\0';} + +/* PUBLIC HTAA_parseArgList() + * PARSE AN ARGUMENT LIST GIVEN IN A HEADER FIELD + * ON ENTRY: + * str is a comma-separated list: + * + * item, item, item + * where + * item ::= value + * | name=value + * | name="value" + * + * Leading and trailing whitespace is ignored + * everywhere except inside quotes, so the following + * examples are equal: + * + * name=value,foo=bar + * name="value",foo="bar" + * name = value , foo = bar + * name = "value" , foo = "bar" + * + * ON EXIT: + * returns a list of name-value pairs (actually HTAssocList*). + * For items with no name, just value, the name is + * the number of order number of that item. E.g. + * "1" for the first, etc. + */ +HTAssocList *HTAA_parseArgList(char *str) +{ + HTAssocList *assoc_list = HTAssocList_new(); + char *cur = NULL; + char *name = NULL; + int n = 0; + + if (!str) + return assoc_list; + + while (*str) { + SKIPWS(str); /* Skip leading whitespace */ + cur = str; + n++; + + while (*cur && *cur != '=' && *cur != ',') + cur++; /* Find end of name (or lonely value without a name) */ + KILLWS(cur); /* Kill trailing whitespace */ + + if (*cur == '=') { /* Name followed by a value */ + *(cur++) = '\0'; /* Terminate name */ + StrAllocCopy(name, str); + SKIPWS(cur); /* Skip WS leading the value */ + str = cur; + if (*str == '"') { /* Quoted value */ + str++; + cur = str; + while (*cur && *cur != '"') + cur++; + if (*cur == '"') + *(cur++) = '\0'; /* Terminate value */ + /* else it is lacking terminating quote */ + SKIPWS(cur); /* Skip WS leading comma */ + if (*cur == ',') + cur++; /* Skip separating colon */ + } else { /* Unquoted value */ + while (*cur && *cur != ',') + cur++; + KILLWS(cur); /* Kill trailing whitespace */ + if (*cur == ',') + *(cur++) = '\0'; + /* else *cur already NULL */ + } + } else { /* No name, just a value */ + if (*cur == ',') + *(cur++) = '\0'; /* Terminate value */ + /* else last value on line (already terminated by NULL) */ + HTSprintf0(&name, "%d", n); /* Item order number for name */ + } + HTAssocList_add(assoc_list, name, str); + str = cur; + } /* while *str */ + + FREE(name); + return assoc_list; +} + +/************** HEADER LINE READER -- DOES UNFOLDING *************************/ + +#define BUFFER_SIZE 1024 + +static size_t buffer_length; +static char *buffer = 0; +static char *start_pointer; +static char *end_pointer; +static int in_soc = -1; + +#ifdef LY_FIND_LEAKS +static void FreeHTAAUtil(void) +{ + FREE(buffer); +} +#endif /* LY_FIND_LEAKS */ + +/* PUBLIC HTAA_setupReader() + * SET UP HEADER LINE READER, i.e., give + * the already-read-but-not-yet-processed + * buffer of text to be read before more + * is read from the socket. + * ON ENTRY: + * start_of_headers is a pointer to a buffer containing + * the beginning of the header lines + * (rest will be read from a socket). + * length is the number of valid characters in + * 'start_of_headers' buffer. + * soc is the socket to use when start_of_headers + * buffer is used up. + * ON EXIT: + * returns nothing. + * Subsequent calls to HTAA_getUnfoldedLine() + * will use this buffer first and then + * proceed to read from socket. + */ +void HTAA_setupReader(char *start_of_headers, + size_t length, + int soc) +{ + if (!start_of_headers) + length = 0; /* initialize length (is this reached at all?) */ + if (buffer == NULL) { /* first call? */ + buffer_length = length; + if (buffer_length < BUFFER_SIZE) /* would fall below BUFFER_SIZE? */ + buffer_length = BUFFER_SIZE; + buffer = (char *) malloc((size_t) (sizeof(char) * (buffer_length + 1))); + } else if (length > buffer_length) { /* need more space? */ + buffer_length = length; + buffer = (char *) realloc((char *) buffer, + (size_t) (sizeof(char) * (buffer_length + 1))); + } + if (buffer == NULL) + outofmem(__FILE__, "HTAA_setupReader"); + +#ifdef LY_FIND_LEAKS + atexit(FreeHTAAUtil); +#endif + start_pointer = buffer; + if (start_of_headers) { + LYStrNCpy(buffer, start_of_headers, length); + end_pointer = buffer + length; + } else { + *start_pointer = '\0'; + end_pointer = start_pointer; + } + in_soc = soc; +} + +/* PUBLIC HTAA_getUnfoldedLine() + * READ AN UNFOLDED HEADER LINE FROM SOCKET + * ON ENTRY: + * HTAA_setupReader must absolutely be called before + * this function to set up internal buffer. + * + * ON EXIT: + * returns a newly-allocated character string representing + * the read line. The line is unfolded, i.e. + * lines that begin with whitespace are appended + * to current line. E.g. + * + * Field-Name: Blaa-Blaa + * This-Is-A-Continuation-Line + * Here-Is_Another + * + * is seen by the caller as: + * + * Field-Name: Blaa-Blaa This-Is-A-Continuation-Line Here-Is_Another + * + */ +char *HTAA_getUnfoldedLine(void) +{ + char *line = NULL; + char *cur; + int count; + BOOL peek_for_folding = NO; + + if (in_soc < 0) { + CTRACE((tfp, "%s %s\n", + "HTAA_getUnfoldedLine: buffer not initialized", + "with function HTAA_setupReader()")); + return NULL; + } + + for (;;) { + + /* Reading from socket */ + + if (start_pointer >= end_pointer) { /*Read the next block and continue */ +#ifdef USE_SSL + if (SSL_handle) + count = SSL_read(SSL_handle, buffer, BUFFER_SIZE); + else + count = NETREAD(in_soc, buffer, BUFFER_SIZE); +#else + count = NETREAD(in_soc, buffer, BUFFER_SIZE); +#endif /* USE_SSL */ + if (count <= 0) { + in_soc = -1; + return line; + } + if (count > (int) buffer_length) + count = (int) buffer_length; + start_pointer = buffer; + end_pointer = buffer + count; + *end_pointer = '\0'; +#ifdef NOT_ASCII + cur = start_pointer; + while (cur < end_pointer) { + *cur = TOASCII(*cur); + cur++; + } +#endif /*NOT_ASCII */ + } + cur = start_pointer; + + /* Unfolding */ + + if (peek_for_folding) { + if (*cur != ' ' && *cur != '\t') + return line; /* Ok, no continuation line */ + else /* So this is a continuation line, continue */ + peek_for_folding = NO; + } + + /* Finding end-of-line */ + + while (cur < end_pointer && *cur != '\n') /* Find the end-of-line */ + cur++; /* (or end-of-buffer). */ + + /* Terminating line */ + + if (cur < end_pointer) { /* So *cur==LF, terminate line */ + *cur = '\0'; /* Overwrite LF */ + if (*(cur - 1) == '\r') + *(cur - 1) = '\0'; /* Overwrite CR */ + peek_for_folding = YES; /* Check for a continuation line */ + } + + /* Copying the result */ + + if (line) + StrAllocCat(line, start_pointer); /* Append */ + else + StrAllocCopy(line, start_pointer); /* A new line */ + + start_pointer = cur + 1; /* Skip the read line */ + + } /* forever */ +} diff --git a/WWW/Library/Implementation/HTAAUtil.h b/WWW/Library/Implementation/HTAAUtil.h new file mode 100644 index 0000000..33a8ee3 --- /dev/null +++ b/WWW/Library/Implementation/HTAAUtil.h @@ -0,0 +1,318 @@ +/* + * $LynxId: HTAAUtil.h,v 1.13 2010/10/27 00:09:52 tom Exp $ + * + * Utilities for the Authorization parts of libwww + * COMMON PARTS OF AUTHORIZATION MODULE TO BOTH SERVER AND BROWSER + * + * This module is the interface to the common parts of Access Authorization (AA) package + * for both server and browser. Important to know about memory allocation: + * + * Routines in this module use dynamic allocation, but free automatically all the memory + * reserved by them. + * + * Therefore the caller never has to (and never should) free() any object returned by + * these functions. + * + * Therefore also all the strings returned by this package are only valid until the next + * call to the same function is made. This approach is selected, because of the nature of + * access authorization: no string returned by the package needs to be valid longer than + * until the next call. + * + * This also makes it easy to plug the AA package in: you don't have to ponder whether to + * free() something here or is it done somewhere else (because it is always done somewhere + * else). + * + * The strings that the package needs to store are copied so the original strings given as + * parameters to AA functions may be freed or modified with no side effects. + * + * Also note: The AA package does not free() anything else than what it has itself + * allocated. + * + */ + +#ifndef HTAAUTIL_H +#define HTAAUTIL_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif +/* + * Numeric constants + */ +#define MAX_USERNAME_LEN 16 /* @@ Longest allowed username */ +#define MAX_PASSWORD_LEN 3*13 /* @@ Longest allowed password */ + /* (encrypted, so really only 3*8) */ +#define MAX_METHODNAME_LEN 12 /* @@ Longest allowed method name */ +#define MAX_FIELDNAME_LEN 16 /* @@ Longest field name in */ + /* protection setup file */ +#define MAX_PATHNAME_LEN 80 /* @@ Longest passwd/group file */ +/* pathname to allow */ +/* + + Datatype definitions + + HTAASCHEME + + The enumeration HTAAScheme represents the possible authentication schemes used by the + WWW Access Authorization. + + */ typedef enum { + HTAA_UNKNOWN, + HTAA_NONE, + HTAA_BASIC, + HTAA_PUBKEY, + HTAA_KERBEROS_V4, + HTAA_KERBEROS_V5, + HTAA_MAX_SCHEMES /* THIS MUST ALWAYS BE LAST! Number of schemes */ + } HTAAScheme; + +/* + + ENUMERATION TO REPRESENT HTTP METHODS + + */ + + typedef enum { + METHOD_UNKNOWN, + METHOD_GET, + METHOD_PUT + } HTAAMethod; + +/* + +Authentication Schemes + + */ + +/* PUBLIC HTAAScheme_enum() + * TRANSLATE SCHEME NAME TO A SCHEME ENUMERATION + * ON ENTRY: + * name is a string representing the scheme name. + * + * ON EXIT: + * returns the enumerated constant for that scheme. + */ + extern HTAAScheme HTAAScheme_enum(const char *name); + +/* PUBLIC HTAAScheme_name() + * GET THE NAME OF A GIVEN SCHEME + * ON ENTRY: + * scheme is one of the scheme enum values: + * HTAA_NONE, HTAA_BASIC, HTAA_PUBKEY, ... + * + * ON EXIT: + * returns the name of the scheme, i.e. + * "none", "basic", "pubkey", ... + */ + extern const char *HTAAScheme_name(HTAAScheme scheme); + +/* + +Methods + + */ + +/* PUBLIC HTAAMethod_enum() + * TRANSLATE METHOD NAME INTO AN ENUMERATED VALUE + * ON ENTRY: + * name is the method name to translate. + * + * ON EXIT: + * returns HTAAMethod enumerated value corresponding + * to the given name. + */ + extern HTAAMethod HTAAMethod_enum(const char *name); + +/* PUBLIC HTAAMethod_name() + * GET THE NAME OF A GIVEN METHOD + * ON ENTRY: + * method is one of the method enum values: + * METHOD_GET, METHOD_PUT, ... + * + * ON EXIT: + * returns the name of the scheme, i.e. + * "GET", "PUT", ... + */ + extern const char *HTAAMethod_name(HTAAMethod method); + +/* PUBLIC HTAAMethod_inList() + * IS A METHOD IN A LIST OF METHOD NAMES + * ON ENTRY: + * method is the method to look for. + * list is a list of method names. + * + * ON EXIT: + * returns YES, if method was found. + * NO, if not found. + */ + extern BOOL HTAAMethod_inList(HTAAMethod method, HTList *list); + +/* + +Match Template Against Filename + + */ + +/* PUBLIC HTAA_templateMatch() + * STRING COMPARISON FUNCTION FOR FILE NAMES + * WITH ONE WILDCARD * IN THE TEMPLATE + * NOTE: + * This is essentially the same code as in HTRules.c, but it + * cannot be used because it is embedded in between other code. + * (In fact, HTRules.c should use this routine, but then this + * routine would have to be more sophisticated... why is life + * sometimes so hard...) + * + * ON ENTRY: + * ctemplate is a template string to match the file name + * against, may contain a single wildcard + * character * which matches zero or more + * arbitrary characters. + * filename is the filename (or pathname) to be matched + * against the template. + * + * ON EXIT: + * returns YES, if filename matches the template. + * NO, otherwise. + */ + extern BOOL HTAA_templateMatch(const char *ctemplate, + const char *filename); + +/* PUBLIC HTAA_templateCaseMatch() + * STRING COMPARISON FUNCTION FOR FILE NAMES + * WITH ONE WILDCARD * IN THE TEMPLATE (Case Insensitive) + * NOTE: + * This is essentially the same code as in HTAA_templateMatch, but + * it compares case insensitive (for VMS). Reason for this routine + * is that HTAA_templateMatch gets called from several places, also + * there where a case sensitive match is needed, so one cannot just + * change the HTAA_templateMatch routine for VMS. + * + * ON ENTRY: + * ctemplate is a template string to match the file name + * against, may contain a single wildcard + * character * which matches zero or more + * arbitrary characters. + * filename is the filename (or pathname) to be matched + * against the template. + * + * ON EXIT: + * returns YES, if filename matches the template. + * NO, otherwise. + */ + extern BOOL HTAA_templateCaseMatch(const char *ctemplate, + const char *filename); + +/* PUBLIC HTAA_makeProtectionTemplate() + * CREATE A PROTECTION TEMPLATE FOR THE FILES + * IN THE SAME DIRECTORY AS THE GIVEN FILE + * (Used by server if there is no fancier way for + * it to tell the client, and by browser if server + * didn't send WWW-ProtectionTemplate: field) + * ON ENTRY: + * docname is the document pathname (from URL). + * + * ON EXIT: + * returns a template matching docname, and other files + * files in that directory. + * + * E.g. /foo/bar/x.html => /foo/bar/ * + * ^ + * Space only to prevent it from + * being a comment marker here, + * there really isn't any space. + */ + extern char *HTAA_makeProtectionTemplate(const char *docname); + +/* + +MIME Argument List Parser + + */ + +/* PUBLIC HTAA_parseArgList() + * PARSE AN ARGUMENT LIST GIVEN IN A HEADER FIELD + * ON ENTRY: + * str is a comma-separated list: + * + * item, item, item + * where + * item ::= value + * | name=value + * | name="value" + * + * Leading and trailing whitespace is ignored + * everywhere except inside quotes, so the following + * examples are equal: + * + * name=value,foo=bar + * name="value",foo="bar" + * name = value , foo = bar + * name = "value" , foo = "bar" + * + * ON EXIT: + * returns a list of name-value pairs (actually HTAssocList*). + * For items with no name, just value, the name is + * the number of order number of that item. E.g. + * "1" for the first, etc. + */ + extern HTList *HTAA_parseArgList(char *str); + +/* + +Header Line Reader + + */ + +/* PUBLIC HTAA_setupReader() + * SET UP HEADER LINE READER, i.e., give + * the already-read-but-not-yet-processed + * buffer of text to be read before more + * is read from the socket. + * ON ENTRY: + * start_of_headers is a pointer to a buffer containing + * the beginning of the header lines + * (rest will be read from a socket). + * length is the number of valid characters in + * 'start_of_headers' buffer. + * soc is the socket to use when start_of_headers + * buffer is used up. + * ON EXIT: + * returns nothing. + * Subsequent calls to HTAA_getUnfoldedLine() + * will use this buffer first and then + * proceed to read from socket. + */ + extern void HTAA_setupReader(char *start_of_headers, + size_t length, + int soc); + +/* PUBLIC HTAA_getUnfoldedLine() + * READ AN UNFOLDED HEADER LINE FROM SOCKET + * ON ENTRY: + * HTAA_setupReader must absolutely be called before + * this function to set up internal buffer. + * + * ON EXIT: + * returns a newly-allocated character string representing + * the read line. The line is unfolded, i.e. + * lines that begin with whitespace are appended + * to current line. E.g. + * + * Field-Name: Blaa-Blaa + * This-Is-A-Continuation-Line + * Here-Is_Another + * + * is seen by the caller as: + * + * Field-Name: Blaa-Blaa This-Is-A-Continuation-Line Here-Is_Another + * + */ + extern char *HTAA_getUnfoldedLine(void); + +#ifdef __cplusplus +} +#endif +#endif /* NOT HTAAUTIL_H */ diff --git a/WWW/Library/Implementation/HTAccess.c b/WWW/Library/Implementation/HTAccess.c new file mode 100644 index 0000000..ba81b83 --- /dev/null +++ b/WWW/Library/Implementation/HTAccess.c @@ -0,0 +1,1303 @@ +/* + * $LynxId: HTAccess.c,v 1.87 2024/01/10 23:52:47 tom Exp $ + * + * Access Manager HTAccess.c + * ============== + * + * Authors + * TBL Tim Berners-Lee timbl@info.cern.ch + * JFG Jean-Francois Groff jfg@dxcern.cern.ch + * DD Denis DeLaRoca (310) 825-4580 + * FM Foteos Macrides macrides@sci.wfeb.edu + * PDM Danny Mayer mayer@ljo.dec.com + * + * History + * 8 Jun 92 Telnet hopping prohibited as telnet is not secure TBL + * 26 Jun 92 When over DECnet, suppressed FTP, Gopher and News. JFG + * 6 Oct 92 Moved HTClientHost and logfile into here. TBL + * 17 Dec 92 Tn3270 added, bug fix. DD + * 4 Feb 93 Access registration, Search escapes bad chars TBL + * PARAMETERS TO HTSEARCH AND HTLOADRELATIVE CHANGED + * 28 May 93 WAIS gateway explicit if no WAIS library linked in. + * 31 May 94 Added DIRECT_WAIS support for VMS. FM + * 27 Jan 95 Fixed proxy support to use NNTPSERVER for checking + * whether or not to use the proxy server. PDM + * 27 Jan 95 Ensured that proxy service will be overridden for files + * on the local host (because HTLoadFile() doesn't try ftp + * for those) and will substitute ftp for remote files. FM + * 28 Jan 95 Tweaked PDM's proxy override mods to handle port info + * for news and wais URL's. FM + * + * Bugs + * This module assumes that that the graphic object is hypertext, as it + * needs to select it when it has been loaded. A superclass needs to be + * defined which accepts select and select_anchor. + */ + +#ifdef VMS +#define DIRECT_WAIS +#endif /* VMS */ + +#include +#include +#include +/* + * Implements: + */ +#include + +/* + * Uses: + */ +#include +#include /* SCW */ + +#ifndef NO_RULES +#include +#endif + +#include +#include /* See bugs above */ +#include +#include +#include + +#include +#include +#include +#include +#include + +/* + * These flags may be set to modify the operation of this module + */ +char *HTClientHost = NULL; /* Name of remote login host if any */ +FILE *HTlogfile = NULL; /* File to which to output one-liners */ +BOOL HTSecure = NO; /* Disable access for telnet users? */ +BOOL HTPermitRedir = NO; /* Always allow redirection in getfile()? */ + +BOOL using_proxy = NO; /* are we using a proxy gateway? */ + +/* + * To generate other things, play with these: + */ +HTFormat HTOutputFormat = NULL; +HTStream *HTOutputStream = NULL; /* For non-interactive, set this */ + +static HTList *protocols = NULL; /* List of registered protocol descriptors */ + +char *use_this_url_instead = NULL; + +static int pushed_assume_LYhndl = -1; /* see LYUC* functions below - kw */ +static char *pushed_assume_MIMEname = NULL; + +#ifdef LY_FIND_LEAKS +static void free_protocols(void) +{ + HTList_delete(protocols); + protocols = NULL; + FREE(pushed_assume_MIMEname); /* shouldn't happen, just in case - kw */ +} +#endif /* LY_FIND_LEAKS */ + +/* Register a Protocol. HTRegisterProtocol() + * -------------------- + */ +BOOL HTRegisterProtocol(HTProtocol * protocol) +{ + if (!protocols) { + protocols = HTList_new(); +#ifdef LY_FIND_LEAKS + atexit(free_protocols); +#endif + } + HTList_addObject(protocols, protocol); + return YES; +} + +/* Register all known protocols. HTAccessInit() + * ----------------------------- + * + * Add to or subtract from this list if you add or remove protocol + * modules. This routine is called the first time the protocol list + * is needed, unless any protocols are already registered, in which + * case it is not called. Therefore the application can override + * this list. + * + * Compiling with NO_INIT prevents all known protocols from being + * forced in at link time. + */ +#ifndef NO_INIT +#ifdef GLOBALREF_IS_MACRO +extern GLOBALREF (HTProtocol, HTTP); +extern GLOBALREF (HTProtocol, HTTPS); +extern GLOBALREF (HTProtocol, HTFile); +extern GLOBALREF (HTProtocol, HTTelnet); +extern GLOBALREF (HTProtocol, HTTn3270); +extern GLOBALREF (HTProtocol, HTRlogin); + +#ifndef DECNET +#ifndef DISABLE_FTP +extern GLOBALREF (HTProtocol, HTFTP); +#endif /* DISABLE_FTP */ +#ifndef DISABLE_NEWS +extern GLOBALREF (HTProtocol, HTNews); +extern GLOBALREF (HTProtocol, HTNNTP); +extern GLOBALREF (HTProtocol, HTNewsPost); +extern GLOBALREF (HTProtocol, HTNewsReply); +extern GLOBALREF (HTProtocol, HTSNews); +extern GLOBALREF (HTProtocol, HTSNewsPost); +extern GLOBALREF (HTProtocol, HTSNewsReply); +#endif /* not DISABLE_NEWS */ +#ifndef DISABLE_GOPHER +extern GLOBALREF (HTProtocol, HTGopher); +extern GLOBALREF (HTProtocol, HTCSO); +#endif /* not DISABLE_GOPHER */ +#ifndef DISABLE_FINGER +extern GLOBALREF (HTProtocol, HTFinger); +#endif /* not DISABLE_FINGER */ +#ifdef DIRECT_WAIS +extern GLOBALREF (HTProtocol, HTWAIS); +#endif /* DIRECT_WAIS */ +#endif /* !DECNET */ +#else +GLOBALREF HTProtocol HTTP, HTTPS, HTFile, HTTelnet, HTTn3270, HTRlogin; + +#ifndef DECNET +#ifndef DISABLE_FTP +GLOBALREF HTProtocol HTFTP; +#endif /* DISABLE_FTP */ +#ifndef DISABLE_NEWS +GLOBALREF HTProtocol HTNews, HTNNTP, HTNewsPost, HTNewsReply; +GLOBALREF HTProtocol HTSNews, HTSNewsPost, HTSNewsReply; +#endif /* not DISABLE_NEWS */ +#ifndef DISABLE_GOPHER +GLOBALREF HTProtocol HTGopher, HTCSO; +#endif /* not DISABLE_GOPHER */ +#ifndef DISABLE_FINGER +GLOBALREF HTProtocol HTFinger; +#endif /* not DISABLE_FINGER */ +#ifdef DIRECT_WAIS +GLOBALREF HTProtocol HTWAIS; +#endif /* DIRECT_WAIS */ +#endif /* !DECNET */ +#endif /* GLOBALREF_IS_MACRO */ + +static void HTAccessInit(void) /* Call me once */ +{ + HTRegisterProtocol(&HTTP); + HTRegisterProtocol(&HTTPS); + HTRegisterProtocol(&HTFile); + HTRegisterProtocol(&HTTelnet); + HTRegisterProtocol(&HTTn3270); + HTRegisterProtocol(&HTRlogin); +#ifndef DECNET +#ifndef DISABLE_FTP + HTRegisterProtocol(&HTFTP); +#endif /* DISABLE_FTP */ +#ifndef DISABLE_NEWS + HTRegisterProtocol(&HTNews); + HTRegisterProtocol(&HTNNTP); + HTRegisterProtocol(&HTNewsPost); + HTRegisterProtocol(&HTNewsReply); + HTRegisterProtocol(&HTSNews); + HTRegisterProtocol(&HTSNewsPost); + HTRegisterProtocol(&HTSNewsReply); +#endif /* not DISABLE_NEWS */ +#ifndef DISABLE_GOPHER + HTRegisterProtocol(&HTGopher); + HTRegisterProtocol(&HTCSO); +#endif /* not DISABLE_GOPHER */ +#ifndef DISABLE_FINGER + HTRegisterProtocol(&HTFinger); +#endif /* not DISABLE_FINGER */ +#ifdef DIRECT_WAIS + HTRegisterProtocol(&HTWAIS); +#endif /* DIRECT_WAIS */ +#endif /* !DECNET */ + LYRegisterLynxProtocols(); +} +#endif /* !NO_INIT */ + +/* Check for proxy override. override_proxy() + * ------------------------- + * + * Check the no_proxy environment variable to get the list + * of hosts for which proxy server is not consulted. + * + * no_proxy is a comma- or space-separated list of machine + * or domain names, with optional :port part. If no :port + * part is present, it applies to all ports on that domain. + * + * Example: + * no_proxy="cern.ch,some.domain:8001" + * + * Use "*" to override all proxy service: + * no_proxy="*" + */ +BOOL override_proxy(const char *addr) +{ + const char *no_proxy = getenv("no_proxy"); + char *p = NULL; + char *at = NULL; + char *host = NULL; + char *Host = NULL; + char *acc_method = NULL; + int port = 0; + int h_len = 0; + + /* + * Check for global override. + */ + if (no_proxy) { + if (!strcmp(no_proxy, "*")) + return YES; + } + + /* + * Never proxy file:// URLs if they are on the local host. HTLoadFile() + * will not attempt ftp for those if direct access fails. We'll check that + * first, in case no_proxy hasn't been defined. - FM + */ + if (!addr) + return NO; + if (!(host = HTParse(addr, "", PARSE_HOST))) + return NO; + if (!*host) { + FREE(host); + return NO; + } + Host = (((at = StrChr(host, '@')) != NULL) ? (at + 1) : host); + + if ((acc_method = HTParse(addr, "", PARSE_ACCESS))) { + if (!strcmp("file", acc_method) && + (LYSameHostname(Host, "localhost") || + LYSameHostname(Host, HTHostName()))) { + FREE(host); + FREE(acc_method); + return YES; + } + FREE(acc_method); + } + + if (!no_proxy) { + FREE(host); + return NO; + } + + if (NULL != (p = HTParsePort(Host, &port))) { /* Port specified */ + *p = 0; /* Chop off port */ + } else { /* Use default port */ + acc_method = HTParse(addr, "", PARSE_ACCESS); + if (acc_method != NULL) { + /* *INDENT-OFF* */ + if (!strcmp(acc_method, "http")) port = 80; + else if (!strcmp(acc_method, "https")) port = 443; + else if (!strcmp(acc_method, "ftp")) port = 21; +#ifndef DISABLE_GOPHER + else if (!strcmp(acc_method, "gopher")) port = 70; +#endif + else if (!strcmp(acc_method, "cso")) port = 105; +#ifndef DISABLE_NEWS + else if (!strcmp(acc_method, "news")) port = 119; + else if (!strcmp(acc_method, "nntp")) port = 119; + else if (!strcmp(acc_method, "newspost")) port = 119; + else if (!strcmp(acc_method, "newsreply")) port = 119; + else if (!strcmp(acc_method, "snews")) port = 563; + else if (!strcmp(acc_method, "snewspost")) port = 563; + else if (!strcmp(acc_method, "snewsreply")) port = 563; +#endif + else if (!strcmp(acc_method, "wais")) port = 210; +#ifndef DISABLE_FINGER + else if (!strcmp(acc_method, "finger")) port = 79; +#endif + else if (!strcmp(acc_method, "telnet")) port = 23; + else if (!strcmp(acc_method, "tn3270")) port = 23; + else if (!strcmp(acc_method, "rlogin")) port = 513; + /* *INDENT-ON* */ + + FREE(acc_method); + } + } + if (!port) + port = 80; /* Default */ + h_len = (int) strlen(Host); + + while (*no_proxy) { + const char *end; + const char *colon = NULL; + int templ_port = 0; + int t_len; + int brackets = 0; + + while (*no_proxy && (WHITE(*no_proxy) || *no_proxy == ',')) + no_proxy++; /* Skip whitespace and separators */ + + end = no_proxy; + while (*end && !WHITE(*end) && *end != ',') { /* Find separator */ + if (!brackets && (*end == ':')) + colon = end; /* Port number given */ + else if (*end == '[') + ++brackets; + else if (*end == ']') + --brackets; + end++; + } + + if (colon) { + /* unlike HTParsePort(), this may be followed by non-digits */ + templ_port = atoi(colon + 1); + t_len = (int) (colon - no_proxy); + } else { + t_len = (int) (end - no_proxy); + } + + if ((!templ_port || templ_port == port) && + (t_len > 0 && t_len <= h_len && + !strncasecomp(Host + h_len - t_len, no_proxy, t_len))) { + FREE(host); + return YES; + } +#ifdef CJK_EX /* ASATAKU PROXY HACK */ + if ((!templ_port || templ_port == port) && + (t_len > 0 && t_len <= h_len && + isdigit(UCH(*no_proxy)) && + !StrNCmp(host, no_proxy, t_len))) { + FREE(host); + return YES; + } +#endif /* ASATAKU PROXY HACK */ + + if (*end) + no_proxy = (end + 1); + else + break; + } + + FREE(host); + return NO; +} + +/* Find physical name and access protocol get_physical() + * -------------------------------------- + * + * On entry, + * addr must point to the fully qualified hypertext reference. + * anchor a parent anchor with whose address is addr + * + * On exit, + * returns HT_NO_ACCESS Error has occurred. + * HT_OK Success + */ +static int get_physical(const char *addr, + HTParentAnchor *anchor) +{ + int result; + char *acc_method = NULL; /* Name of access method */ + char *physical = NULL; + char *Server_addr = NULL; + BOOL override_flag = NO; + + CTRACE((tfp, "get_physical %s\n", addr)); + + /* + * Make sure the using_proxy variable is FALSE. + */ + using_proxy = NO; + +#ifndef NO_RULES + if ((physical = HTTranslate(addr)) == 0) { + if (redirecting_url) { + return HT_REDIRECTING; + } + return HT_FORBIDDEN; + } + if (anchor->isISMAPScript == TRUE) { + StrAllocCat(physical, "?0,0"); + CTRACE((tfp, "HTAccess: Appending '?0,0' coordinate pair.\n")); + } + if (!StrNCmp(physical, "Proxied=", 8)) { + HTAnchor_setPhysical(anchor, physical + 8); + using_proxy = YES; + } else if (!StrNCmp(physical, "NoProxy=", 8)) { + HTAnchor_setPhysical(anchor, physical + 8); + override_flag = YES; + } else { + HTAnchor_setPhysical(anchor, physical); + } + FREE(physical); /* free our copy */ +#else + if (anchor->isISMAPScript == TRUE) { + StrAllocCopy(physical, addr); + StrAllocCat(physical, "?0,0"); + CTRACE((tfp, "HTAccess: Appending '?0,0' coordinate pair.\n")); + HTAnchor_setPhysical(anchor, physical); + FREE(physical); /* free our copy */ + } else { + HTAnchor_setPhysical(anchor, addr); + } +#endif /* NO_RULES */ + + acc_method = HTParse(HTAnchor_physical(anchor), STR_FILE_URL, PARSE_ACCESS); + + /* + * Check whether gateway access has been set up for this. + * + * This function can be replaced by the rule system above. + * + * If the rule system has already determined that we should use a proxy, or + * that we shouldn't, ignore proxy-related settings, don't use no_proxy + * either. + */ +#define USE_GATEWAYS +#ifdef USE_GATEWAYS + + if (!override_flag && !using_proxy) { /* else ignore no_proxy env var */ + char *host = NULL; + int port; + + if (!strcasecomp(acc_method, "news")) { + /* + * News is different, so we need to check the name of the server, + * as well as the default port for selective exclusions. + */ + if ((host = HTParse(addr, "", PARSE_HOST))) { + if (HTParsePort(host, &port) == NULL) { + StrAllocCopy(Server_addr, "news://"); + StrAllocCat(Server_addr, host); + StrAllocCat(Server_addr, ":119/"); + } + FREE(host); + } else if (LYGetEnv("NNTPSERVER") != NULL) { + StrAllocCopy(Server_addr, "news://"); + StrAllocCat(Server_addr, LYGetEnv("NNTPSERVER")); + StrAllocCat(Server_addr, ":119/"); + } + } else if (!strcasecomp(acc_method, "wais")) { + /* + * Wais also needs checking of the default port for selective + * exclusions. + */ + if ((host = HTParse(addr, "", PARSE_HOST))) { + if (!(HTParsePort(host, &port))) { + StrAllocCopy(Server_addr, "wais://"); + StrAllocCat(Server_addr, host); + StrAllocCat(Server_addr, ":210/"); + } + FREE(host); + } else + StrAllocCopy(Server_addr, addr); + } else { + StrAllocCopy(Server_addr, addr); + } + override_flag = override_proxy(Server_addr); + } + + if (!override_flag && !using_proxy) { + char *gateway_parameter = NULL, *gateway, *proxy; + + /* + * Search for gateways. + */ + HTSprintf0(&gateway_parameter, "WWW_%s_GATEWAY", acc_method); + gateway = LYGetEnv(gateway_parameter); /* coerce for decstation */ + + /* + * Search for proxy servers. + */ + if (!strcmp(acc_method, "file")) + /* + * If we got to here, a file URL is for ftp on a remote host. - FM + */ + strcpy(gateway_parameter, "ftp_proxy"); + else + sprintf(gateway_parameter, "%s_proxy", acc_method); + proxy = LYGetEnv(gateway_parameter); + FREE(gateway_parameter); + + if (gateway) + CTRACE((tfp, "Gateway found: %s\n", gateway)); + if (proxy) + CTRACE((tfp, "proxy server found: %s\n", proxy)); + + /* + * Proxy servers have precedence over gateway servers. + */ + if (proxy) { + char *gatewayed = NULL; + + StrAllocCopy(gatewayed, proxy); + if (!StrNCmp(gatewayed, "http", 4)) { + char *cp = strrchr(gatewayed, '/'); + + /* Append a slash to the proxy specification if it doesn't + * end in one but otherwise looks normal (starts with "http", + * has no '/' other than ones before the hostname). - kw */ + if (cp && (cp - gatewayed) <= 7) + LYAddHtmlSep(&gatewayed); + } + /* + * Ensure that the proxy server uses ftp for file URLs. - FM + */ + if (!StrNCmp(addr, "file", 4)) { + StrAllocCat(gatewayed, "ftp"); + StrAllocCat(gatewayed, (addr + 4)); + } else + StrAllocCat(gatewayed, addr); + using_proxy = YES; + if (anchor->isISMAPScript == TRUE) + StrAllocCat(gatewayed, "?0,0"); + HTAnchor_setPhysical(anchor, gatewayed); + FREE(gatewayed); + FREE(acc_method); + + acc_method = HTParse(HTAnchor_physical(anchor), + STR_HTTP_URL, PARSE_ACCESS); + + } else if (gateway) { + char *path = HTParse(addr, "", + PARSE_HOST + PARSE_PATH + PARSE_PUNCTUATION); + + /* Chop leading / off to make host into part of path */ + char *gatewayed = HTParse(path + 1, gateway, PARSE_ALL); + + FREE(path); + HTAnchor_setPhysical(anchor, gatewayed); + FREE(gatewayed); + FREE(acc_method); + + acc_method = HTParse(HTAnchor_physical(anchor), + STR_HTTP_URL, PARSE_ACCESS); + } + } + FREE(Server_addr); +#endif /* use gateways */ + + /* + * Search registered protocols to find suitable one. + */ + result = HT_NO_ACCESS; + { + int i, n; + +#ifndef NO_INIT + if (!protocols) + HTAccessInit(); +#endif + n = HTList_count(protocols); + for (i = 0; i < n; i++) { + HTProtocol *p = (HTProtocol *) HTList_objectAt(protocols, i); + + if (!strcmp(p->name, acc_method)) { + HTAnchor_setProtocol(anchor, p); + FREE(acc_method); + result = HT_OK; + break; + } + } + } + + FREE(acc_method); + return result; +} + +/* + * Temporarily set the int UCLYhndl_for_unspec and string UCLYhndl_for_unspec + * used for charset "assuming" to the values implied by a HTParentAnchor's + * UCStages, after saving the current values for later restoration. - kw @@@ + * These functions may not really belong here, but where else? I want the + * "pop" to occur as soon as possible after loading has finished. - kw @@@ + */ +void LYUCPushAssumed(HTParentAnchor *anchor) +{ + int anchor_LYhndl = -1; + LYUCcharset *anchor_UCI = NULL; + + if (anchor) { + anchor_LYhndl = HTAnchor_getUCLYhndl(anchor, UCT_STAGE_PARSER); + if (anchor_LYhndl >= 0) + anchor_UCI = HTAnchor_getUCInfoStage(anchor, + UCT_STAGE_PARSER); + if (anchor_UCI && anchor_UCI->MIMEname) { + pushed_assume_MIMEname = UCAssume_MIMEcharset; + UCAssume_MIMEcharset = NULL; + if (HTCJK == JAPANESE) + StrAllocCopy(UCAssume_MIMEcharset, pushed_assume_MIMEname); + else + StrAllocCopy(UCAssume_MIMEcharset, anchor_UCI->MIMEname); + pushed_assume_LYhndl = anchor_LYhndl; + /* some diagnostics */ + if (UCLYhndl_for_unspec != anchor_LYhndl) + CTRACE((tfp, + "LYUCPushAssumed: UCLYhndl_for_unspec changed %d -> %d\n", + UCLYhndl_for_unspec, + anchor_LYhndl)); + UCLYhndl_for_unspec = anchor_LYhndl; + return; + } + } + pushed_assume_LYhndl = -1; + FREE(pushed_assume_MIMEname); +} + +/* + * Restore the int UCLYhndl_for_unspec and string UCLYhndl_for_unspec used for + * charset "assuming" from the values saved by LYUCPushAssumed, if any. - kw + */ +int LYUCPopAssumed(void) +{ + if (pushed_assume_LYhndl >= 0) { + /* some diagnostics */ + if (UCLYhndl_for_unspec != pushed_assume_LYhndl) + CTRACE((tfp, + "LYUCPopAssumed: UCLYhndl_for_unspec changed %d -> %d\n", + UCLYhndl_for_unspec, + pushed_assume_LYhndl)); + UCLYhndl_for_unspec = pushed_assume_LYhndl; + pushed_assume_LYhndl = -1; + FREE(UCAssume_MIMEcharset); + UCAssume_MIMEcharset = pushed_assume_MIMEname; + pushed_assume_MIMEname = NULL; + return UCLYhndl_for_unspec; + } + return -1; +} + +/* Load a document HTLoad() + * --------------- + * + * This is an internal routine, which has an address AND a matching + * anchor. (The public routines are called with one OR the other.) + * + * On entry, + * addr must point to the fully qualified hypertext reference. + * anchor a parent anchor with whose address is addr + * + * On exit, + * returns <0 Error has occurred. + * HT_LOADED Success + * HT_NO_DATA Success, but no document loaded. + * (telnet session started etc) + */ +static int HTLoad(const char *addr, + HTParentAnchor *anchor, + HTFormat format_out, + HTStream *sink) +{ + HTProtocol *p; + int status = get_physical(addr, anchor); + + if (reloading) { + FREE(anchor->charset); + FREE(anchor->UCStages); + } + + if (status == HT_FORBIDDEN) { + /* prevent crash if telnet or similar was forbidden by rule. - kw */ + LYFixCursesOn("show alert:"); + status = HTLoadError(sink, 500, gettext("Access forbidden by rule")); + } else if (status == HT_REDIRECTING) { + ; /* fake redirection by rule, to redirecting_url */ + } else if (status >= 0) { + /* prevent crash if telnet or similar mapped or proxied by rule. - kw */ + LYFixCursesOnForAccess(addr, HTAnchor_physical(anchor)); + p = (HTProtocol *) HTAnchor_protocol(anchor); + anchor->parent->underway = TRUE; /* Hack to deal with caching */ + status = p->load(HTAnchor_physical(anchor), + anchor, format_out, sink); + anchor->parent->underway = FALSE; + LYUCPopAssumed(); + } + return status; +} + +/* Get a save stream for a document HTSaveStream() + * -------------------------------- + */ +HTStream *HTSaveStream(HTParentAnchor *anchor) +{ + HTProtocol *p = (HTProtocol *) HTAnchor_protocol(anchor); + + if (!p) + return NULL; + + return p->saveStream(anchor); +} + +int redirection_limit = 10; +int redirection_attempts = 0; /* counter in HTLoadDocument */ + +static BOOL too_many_redirections(void) +{ + if (redirection_attempts > redirection_limit) { + char *msg = NULL; + + HTSprintf0(&msg, TOO_MANY_REDIRECTIONS, redirection_limit); + free(msg); + redirection_attempts = 0; + return TRUE; + } + return FALSE; +} + +/* Load a document - with logging etc HTLoadDocument() + * ---------------------------------- + * + * - Checks or documents already loaded + * - Logs the access + * - Allows stdin filter option + * - Trace output and error messages + * + * On Entry, + * anchor is the node_anchor for the document + * full_address The address of the document to be accessed. + * filter if YES, treat stdin as HTML + * + * On Exit, + * returns YES Success in opening document + * NO Failure + */ +static BOOL HTLoadDocument(const char *full_address, /* may include #fragment */ + HTParentAnchor *anchor, + HTFormat format_out, + HTStream *sink) +{ + int status; + HText *text; + const char *address_to_load = full_address; + char *cp; + BOOL ForcingNoCache = LYforce_no_cache; + + CTRACE((tfp, "HTAccess: loading document %s\n", NonNull(address_to_load))); + if (isEmpty(address_to_load)) + return NO; + + /* + * Free use_this_url_instead and reset permanent_redirection if not done + * elsewhere. - FM + */ + FREE(use_this_url_instead); + permanent_redirection = FALSE; + + if (too_many_redirections()) { + return NO; + } + + /* + * If this is marked as an internal link but we don't have the document + * loaded any more, and we haven't explicitly flagged that we want to + * reload with LYforce_no_cache, then something has disappeared from the + * cache when we expected it to be still there. The user probably doesn't + * expect a new network access. So if we have POST data and safe is not + * set in the anchor, ask for confirmation, and fail if not granted. The + * exception are LYNXIMGMAP documents, for which we defer to LYLoadIMGmap + * for prompting if necessary. - kw + */ + text = (HText *) HTAnchor_document(anchor); + if (LYinternal_flag && !text && !LYforce_no_cache && + anchor->post_data && !anchor->safe && + !isLYNXIMGMAP(full_address) && + HTConfirm(gettext("Document with POST content not found in cache. Resubmit?")) + != TRUE) { + return NO; + } + + /* + * If we don't have POST content, check whether this is a previous + * redirecting URL, and keep re-checking until we get to the final + * destination or redirection limit. If we do have POST content, we didn't + * allow permanent redirection, and an interactive user will be deciding + * whether to keep redirecting. - FM + */ + if (!anchor->post_data) { + while ((cp = HTAnchor_physical(anchor)) != NULL && + !StrNCmp(cp, "Location=", 9)) { + DocAddress NewDoc; + + CTRACE((tfp, "HTAccess: '%s' is a redirection URL.\n", + anchor->address)); + CTRACE((tfp, "HTAccess: Redirecting to '%s'\n", cp + 9)); + + /* + * Don't exceed the redirection_attempts limit. - FM + */ + ++redirection_attempts; + if (too_many_redirections()) { + FREE(use_this_url_instead); + return NO; + } + + /* + * Set up the redirection. - FM + */ + StrAllocCopy(use_this_url_instead, cp + 9); + NewDoc.address = use_this_url_instead; + NewDoc.post_data = NULL; + NewDoc.post_content_type = NULL; + NewDoc.bookmark = anchor->bookmark; + NewDoc.isHEAD = anchor->isHEAD; + NewDoc.safe = anchor->safe; + anchor = HTAnchor_findAddress(&NewDoc); + } + } + /* + * If we had previous redirection, go back and check out that the URL under + * the current restrictions. - FM + */ + if (use_this_url_instead) { + FREE(redirecting_url); + return (NO); + } + + /* + * See if we can use an already loaded document. + */ + text = (HText *) HTAnchor_document(anchor); + if (text && !LYforce_no_cache) { + /* + * We have a cached rendition of the target document. Check if it's OK + * to re-use it. We consider it OK if: + * (1) the anchor does not have the no_cache element set, or + * (2) we've overridden it, e.g., because we are acting on a PREV_DOC + * command or a link in the History Page and it's not a reply from a + * POST with the LYresubmit_posts flag set, or + * (3) we are repositioning within the currently loaded document based + * on the target anchor's address (URL_Reference). + * + * If track_internal_links is false, HText_AreDifferent() is + * used to determine whether (3) applies. If the target address + * differs from that of the current document only by a fragment and the + * target address has an appended fragment, repositioning without + * reloading is always assumed. Note that HText_AreDifferent() + * currently always returns TRUE if the target has a LYNXIMGMAP URL, so + * that an internally generated pseudo-document will normally not be + * re-used unless condition (2) applies. (Condition (1) cannot apply + * since in LYMap.c, no_cache is always set in the anchor object). + * This doesn't guarantee that the resource from which the MAP element + * is taken will be read again (reloaded) when the list of links for a + * client-side image map is regenerated, when in some cases it should + * (e.g., user requested RELOAD, or HTTP response with no-cache header + * and we are not overriding). + * + * If track_internal_links is true, a target address that + * points to the same URL as the current document may still result in + * reloading, depending on whether the original URL-Reference was given + * as an internal link in the context of the previously loaded + * document. HText_AreDifferent() is not used here for testing whether + * we are just repositioning. For an internal link, the potential + * callers of this function from mainloop() down will either avoid + * making the call (and do the repositioning differently) or set + * LYinternal_flag (or LYoverride_no_cache). Note that (a) LYNXIMGMAP + * pseudo-documents and (b) The "List Page" document are treated + * logically as being part of the document on which they are based, for + * the purpose of whether to treat a link as internal, but the logic + * for this (by setting LYinternal_flag as necessary) is implemented + * elsewhere. There is a specific test for LYNXIMGMAP here so that the + * generated pseudo-document will not be re-used unless + * LYoverride_no_cache is set. The same caveat as above applies w.r.t. + * reloading of the underlying resource. + * + * We also should be checking other aspects of cache regulation (e.g., + * based on an If-Modified-Since check, etc.) but the code for doing + * those other things isn't available yet. + */ + if ((reloading != REAL_RELOAD) && + (LYoverride_no_cache || + ((!track_internal_links && + (!HText_hasNoCacheSet(text) || + !HText_AreDifferent(anchor, full_address))) || + (track_internal_links && + (((LYinternal_flag || !HText_hasNoCacheSet(text)) && + !isLYNXIMGMAP(full_address))))))) { + CTRACE((tfp, "HTAccess: Document already in memory.\n")); + HText_select(text); + +#ifdef DIRED_SUPPORT + if (HTAnchor_format(anchor) == WWW_DIRED) + lynx_edit_mode = TRUE; +#endif + redirection_attempts = 0; + return YES; + } else { + ForcingNoCache = YES; + BStrFree(anchor->post_data); + CTRACE((tfp, "HTAccess: Auto-reloading document.\n")); + } + } + + if (HText_HaveUserChangedForms(text)) { + /* + * Issue a warning. User forms content will be lost. + * Will not restore changed forms, currently. + */ + HTAlert(RELOADING_FORM); + } + + /* + * Get the document from the net. If we are auto-reloading, the mutable + * anchor elements from the previous rendition should be freed in + * conjunction with loading of the new rendition. - FM + */ + LYforce_no_cache = NO; /* reset after each time through */ + if (ForcingNoCache) { + FREE(anchor->title); /* ??? */ + } + status = HTLoad(address_to_load, anchor, format_out, sink); + CTRACE((tfp, "HTAccess: status=%d\n", status)); + + /* + * RECOVERY: if the loading failed, and we had a cached HText copy, and no + * new HText created - use a previous copy, issue a warning. + */ + if (text && status < 0 && (HText *) HTAnchor_document(anchor) == text) { + HTAlert(gettext("Loading failed, use a previous copy.")); + CTRACE((tfp, "HTAccess: Loading failed, use a previous copy.\n")); + HText_select(text); + +#ifdef DIRED_SUPPORT + if (HTAnchor_format(anchor) == WWW_DIRED) + lynx_edit_mode = TRUE; +#endif + redirection_attempts = 0; + return YES; + } + + /* + * Log the access if necessary. + */ + if (HTlogfile) { + time_t theTime; + + time(&theTime); + fprintf(HTlogfile, "%24.24s %s %s %s\n", + ctime(&theTime), + HTClientHost ? HTClientHost : "local", + status < 0 ? "FAIL" : "GET", + full_address); + fflush(HTlogfile); /* Actually update it on disk */ + CTRACE((tfp, "Log: %24.24s %s %s %s\n", + ctime(&theTime), + HTClientHost ? HTClientHost : "local", + status < 0 ? "FAIL" : "GET", + full_address)); + } + + /* + * Check out what we received from the net. + */ + if (status == HT_REDIRECTING) { + /* Exported from HTMIME.c, of all places. */ + /* NO!! - FM */ + /* + * Doing this via HTMIME.c meant that the redirection cover page was + * already loaded before we learned that we want a different URL. + * Also, changing anchor->address, as Lynx was doing, meant we could + * never again access its hash table entry, creating an insolvable + * memory leak. Instead, if we had a 301 status and set + * permanent_redirection, we'll load the new URL in anchor->physical, + * preceded by a token, which we can check to make replacements on + * subsequent access attempts. We'll check recursively, and retrieve + * the final URL if we had multiple redirections to it. If we just + * went to HTLoad now, as Lou originally had this, we couldn't do + * Lynx's security checks and alternate handling of some URL types. + * So, instead, we'll go all the way back to the top of getfile in + * LYGetFile.c when the status is HT_REDIRECTING. This may seem + * bizarre, but it works like a charm! - FM + * + * Actually, the location header for redirections is now again picked + * up in HTMIME.c. But that's an internal matter between HTTP.c and + * HTMIME.c, is still under control of HTLoadHTTP for http URLs, is + * done in a way that doesn't load the redirection response's body + * (except when wanted as an error fallback), and thus need not concern + * us here. - kw 1999-12-02 + */ + CTRACE((tfp, "HTAccess: '%s' is a redirection URL.\n", + address_to_load)); + CTRACE((tfp, "HTAccess: Redirecting to '%s'\n", + redirecting_url)); + /* + * Prevent circular references. + */ + if (strcmp(address_to_load, redirecting_url)) { /* if different */ + /* + * Load token and redirecting url into anchor->physical if we had + * 301 Permanent redirection. HTTP.c does not allow this if we + * have POST content. - FM + */ + if (permanent_redirection) { + StrAllocCopy(anchor->physical, "Location="); + StrAllocCat(anchor->physical, redirecting_url); + } + + /* + * Set up flags before return to getfile. - FM + */ + StrAllocCopy(use_this_url_instead, redirecting_url); + if (ForcingNoCache) + LYforce_no_cache = YES; + ++redirection_attempts; + FREE(redirecting_url); + permanent_redirection = FALSE; + return (NO); + } + ++redirection_attempts; + FREE(redirecting_url); + permanent_redirection = FALSE; + return (YES); + } + + /* + * We did not receive a redirecting URL. - FM + */ + redirection_attempts = 0; + FREE(redirecting_url); + permanent_redirection = FALSE; + + if (status == HT_LOADED) { + CTRACE((tfp, "HTAccess: `%s' has been accessed.\n", + full_address)); + return YES; + } + if (status == HT_PARTIAL_CONTENT) { + HTAlert(gettext("Loading incomplete.")); + CTRACE((tfp, "HTAccess: `%s' has been accessed, partial content.\n", + full_address)); + return YES; + } + + if (status == HT_NO_DATA) { + CTRACE((tfp, "HTAccess: `%s' has been accessed, No data left.\n", + full_address)); + return NO; + } + + if (status == HT_NOT_LOADED) { + CTRACE((tfp, "HTAccess: `%s' has been accessed, No data loaded.\n", + full_address)); + return NO; + } + + if (status == HT_INTERRUPTED) { + CTRACE((tfp, + "HTAccess: `%s' has been accessed, transfer interrupted.\n", + full_address)); + return NO; + } + + if (status > 0) { + /* + * If you get this, then please find which routine is returning a + * positive unrecognized error code! + */ + fprintf(stderr, + gettext("**** HTAccess: socket or file number returned by obsolete load routine!\n")); + fprintf(stderr, + gettext("**** HTAccess: Internal software error. Please mail lynx-dev@nongnu.org!\n")); + fprintf(stderr, gettext("**** HTAccess: Status returned was: %d\n"), status); + exit_immediately(EXIT_FAILURE); + } + + /* Failure in accessing a document */ + cp = NULL; + StrAllocCopy(cp, gettext("Can't Access")); + StrAllocCat(cp, " `"); + StrAllocCat(cp, full_address); + StrAllocCat(cp, "'"); + _HTProgress(cp); + FREE(cp); + + CTRACE((tfp, "HTAccess: Can't access `%s'\n", full_address)); + HTLoadError(sink, 500, gettext("Unable to access document.")); + return NO; +} /* HTLoadDocument */ + +/* Load a document from absolute name. HTLoadAbsolute() + * ----------------------------------- + * + * On Entry, + * addr The absolute address of the document to be accessed. + * filter if YES, treat document as HTML + * + * On Exit, + * returns YES Success in opening document + * NO Failure + */ +BOOL HTLoadAbsolute(const DocAddress *docaddr) +{ + BOOL result; + HTParentAnchor *anchor = HTAnchor_findAddress(docaddr); + + result = HTLoadDocument(docaddr->address, + anchor, + (HTOutputFormat ? HTOutputFormat : WWW_PRESENT), + HTOutputStream); + if (!result) { + HTAnchor_delete(anchor->parent); + } + return result; +} + +/* Load a document from relative name. HTLoadRelative() + * ----------------------------------- + * + * On Entry, + * relative_name The relative address of the document + * to be accessed. + * + * On Exit, + * returns YES Success in opening document + * NO Failure + */ +BOOL HTLoadRelative(const char *relative_name, + HTParentAnchor *here) +{ + DocAddress full_address; + BOOL result; + char *mycopy = NULL; + char *stripped = NULL; + + full_address.address = NULL; + full_address.post_data = NULL; + full_address.post_content_type = NULL; + full_address.bookmark = NULL; + full_address.isHEAD = FALSE; + full_address.safe = FALSE; + + StrAllocCopy(mycopy, relative_name); + + stripped = HTStrip(mycopy); + full_address.address = + HTParse(stripped, + here->address, + PARSE_ALL_WITHOUT_ANCHOR); + result = HTLoadAbsolute(&full_address); + /* + * If we got redirection, result will be NO, but use_this_url_instead will + * be set. The calling routine should check both and do whatever is + * appropriate. - FM + */ + FREE(full_address.address); + FREE(mycopy); /* Memory leak fixed 10/7/92 -- JFG */ + return result; +} + +/* Search. HTSearch() + * ------- + * + * Performs a keyword search on word given by the user. Adds the + * keyword to the end of the current address and attempts to open + * the new address. + * + * On Entry, + * *keywords space-separated keyword list or similar search list + * here is anchor search is to be done on. + */ +static char hex(int i) +{ + const char *hexchars = "0123456789ABCDEF"; + + return hexchars[i]; +} + +BOOL HTSearch(const char *keywords, + HTParentAnchor *here) +{ +#define acceptable \ +"1234567890abcdefghijlkmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-_" + + char *q, *u; + const char *p, *s, *e; /* Pointers into keywords */ + char *address = NULL; + BOOL result; + char *escaped = typecallocn(char, (strlen(keywords) * 3) + 1); + static const BOOL isAcceptable[96] = + /* *INDENT-OFF* */ + /* 0 1 2 3 4 5 6 7 8 9 A B C D E F */ + { 0,0,0,0,0,0,0,0,0,0,1,0,0,1,1,0, /* 2x !"#$%&'()*+,-./ */ + 1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0, /* 3x 0123456789:;<=>? */ + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 4x @ABCDEFGHIJKLMNO */ + 1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,1, /* 5X PQRSTUVWXYZ[\]^_ */ + 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 6x `abcdefghijklmno */ + 1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0 }; /* 7X pqrstuvwxyz{\}~ DEL */ + /* *INDENT-ON* */ + + if (escaped == NULL) + outofmem(__FILE__, "HTSearch"); + + if (here->isIndexAction == NULL) { + free(escaped); + return FALSE; + } + StrAllocCopy(address, here->isIndexAction); + + /* + * Convert spaces to + and hex escape unacceptable characters. + */ + for (s = keywords; *s && WHITE(*s); s++) /* Scan */ + ; /* Skip white space */ + for (e = s + strlen(s); e > s && WHITE(*(e - 1)); e--) /* Scan */ + ; /* Skip trailers */ + for (q = escaped, p = s; p < e; p++) { /* Scan stripped field */ + unsigned char c = UCH(TOASCII(*p)); + + if (WHITE(*p)) { + *q++ = '+'; + } else if (IS_CJK_TTY) { + *q++ = *p; + } else if (c >= 32 && c <= UCH(127) && isAcceptable[c - 32]) { + *q++ = *p; /* 930706 TBL for MVS bug */ + } else { + *q++ = '%'; + *q++ = hex((int) (c >> 4)); + *q++ = hex((int) (c & 15)); + } + } /* Loop over string */ + *q = '\0'; /* Terminate escaped string */ + u = StrChr(address, '?'); /* Find old search string */ + if (u != NULL) + *u = '\0'; /* Chop old search off */ + + StrAllocCat(address, "?"); + StrAllocCat(address, escaped); + FREE(escaped); + result = HTLoadRelative(address, here); + FREE(address); + + /* + * If we got redirection, result will be NO, but use_this_url_instead will + * be set. The calling routine should check both and do whatever is + * appropriate. Only an http server (not a gopher or wais server) could + * return redirection. Lynx will go all the way back to its mainloop() and + * subject a redirecting URL to all of its security and restrictions + * checks. - FM + */ + return result; +} + +/* Search Given Indexname. HTSearchAbsolute() + * ----------------------- + * + * Performs a keyword search on word given by the user. Adds the + * keyword to the end of the current address and attempts to open + * the new address. + * + * On Entry, + * *keywords space-separated keyword list or similar search list + * *indexname is name of object search is to be done on. + */ +BOOL HTSearchAbsolute(const char *keywords, + char *indexname) +{ + DocAddress abs_doc; + HTParentAnchor *anchor; + + abs_doc.address = indexname; + abs_doc.post_data = NULL; + abs_doc.post_content_type = NULL; + abs_doc.bookmark = NULL; + abs_doc.isHEAD = FALSE; + abs_doc.safe = FALSE; + + anchor = HTAnchor_findAddress(&abs_doc); + return HTSearch(keywords, anchor); +} diff --git a/WWW/Library/Implementation/HTAccess.h b/WWW/Library/Implementation/HTAccess.h new file mode 100644 index 0000000..5ee5ba9 --- /dev/null +++ b/WWW/Library/Implementation/HTAccess.h @@ -0,0 +1,236 @@ +/* + * $LynxId: HTAccess.h,v 1.22 2024/01/10 23:54:11 tom Exp $ + * HTAccess: Access manager for libwww + * ACCESS MANAGER + * + * This module keeps a list of valid protocol (naming scheme) specifiers with + * associated access code. It allows documents to be loaded given various + * combinations of parameters. New access protocols may be registered at any + * time. + * + * Part of the libwww library . + * + */ +#ifndef HTACCESS_H +#define HTACCESS_H + +/* Definition uses: +*/ +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + extern char *use_this_url_instead; + + extern int redirection_limit; + extern int redirection_attempts; + +/* Return codes from load routines: + * + * These codes may be returned by the protocol modules, + * and by the HTLoad routines. + * In general, positive codes are OK and negative ones are bad. + */ + +/* + +Default Addresses + + These control the home page selection. To mess with these for normal browses is asking + for user confusion. + + */ +#define LOGICAL_DEFAULT "WWW_HOME" /* Defined to be the home page */ + +#ifndef PERSONAL_DEFAULT +#define PERSONAL_DEFAULT "WWW/default.html" /* in home directory */ +#endif +#ifndef LOCAL_DEFAULT_FILE +#define LOCAL_DEFAULT_FILE "/usr/local/lib/WWW/default.html" +#endif +/* If one telnets to a www access point, + it will look in this file for home page */ +#ifndef REMOTE_POINTER +#define REMOTE_POINTER "/etc/www-remote.url" /* can't be file */ +#endif +/* and if that fails it will use this. */ +#ifndef REMOTE_ADDRESS +#define REMOTE_ADDRESS "http://www.w3.org/remote.html" /* can't be file */ +#endif + +/* If run from telnet daemon and no -l specified, use this file: +*/ +#ifndef DEFAULT_LOGFILE +#define DEFAULT_LOGFILE "/usr/adm/www-log/www-log" +#endif + +/* If the home page isn't found, use this file: +*/ +#ifndef LAST_RESORT +#define LAST_RESORT "http://www.w3.org/default.html" +#endif + +/* + +Flags which may be set to control this module + + */ +#ifdef NOT + extern int HTDiag; /* Flag: load source as plain text */ +#endif /* NOT */ + extern char *HTClientHost; /* Name or number of telnetting host */ + extern FILE *HTlogfile; /* File to output one-liners to */ + extern BOOL HTSecure; /* Disable security holes? */ + extern BOOL HTPermitRedir; /* Special flag for getfile() */ + extern HTStream *HTOutputStream; /* For non-interactive, set this */ + extern HTFormat HTOutputFormat; /* To convert on load, set this */ + +/* Check for proxy override. override_proxy() + * + * Check the no_proxy environment variable to get the list + * of hosts for which proxy server is not consulted. + * + * no_proxy is a comma- or space-separated list of machine + * or domain names, with optional :port part. If no :port + * part is present, it applies to all ports on that domain. + * + * Example: + * no_proxy="cern.ch,some.domain:8001" + * + * Use "*" to override all proxy service: + * no_proxy="*" + */ + extern BOOL override_proxy(const char *addr); + +/* + +Load a document from relative name + + ON ENTRY, + relative_name The relative address of the file to be accessed. + here The anchor of the object being searched + + ON EXIT, + returns YES Success in opening file + NO Failure + + */ + extern BOOL HTLoadRelative(const char *relative_name, + HTParentAnchor *here); + +/* + +Load a document from absolute name + + ON ENTRY, + addr The absolute address of the document to be accessed. + filter_it if YES, treat document as HTML + + ON EXIT, + returns YES Success in opening document + NO Failure + + */ + extern BOOL HTLoadAbsolute(const DocAddress *addr); + +/* + +Make a stream for Saving object back + + ON ENTRY, + anchor is valid anchor which has previously been loaded + + ON EXIT, + returns 0 if error else a stream to save the object to. + + */ + extern HTStream *HTSaveStream(HTParentAnchor *anchor); + +/* + +Search + + Performs a search on word given by the user. Adds the search words to the end of the + current address and attempts to open the new address. + + ON ENTRY, + *keywords space-separated keyword list or similar search list + here The anchor of the object being searched + + */ + extern BOOL HTSearch(const char *keywords, HTParentAnchor *here); + +/* + +Search Given Indexname + + Performs a keyword search on word given by the user. Adds the keyword to the end of + the current address and attempts to open the new address. + + ON ENTRY, + *keywords space-separated keyword list or similar search list + *indexname is name of object search is to be done on. + + */ + extern BOOL HTSearchAbsolute(const char *keywords, + char *indexname); + +/* + +Register an access method + + */ + + typedef struct _HTProtocol { + const char *name; + + int (*load) (const char *full_address, + HTParentAnchor *anchor, + HTFormat format_out, + HTStream *sink); + + HTStream *(*saveStream) (HTParentAnchor *anchor); + + } HTProtocol; + + extern BOOL HTRegisterProtocol(HTProtocol * protocol); + +/* + +Generate the anchor for the home page + + */ + +/* + + As it involves file access, this should only be done once when the program first runs. + This is a default algorithm -- browser don't HAVE to use this. + + */ + extern HTParentAnchor *HTHomeAnchor(void); + +/* + +Return Host Name + + */ + extern const char *HTHostName(void); + +/* + +For registering protocols supported by Lynx + +*/ + extern void LYRegisterLynxProtocols(void); + + extern void LYUCPushAssumed(HTParentAnchor *anchor); + extern int LYUCPopAssumed(void); + + extern BOOL using_proxy; /* Are we using an NNTP proxy? */ + +#ifdef __cplusplus +} +#endif +#endif /* HTACCESS_H */ diff --git a/WWW/Library/Implementation/HTAnchor.c b/WWW/Library/Implementation/HTAnchor.c new file mode 100644 index 0000000..b5ca251 --- /dev/null +++ b/WWW/Library/Implementation/HTAnchor.c @@ -0,0 +1,1460 @@ +/* + * $LynxId: HTAnchor.c,v 1.82 2020/01/21 21:58:52 tom Exp $ + * + * Hypertext "Anchor" Object HTAnchor.c + * ========================== + * + * An anchor represents a region of a hypertext document which is linked to + * another anchor in the same or a different document. + * + * History + * + * Nov 1990 Written in Objective-C for the NeXT browser (TBL) + * 24-Oct-1991 (JFG), written in C, browser-independent + * 21-Nov-1991 (JFG), first complete version + * + * (c) Copyright CERN 1991 - See Copyright.html + */ + +#define HASH_SIZE 997 /* Arbitrary prime. Memory/speed tradeoff */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define HASH_OF(h, v) ((HASH_TYPE)((h) * 3 + UCH(v)) % HASH_SIZE) + +static HASH_TYPE anchor_hash(const char *cp_address) +{ + HASH_TYPE hash; + const char *p; + + for (p = cp_address, hash = 0; *p; p++) + hash = HASH_OF(hash, *p); + + return (hash); +} + +typedef struct _HyperDoc Hyperdoc; + +#ifdef VMS +struct _HyperDoc { + int junk; /* VMS cannot handle pointers to undefined structs */ +}; +#endif /* VMS */ + +/* Table of lists of all parents */ +static HTList adult_table[HASH_SIZE] = +{ + {NULL, NULL}}; + +/* Creation Methods + * ================ + * + * Do not use "new" by itself outside this module. In order to enforce + * consistency, we insist that you furnish more information about the + * anchor you are creating : use newWithParent or newWithAddress. + */ +static HTParentAnchor0 *HTParentAnchor0_new(const char *address, + unsigned hash) +{ + HTParentAnchor0 *newAnchor = typecalloc(HTParentAnchor0); + + if (newAnchor == NULL) + outofmem(__FILE__, "HTParentAnchor0_new"); + + newAnchor->parent = newAnchor; /* self */ + StrAllocCopy(newAnchor->address, address); + newAnchor->adult_hash = (HASH_TYPE) hash; + + return (newAnchor); +} + +static HTParentAnchor *HTParentAnchor_new(HTParentAnchor0 *parent) +{ + HTParentAnchor *newAnchor = typecalloc(HTParentAnchor); + + if (newAnchor == NULL) + outofmem(__FILE__, "HTParentAnchor_new"); + + newAnchor->parent = parent; /* cross reference */ + parent->info = newAnchor; /* cross reference */ + newAnchor->address = parent->address; /* copy pointer */ + + newAnchor->isISMAPScript = FALSE; /* Lynx appends ?0,0 if TRUE. - FM */ + newAnchor->isHEAD = FALSE; /* HEAD request if TRUE. - FM */ + newAnchor->safe = FALSE; /* Safe. - FM */ + newAnchor->no_cache = FALSE; /* no-cache? - FM */ + newAnchor->inBASE = FALSE; /* duplicated from HTML.c/h */ + newAnchor->content_length = 0; /* Content-Length. - FM */ + return (newAnchor); +} + +static HTChildAnchor *HTChildAnchor_new(HTParentAnchor0 *parent) +{ + HTChildAnchor *p = typecalloc(HTChildAnchor); + + if (p == NULL) + outofmem(__FILE__, "HTChildAnchor_new"); + + p->parent = parent; /* parent reference */ + return p; +} + +static HTChildAnchor *HText_pool_ChildAnchor_new(HTParentAnchor *parent) +{ + HTChildAnchor *p = (HTChildAnchor *) HText_pool_calloc((HText *) (parent->document), + (unsigned) sizeof(HTChildAnchor)); + + if (p == NULL) + outofmem(__FILE__, "HText_pool_ChildAnchor_new"); + + p->parent = parent->parent; /* parent reference */ + return p; +} + +#ifdef CASE_INSENSITIVE_ANCHORS +/* Case insensitive string comparison */ +#define HT_EQUIV(a,b) (TOUPPER(a) == TOUPPER(b)) +#else +/* Case sensitive string comparison */ +#define HT_EQUIV(a,b) ((a) == (b)) +#endif + +/* Null-terminated string comparison + * --------------------------------- + * On entry, + * s Points to one string, null terminated + * t points to the other. + * On exit, + * returns YES if the strings are equivalent + * NO if they differ. + */ +static BOOL HTSEquivalent(const char *s, + const char *t) +{ + if (s && t) { /* Make sure they point to something */ + for (; *s && *t; s++, t++) { + if (!HT_EQUIV(*s, *t)) { + return (NO); + } + } + return (BOOL) (HT_EQUIV(*s, *t)); + } else { + return (BOOL) (s == t); /* Two NULLs are equivalent, aren't they ? */ + } +} + +/* Binary string comparison + * ------------------------ + * On entry, + * s Points to one bstring + * t points to the other. + * On exit, + * returns YES if the strings are equivalent + * NO if they differ. + */ +static BOOL HTBEquivalent(const bstring *s, + const bstring *t) +{ + if (s && t && BStrLen(s) == BStrLen(t)) { + int j; + int len = BStrLen(s); + + for (j = 0; j < len; ++j) { + if (!HT_EQUIV(BStrData(s)[j], BStrData(t)[j])) { + return (NO); + } + } + return (YES); + } else { + return (BOOL) (s == t); /* Two NULLs are equivalent, aren't they ? */ + } +} + +/* + * Three-way compare function + */ +static int compare_anchors(void *l, + void *r) +{ + const char *a = ((HTChildAnchor *) l)->tag; + const char *b = ((HTChildAnchor *) r)->tag; + + /* both tags are not NULL */ + +#ifdef CASE_INSENSITIVE_ANCHORS + return strcasecomp(a, b); /* Case insensitive */ +#else + return strcmp(a, b); /* Case sensitive - FM */ +#endif /* CASE_INSENSITIVE_ANCHORS */ +} + +/* Create new or find old sub-anchor + * --------------------------------- + * + * This one is for a named child. + * The parent anchor must already exist. + */ +static HTChildAnchor *HTAnchor_findNamedChild(HTParentAnchor0 *parent, + const char *tag) +{ + HTChildAnchor *child; + + if (parent && tag && *tag) { /* TBL */ + if (parent->children) { + /* + * Parent has children. Search them. + */ + HTChildAnchor sample; + + sample.tag = DeConst(tag); /* for compare_anchors() only */ + + child = (HTChildAnchor *) HTBTree_search(parent->children, &sample); + if (child != NULL) { + CTRACE((tfp, + "Child anchor %p of parent %p with name `%s' already exists.\n", + (void *) child, (void *) parent, tag)); + return (child); + } + } else { /* parent doesn't have any children yet : create family */ + parent->children = HTBTree_new(compare_anchors); + } + + child = HTChildAnchor_new(parent); + CTRACE((tfp, "HTAnchor: New Anchor %p named `%s' is child of %p\n", + (void *) child, + NonNull(tag), + (void *) child->parent)); + + StrAllocCopy(child->tag, tag); /* should be set before HTBTree_add */ + HTBTree_add(parent->children, child); + return (child); + + } else { + CTRACE((tfp, "HTAnchor_findNamedChild called with NULL parent.\n")); + return (NULL); + } + +} + +/* + * This one is for a new unnamed child being edited into an existing + * document. The parent anchor and the document must already exist. + * (Just add new unnamed child). + */ +static HTChildAnchor *HTAnchor_addChild(HTParentAnchor *parent) +{ + HTChildAnchor *child; + + if (!parent) { + CTRACE((tfp, "HTAnchor_addChild called with NULL parent.\n")); + return (NULL); + } + + child = HText_pool_ChildAnchor_new(parent); + CTRACE((tfp, "HTAnchor: New unnamed Anchor %p is child of %p\n", + (void *) child, + (void *) child->parent)); + + child->tag = 0; + HTList_linkObject(&parent->children_notag, child, &child->_add_children_notag); + + return (child); +} + +static HTParentAnchor0 *HTAnchor_findAddress_in_adult_table(const DocAddress *newdoc); + +static BOOL HTAnchor_link(HTChildAnchor *child, + HTAnchor * destination, + HTLinkType *type); + +/* Create or find a child anchor with a possible link + * -------------------------------------------------- + * + * Create new anchor with a given parent and possibly + * a name, and possibly a link to a _relatively_ named anchor. + * (Code originally in ParseHTML.h) + */ +HTChildAnchor *HTAnchor_findChildAndLink(HTParentAnchor *parent, /* May not be 0 */ + const char *tag, /* May be "" or 0 */ + const char *href, /* May be "" or 0 */ + HTLinkType *ltype) /* May be 0 */ +{ + HTChildAnchor *child; + + CTRACE((tfp, "Entered HTAnchor_findChildAndLink: tag=`%s',%s href=`%s'\n", + NonNull(tag), + (ltype == HTInternalLink) ? " (internal link)" : "", + NonNull(href))); + + if (parent == 0) { + child = 0; + } else { + if (non_empty(tag)) { + child = HTAnchor_findNamedChild(parent->parent, tag); + } else { + child = HTAnchor_addChild(parent); + } + + if (non_empty(href)) { + const char *fragment = NULL; + HTParentAnchor0 *dest; + + if (ltype == HTInternalLink && *href == '#') { + dest = parent->parent; + } else { + const char *relative_to = ((parent->inBASE && *href != '#') + ? parent->content_base + : parent->address); + DocAddress parsed_doc; + + parsed_doc.address = HTParse(href, relative_to, + PARSE_ALL_WITHOUT_ANCHOR); + + parsed_doc.post_data = NULL; + parsed_doc.post_content_type = NULL; + if (ltype && parent->post_data && ltype == HTInternalLink) { + /* for internal links, find a destination with the same + post data if the source of the link has post data. - kw + Example: LYNXIMGMAP: */ + parsed_doc.post_data = parent->post_data; + parsed_doc.post_content_type = parent->post_content_type; + } + parsed_doc.bookmark = NULL; + parsed_doc.isHEAD = FALSE; + parsed_doc.safe = FALSE; + + dest = HTAnchor_findAddress_in_adult_table(&parsed_doc); + FREE(parsed_doc.address); + } + + /* + * [from HTAnchor_findAddress()] + * If the address represents a sub-anchor, we load its parent (above), + * then we create a named child anchor within that parent. + */ + fragment = (*href == '#') ? href + 1 : HTParseAnchor(href); + + if (*fragment) + dest = (HTParentAnchor0 *) HTAnchor_findNamedChild(dest, fragment); + + if (tag && *tag) { + if (child->dest) { /* DUPLICATE_ANCHOR_NAME_WORKAROUND - kw */ + CTRACE((tfp, + "*** Duplicate ChildAnchor %p named `%s'", + (void *) child, tag)); + if ((HTAnchor *) dest != child->dest || ltype != child->type) { + CTRACE((tfp, + ", different dest %p or type, creating unnamed child\n", + (void *) child->dest)); + child = HTAnchor_addChild(parent); + } + } + } + HTAnchor_link(child, (HTAnchor *) dest, ltype); + } + } + return child; +} + +/* Create new or find old parent anchor + * ------------------------------------ + * + * Me one is for a reference which is found in a document, and might + * not be already loaded. + * Note: You are not guaranteed a new anchor -- you might get an old one, + * like with fonts. + */ +HTParentAnchor *HTAnchor_findAddress(const DocAddress *newdoc) +{ + /* Anchor tag specified ? */ + const char *tag = HTParseAnchor(newdoc->address); + + CTRACE((tfp, "Entered HTAnchor_findAddress\n")); + + /* + * If the address represents a sub-anchor, we load its parent, then we + * create a named child anchor within that parent. + */ + if (*tag) { + DocAddress parsed_doc; + HTParentAnchor0 *foundParent; + + parsed_doc.address = HTParse(newdoc->address, "", + PARSE_ALL_WITHOUT_ANCHOR); + parsed_doc.post_data = newdoc->post_data; + parsed_doc.post_content_type = newdoc->post_content_type; + parsed_doc.bookmark = newdoc->bookmark; + parsed_doc.isHEAD = newdoc->isHEAD; + parsed_doc.safe = newdoc->safe; + + foundParent = HTAnchor_findAddress_in_adult_table(&parsed_doc); + (void) HTAnchor_findNamedChild(foundParent, tag); + FREE(parsed_doc.address); + return HTAnchor_parent((HTAnchor *) foundParent); + } + return HTAnchor_parent((HTAnchor *) HTAnchor_findAddress_in_adult_table(newdoc)); +} + +/* The address has no anchor tag, for sure. + */ +static HTParentAnchor0 *HTAnchor_findAddress_in_adult_table(const DocAddress *newdoc) +{ + /* + * Check whether we have this node. + */ + HASH_TYPE hash; + HTList *adults; + HTList *grownups; + HTParentAnchor0 *foundAnchor; + BOOL need_extra_info = (BOOL) (newdoc->post_data || + newdoc->post_content_type || + newdoc->bookmark || + newdoc->isHEAD || + newdoc->safe); + + /* + * We need not free adult_table[] atexit - it should be perfectly empty + * after free'ing all HText's. (There is an error if it is not empty at + * exit). -LP + */ + + /* + * Select list from hash table, + */ + hash = anchor_hash(newdoc->address); + adults = &(adult_table[hash]); + + /* + * Search list for anchor. + */ + grownups = adults; + while (NULL != (foundAnchor = + (HTParentAnchor0 *) HTList_nextObject(grownups))) { + if (HTSEquivalent(foundAnchor->address, newdoc->address) && + + ((!foundAnchor->info && !need_extra_info) || + (foundAnchor->info && + HTBEquivalent(foundAnchor->info->post_data, newdoc->post_data) && + foundAnchor->info->isHEAD == newdoc->isHEAD))) { + CTRACE((tfp, "Anchor %p with address `%s' already exists.\n", + (void *) foundAnchor, newdoc->address)); + return foundAnchor; + } + } + + /* + * Node not found: create new anchor. + */ + foundAnchor = HTParentAnchor0_new(newdoc->address, hash); + CTRACE((tfp, "New anchor %p has hash %d and address `%s'\n", + (void *) foundAnchor, hash, newdoc->address)); + + if (need_extra_info) { + /* rare case, create a big structure */ + HTParentAnchor *p = HTParentAnchor_new(foundAnchor); + + if (newdoc->post_data) + BStrCopy(p->post_data, newdoc->post_data); + if (newdoc->post_content_type) + StrAllocCopy(p->post_content_type, + newdoc->post_content_type); + if (newdoc->bookmark) + StrAllocCopy(p->bookmark, newdoc->bookmark); + p->isHEAD = newdoc->isHEAD; + p->safe = newdoc->safe; + } + HTList_linkObject(adults, foundAnchor, &foundAnchor->_add_adult); + + return foundAnchor; +} + +/* Create new or find old named anchor - simple form + * ------------------------------------------------- + * + * Like HTAnchor_findAddress, but simpler to use for simple cases. + * No post data etc. can be supplied. - kw + */ +HTParentAnchor *HTAnchor_findSimpleAddress(const char *url) +{ + DocAddress urldoc; + + urldoc.address = DeConst(url); /* ignore warning, it IS treated like const - kw */ + urldoc.post_data = NULL; + urldoc.post_content_type = NULL; + urldoc.bookmark = NULL; + urldoc.isHEAD = FALSE; + urldoc.safe = FALSE; + return HTAnchor_findAddress(&urldoc); +} + +/* Link me Anchor to another given one + * ------------------------------------- + */ +static BOOL HTAnchor_link(HTChildAnchor *child, + HTAnchor * destination, + HTLinkType *type) +{ + if (!(child && destination)) + return (NO); /* Can't link to/from non-existing anchor */ + + CTRACE((tfp, "Linking child %p to anchor %p\n", (void *) child, (void *) destination)); + if (child->dest) { + CTRACE((tfp, "*** child anchor already has destination, exiting!\n")); + return (NO); + } + + child->dest = destination; + child->type = type; + + if (child->parent != destination->parent) + /* link only foreign children */ + HTList_linkObject(&destination->parent->sources, child, &child->_add_sources); + + return (YES); /* Success */ +} + +/* Delete an anchor and possibly related things (auto garbage collection) + * -------------------------------------------- + * + * The anchor is only deleted if the corresponding document is not loaded. + * All outgoing links from children are deleted, and children are + * removed from the sources lists of theirs targets. + * We also try to delete the targets whose documents are not loaded. + * If this anchor's sources list is empty, we delete it and its children. + */ + +/* + * Recursively try to delete destination anchor of this child. + * In any event, this will tell destination anchor that we + * no longer consider it a destination. + */ +static void deleteLinks(HTChildAnchor *me) +{ + /* + * Unregister me with our destination anchor's parent. + */ + if (me->dest) { + HTParentAnchor0 *parent = me->dest->parent; + + /* + * Start. Set the dest pointer to zero. + */ + me->dest = NULL; + + /* + * Remove me from the parent's sources so that the parent knows one + * less anchor is its dest. + */ + if ((me->parent != parent) && !HTList_isEmpty(&parent->sources)) { + /* + * Really should only need to deregister once. + */ + HTList_unlinkObject(&parent->sources, (void *) me); + } + + /* + * Recursive call. Test here to avoid calling overhead. Don't delete + * if document is loaded or being loaded. + */ + if ((me->parent != parent) && + parent != NULL && + !parent->underway && + (!parent->info || !parent->info->document)) { + HTAnchor_delete(parent); + } + + /* + * At this point, we haven't a destination. Set it to be so. Leave + * the HTAtom pointed to by type up to other code to handle (reusable, + * near static). + */ + me->type = NULL; + } +} + +static void HTParentAnchor_free(HTParentAnchor *me); + +BOOL HTAnchor_delete(HTParentAnchor0 *me) +{ + /* + * Memory leaks fixed. + * 05-27-94 Lynx 2-3-1 Garrett Arch Blythe + */ + HTBTElement *ele; + HTChildAnchor *child; + + /* + * Do nothing if nothing to do. + */ + if (!me) { + return (NO); + } + + /* + * Don't delete if document is loaded or being loaded. + */ + if (me->underway || (me->info && me->info->document)) { + return (NO); + } + + /* + * Mark ourselves busy, so that recursive calls of this function on this + * HTParentAnchor0 will not free it from under our feet. - kw + */ + me->underway = TRUE; + + { + /* + * Delete all outgoing links from named children. Do not delete named + * children itself (may have incoming links). + */ + if (me->children) { + ele = HTBTree_next(me->children, NULL); + while (ele != NULL) { + child = (HTChildAnchor *) HTBTree_object(ele); + if (child->dest) + deleteLinks(child); + ele = HTBTree_next(me->children, ele); + } + } + } + me->underway = FALSE; + + /* + * There are still incoming links to this one (we are the + * destination of another anchor). + */ + if (!HTList_isEmpty(&me->sources)) { + /* + * Can't delete parent, still have sources. + */ + return (NO); + } + + /* + * No more incoming and outgoing links : kill everything First, delete + * named children. + */ + if (me->children) { + ele = HTBTree_next(me->children, NULL); + while (ele != NULL) { + child = (HTChildAnchor *) HTBTree_object(ele); + FREE(child->tag); + FREE(child); + ele = HTBTree_next(me->children, ele); + } + HTBTree_free(me->children); + } + + /* + * Delete the ParentAnchor, if any. (Document was already deleted). + */ + if (me->info) { + HTParentAnchor_free(me->info); + FREE(me->info); + } + + /* + * Remove ourselves from the hash table's list. + */ + HTList_unlinkObject(&(adult_table[me->adult_hash]), (void *) me); + + /* + * Free the address. + */ + FREE(me->address); + + /* + * Finally, kill the parent anchor passed in. + */ + FREE(me); + + return (YES); +} + +/* + * Unnamed children (children_notag) have no sense without HText - delete them + * and their links if we are about to free HText. Document currently exists. + * Called within HText_free(). + */ +void HTAnchor_delete_links(HTParentAnchor *me) +{ + HTList *cur; + HTChildAnchor *child; + + /* + * Do nothing if nothing to do. + */ + if (!me || !me->document) { + return; + } + + /* + * Mark ourselves busy, so that recursive calls on this HTParentAnchor0 + * will not free it from under our feet. - kw + */ + me->parent->underway = TRUE; + + /* + * Delete all outgoing links from unnamed children. + */ + if (!HTList_isEmpty(&me->children_notag)) { + cur = &me->children_notag; + while ((child = + (HTChildAnchor *) HTList_unlinkLastObject(cur)) != 0) { + deleteLinks(child); + /* child allocated in HText pool, HText_free() will free it later */ + } + } + me->parent->underway = FALSE; +} + +static void HTParentAnchor_free(HTParentAnchor *me) +{ + /* + * Delete the methods list. + */ + if (me->methods) { + /* + * Leave what the methods point to up in memory for other code (near + * static stuff). + */ + HTList_delete(me->methods); + me->methods = NULL; + } + + /* + * Free up all allocated members. + */ + FREE(me->charset); + FREE(me->isIndexAction); + FREE(me->isIndexPrompt); + FREE(me->title); + FREE(me->physical); + BStrFree(me->post_data); + FREE(me->post_content_type); + FREE(me->bookmark); + FREE(me->owner); + FREE(me->RevTitle); + FREE(me->citehost); +#ifdef USE_SOURCE_CACHE + HTAnchor_clearSourceCache(me); +#endif + if (me->FileCache) { + FILE *fd; + + if ((fd = fopen(me->FileCache, "r")) != NULL) { + fclose(fd); + (void) remove(me->FileCache); + } + FREE(me->FileCache); + } + FREE(me->SugFname); + FREE(me->cache_control); + HTChunkClear(&(me->http_headers)); + FREE(me->content_type_params); + FREE(me->content_type); + FREE(me->content_language); + FREE(me->content_encoding); + FREE(me->content_base); + FREE(me->content_disposition); + FREE(me->content_location); + FREE(me->content_md5); + FREE(me->message_id); + FREE(me->subject); + FREE(me->date); + FREE(me->expires); + + FREE(me->last_modified); + FREE(me->ETag); + FREE(me->server); +#ifdef USE_COLOR_STYLE + FREE(me->style); +#endif + + /* + * Original code wanted a way to clean out the HTFormat if no longer needed + * (ref count?). I'll leave it alone since those HTAtom objects are a + * little harder to know where they are being referenced all at one time. + * (near static) + */ + + FREE(me->UCStages); + ImageMapList_free(me->imaps); +} + +#ifdef USE_SOURCE_CACHE +void HTAnchor_clearSourceCache(HTParentAnchor *me) +{ + /* + * Clean up the source cache, if any. + */ + if (me->source_cache_file) { + CTRACE((tfp, "SourceCache: Removing file %s\n", + me->source_cache_file)); + (void) LYRemoveTemp(me->source_cache_file); + FREE(me->source_cache_file); + } + if (me->source_cache_chunk) { + CTRACE((tfp, "SourceCache: Removing memory chunk %p\n", + (void *) me->source_cache_chunk)); + HTChunkFree(me->source_cache_chunk); + me->source_cache_chunk = NULL; + } +} +#endif /* USE_SOURCE_CACHE */ + +/* Data access functions + * --------------------- + */ +HTParentAnchor *HTAnchor_parent(HTAnchor * me) +{ + if (!me) + return NULL; + + if (me->parent->info) + return me->parent->info; + + /* else: create a new structure */ + return HTParentAnchor_new(me->parent); +} + +void HTAnchor_setDocument(HTParentAnchor *me, + HyperDoc *doc) +{ + if (me) + me->document = doc; +} + +HyperDoc *HTAnchor_document(HTParentAnchor *me) +{ + return (me ? me->document : NULL); +} + +char *HTAnchor_address(HTAnchor * me) +{ + char *addr = NULL; + + if (me) { + if (((HTParentAnchor0 *) me == me->parent) || + ((HTParentAnchor *) me == me->parent->info) || + !((HTChildAnchor *) me)->tag) { /* it's an adult or no tag */ + StrAllocCopy(addr, me->parent->address); + } else { /* it's a named child */ + HTSprintf0(&addr, "%s#%s", + me->parent->address, ((HTChildAnchor *) me)->tag); + } + } + return (addr); +} + +char *HTAnchor_short_address(HTAnchor * me) +{ + const char chop[] = "file://localhost/"; + char *addr = HTAnchor_address(me); + + if (!strncmp(addr, chop, sizeof(chop) - 1)) { + char *a = addr + 7; + char *b = addr + sizeof(chop) - 2; + + while ((*a++ = *b++) != '\0') { + ; + } + } + return addr; +} + +void HTAnchor_setFormat(HTParentAnchor *me, + HTFormat form) +{ + if (me) + me->format = form; +} + +HTFormat HTAnchor_format(HTParentAnchor *me) +{ + return (me ? me->format : NULL); +} + +void HTAnchor_setIndex(HTParentAnchor *me, + const char *address) +{ + if (me) { + me->isIndex = YES; + StrAllocCopy(me->isIndexAction, address); + } +} + +void HTAnchor_setPrompt(HTParentAnchor *me, + const char *prompt) +{ + if (me) { + StrAllocCopy(me->isIndexPrompt, prompt); + } +} + +BOOL HTAnchor_isIndex(HTParentAnchor *me) +{ + return (BOOL) (me + ? me->isIndex + : NO); +} + +/* Whether Anchor has been designated as an ISMAP link + * (normally by presence of an ISMAP attribute on A or IMG) - KW + */ +BOOL HTAnchor_isISMAPScript(HTAnchor * me) +{ + return (BOOL) ((me && me->parent->info) + ? me->parent->info->isISMAPScript + : NO); +} + +#if defined(USE_COLOR_STYLE) +/* Style handling. +*/ +const char *HTAnchor_style(HTParentAnchor *me) +{ + return (me ? me->style : NULL); +} + +void HTAnchor_setStyle(HTParentAnchor *me, + const char *style) +{ + if (me) { + StrAllocCopy(me->style, style); + } +} +#endif + +/* Title handling. +*/ +const char *HTAnchor_title(HTParentAnchor *me) +{ + return (me ? me->title : NULL); +} + +void HTAnchor_setTitle(HTParentAnchor *me, + const char *title) +{ + int i; + + if (me) { + if (title) { + StrAllocCopy(me->title, title); + for (i = 0; me->title[i]; i++) { + if (UCH(me->title[i]) == 1 || + UCH(me->title[i]) == 2) { + me->title[i] = ' '; + } + } + } else { + CTRACE((tfp, "HTAnchor_setTitle: New title is NULL! ")); + if (me->title) { + CTRACE((tfp, "Old title was \"%s\".\n", me->title)); + FREE(me->title); + } else { + CTRACE((tfp, "Old title was NULL.\n")); + } + } + } +} + +void HTAnchor_appendTitle(HTParentAnchor *me, + const char *title) +{ + int i; + + if (me) { + StrAllocCat(me->title, title); + for (i = 0; me->title[i]; i++) { + if (UCH(me->title[i]) == 1 || + UCH(me->title[i]) == 2) { + me->title[i] = ' '; + } + } + } +} + +/* Bookmark handling. +*/ +const char *HTAnchor_bookmark(HTParentAnchor *me) +{ + return (me ? me->bookmark : NULL); +} + +void HTAnchor_setBookmark(HTParentAnchor *me, + const char *bookmark) +{ + if (me) + StrAllocCopy(me->bookmark, bookmark); +} + +/* Owner handling. +*/ +const char *HTAnchor_owner(HTParentAnchor *me) +{ + return (me ? me->owner : NULL); +} + +void HTAnchor_setOwner(HTParentAnchor *me, + const char *owner) +{ + if (me) { + StrAllocCopy(me->owner, owner); + } +} + +/* TITLE handling in LINKs with REV="made" or REV="owner". - FM +*/ +const char *HTAnchor_RevTitle(HTParentAnchor *me) +{ + return (me ? me->RevTitle : NULL); +} + +void HTAnchor_setRevTitle(HTParentAnchor *me, + const char *title) +{ + int i; + + if (me) { + StrAllocCopy(me->RevTitle, title); + for (i = 0; me->RevTitle[i]; i++) { + if (UCH(me->RevTitle[i]) == 1 || + UCH(me->RevTitle[i]) == 2) { + me->RevTitle[i] = ' '; + } + } + } +} + +#ifndef DISABLE_BIBP +/* Citehost for bibp links from LINKs with REL="citehost". - RDC +*/ +const char *HTAnchor_citehost(HTParentAnchor *me) +{ + return (me ? me->citehost : NULL); +} + +void HTAnchor_setCitehost(HTParentAnchor *me, + const char *citehost) +{ + if (me) { + StrAllocCopy(me->citehost, citehost); + } +} +#endif /* !DISABLE_BIBP */ + +/* Suggested filename handling. - FM + * (will be loaded if we had a Content-Disposition + * header or META element with filename=name.suffix) + */ +const char *HTAnchor_SugFname(HTParentAnchor *me) +{ + return (me ? me->SugFname : NULL); +} + +/* HTTP Headers. +*/ +const char *HTAnchor_http_headers(HTParentAnchor *me) +{ + return (me ? me->http_headers.data : NULL); +} + +/* Content-Type handling (parameter list). +*/ +const char *HTAnchor_content_type_params(HTParentAnchor *me) +{ + return (me ? me->content_type_params : NULL); +} + +/* Content-Encoding handling. - FM + * (will be loaded if we had a Content-Encoding + * header.) + */ +const char *HTAnchor_content_encoding(HTParentAnchor *me) +{ + return (me ? me->content_encoding : NULL); +} + +/* Content-Type handling. - FM +*/ +const char *HTAnchor_content_type(HTParentAnchor *me) +{ + return (me ? me->content_type : NULL); +} + +/* Last-Modified header handling. - FM +*/ +const char *HTAnchor_last_modified(HTParentAnchor *me) +{ + return (me ? me->last_modified : NULL); +} + +/* Date header handling. - FM +*/ +const char *HTAnchor_date(HTParentAnchor *me) +{ + return (me ? me->date : NULL); +} + +/* Server header handling. - FM +*/ +const char *HTAnchor_server(HTParentAnchor *me) +{ + return (me ? me->server : NULL); +} + +/* Safe header handling. - FM +*/ +BOOL HTAnchor_safe(HTParentAnchor *me) +{ + return (BOOL) (me ? me->safe : FALSE); +} + +/* Content-Base header handling. - FM +*/ +const char *HTAnchor_content_base(HTParentAnchor *me) +{ + return (me ? me->content_base : NULL); +} + +/* Content-Location header handling. - FM +*/ +const char *HTAnchor_content_location(HTParentAnchor *me) +{ + return (me ? me->content_location : NULL); +} + +/* Message-ID, used for mail replies - kw +*/ +const char *HTAnchor_messageID(HTParentAnchor *me) +{ + return (me ? me->message_id : NULL); +} + +BOOL HTAnchor_setMessageID(HTParentAnchor *me, + const char *messageid) +{ + if (!(me && messageid && *messageid)) { + return FALSE; + } + StrAllocCopy(me->message_id, messageid); + return TRUE; +} + +/* Subject, used for mail replies - kw +*/ +const char *HTAnchor_subject(HTParentAnchor *me) +{ + return (me ? me->subject : NULL); +} + +BOOL HTAnchor_setSubject(HTParentAnchor *me, + const char *subject) +{ + if (!(me && subject && *subject)) { + return FALSE; + } + StrAllocCopy(me->subject, subject); + return TRUE; +} + +/* Manipulation of links + * --------------------- + */ +HTAnchor *HTAnchor_followLink(HTChildAnchor *me) +{ + return (me->dest); +} + +HTAnchor *HTAnchor_followTypedLink(HTChildAnchor *me, + HTLinkType *type) +{ + if (me->type == type) + return (me->dest); + return (NULL); /* No link of me type */ +} + +/* Methods List + * ------------ + */ +HTList *HTAnchor_methods(HTParentAnchor *me) +{ + if (!me->methods) { + me->methods = HTList_new(); + } + return (me->methods); +} + +/* Protocol + * -------- + */ +void *HTAnchor_protocol(HTParentAnchor *me) +{ + return (me->protocol); +} + +void HTAnchor_setProtocol(HTParentAnchor *me, + void *protocol) +{ + me->protocol = protocol; +} + +/* Physical Address + * ---------------- + */ +char *HTAnchor_physical(HTParentAnchor *me) +{ + return (me->physical); +} + +void HTAnchor_setPhysical(HTParentAnchor *me, + char *physical) +{ + if (me) { + StrAllocCopy(me->physical, physical); + } +} + +#ifdef DEBUG +static void show_stages(HTParentAnchor *me, const char *tag, int which_stage) +{ + int j; + + CTRACE((tfp, "Stages %s*%s", NonNull(me->charset), tag)); + for (j = 0; j < UCT_STAGEMAX; j++) { + CTRACE((tfp, " ")); + if (j == which_stage) + CTRACE((tfp, "(")); + CTRACE((tfp, "%d:%d:%s", + j, + me->UCStages->s[j].LYhndl, + NonNull(me->UCStages->s[j].C.MIMEname))); + if (j == which_stage) + CTRACE((tfp, ")")); + } + CTRACE((tfp, "\n")); +} +#else +#define show_stages(me,tag,which_stage) /* nothing */ +#endif + +/* + * We store charset info in the HTParentAnchor object, for several + * "stages". (See UCDefs.h) + * A stream method is supposed to know what stage in the model it is. + * + * General model MIME -> parser -> structured -> HText + * e.g., text/html + * from HTTP: HTMIME.c -> SGML.c -> HTML.c -> GridText.c + * text/plain + * from file: HTFile.c -> HTPlain.c -> GridText.c + * + * The lock/set_by is used to lock e.g. a charset set by an explicit + * HTTP MIME header against overriding by a HTML META tag - the MIME + * header has higher priority. Defaults (from -assume_.. options etc.) + * will not override charset explicitly given by server. + * + * Some advantages of keeping this in the HTAnchor: + * - Global variables are bad. + * - Can remember a charset given by META tag when toggling to SOURCE view. + * - Can remember a charset given by href in another doc. + * + * We don't modify the HTParentAnchor's charset element + * here, that one will only be set when explicitly given. + */ +LYUCcharset *HTAnchor_getUCInfoStage(HTParentAnchor *me, + int which_stage) +{ + LYUCcharset *result = NULL; + + if (me) { + if (!me->UCStages) { + int i; + int chndl = UCLYhndl_for_unspec; /* always >= 0 */ + UCAnchorInfo *stages = typecalloc(UCAnchorInfo); + + if (stages == NULL) + outofmem(__FILE__, "HTAnchor_getUCInfoStage"); + + for (i = 0; i < UCT_STAGEMAX; i++) { + stages->s[i].C.MIMEname = ""; + stages->s[i].LYhndl = -1; + } + if (me->charset) { + chndl = UCGetLYhndl_byMIME(me->charset); + if (chndl < 0) + chndl = UCLYhndl_for_unrec; + if (chndl < 0) + /* + * UCLYhndl_for_unrec not defined :-( + * fallback to UCLYhndl_for_unspec which always valid. + */ + chndl = UCLYhndl_for_unspec; /* always >= 0 */ + } + MemCpy(&stages->s[UCT_STAGE_MIME].C, &LYCharSet_UC[chndl], + sizeof(LYUCcharset)); + + stages->s[UCT_STAGE_MIME].lock = UCT_SETBY_DEFAULT; + stages->s[UCT_STAGE_MIME].LYhndl = chndl; + me->UCStages = stages; + } + result = (&me->UCStages->s[which_stage].C); + show_stages(me, "_getUCInfoStage", which_stage); + } + return (result); +} + +int HTAnchor_getUCLYhndl(HTParentAnchor *me, + int which_stage) +{ + if (me) { + if (!me->UCStages) { + /* + * This will allocate and initialize, if not yet done. + */ + (void) HTAnchor_getUCInfoStage(me, which_stage); + } + if (me->UCStages->s[which_stage].lock > UCT_SETBY_NONE) { + return (me->UCStages->s[which_stage].LYhndl); + } + } + return (-1); +} + +#ifdef CAN_SWITCH_DISPLAY_CHARSET +static void setup_switch_display_charset(HTParentAnchor *me, int h) +{ + if (!Switch_Display_Charset(h, SWITCH_DISPLAY_CHARSET_MAYBE)) + return; + HTAnchor_setUCInfoStage(me, current_char_set, + UCT_STAGE_HTEXT, UCT_SETBY_MIME); /* highest priority! */ + HTAnchor_setUCInfoStage(me, current_char_set, + UCT_STAGE_STRUCTURED, UCT_SETBY_MIME); /* highest priority! */ + CTRACE((tfp, + "changing UCInfoStage: HTEXT/STRUCTURED stages charset='%s'.\n", + LYCharSet_UC[current_char_set].MIMEname)); +} +#endif + +LYUCcharset *HTAnchor_setUCInfoStage(HTParentAnchor *me, + int LYhndl, + int which_stage, + int set_by) +{ + if (me) { + /* + * This will allocate and initialize, if not yet done. + */ + LYUCcharset *p = HTAnchor_getUCInfoStage(me, which_stage); + + /* + * Can we override? + */ + if (set_by >= me->UCStages->s[which_stage].lock) { +#ifdef CAN_SWITCH_DISPLAY_CHARSET + int ohandle = me->UCStages->s[which_stage].LYhndl; +#endif + me->UCStages->s[which_stage].lock = set_by; + me->UCStages->s[which_stage].LYhndl = LYhndl; + if (LYhndl >= 0) { + MemCpy(p, &LYCharSet_UC[LYhndl], sizeof(LYUCcharset)); + +#ifdef CAN_SWITCH_DISPLAY_CHARSET + /* Allow a switch to a more suitable display charset */ + if (LYhndl != ohandle && which_stage == UCT_STAGE_PARSER) + setup_switch_display_charset(me, LYhndl); +#endif + } else { + p->UChndl = -1; + } + show_stages(me, "_setUCInfoStage", which_stage); + return (p); + } + } + return (NULL); +} + +LYUCcharset *HTAnchor_resetUCInfoStage(HTParentAnchor *me, + int LYhndl, + int which_stage, + int set_by) +{ + LYUCcharset *result = NULL; + int ohandle; + + if (me && me->UCStages) { + me->UCStages->s[which_stage].lock = set_by; + ohandle = me->UCStages->s[which_stage].LYhndl; + me->UCStages->s[which_stage].LYhndl = LYhndl; +#ifdef CAN_SWITCH_DISPLAY_CHARSET + /* Allow a switch to a more suitable display charset */ + if (LYhndl >= 0 && LYhndl != ohandle + && which_stage == UCT_STAGE_PARSER) + setup_switch_display_charset(me, LYhndl); +#else + (void) ohandle; +#endif + show_stages(me, "_resetUCInfoStage", which_stage); + result = (&me->UCStages->s[which_stage].C); + } + return result; +} + +/* + * A set_by of (-1) means use the lock value from the from_stage. + */ +LYUCcharset *HTAnchor_copyUCInfoStage(HTParentAnchor *me, + int to_stage, + int from_stage, + int set_by) +{ + if (me) { + /* + * This will allocate and initialize, if not yet done. + */ + LYUCcharset *p_from = HTAnchor_getUCInfoStage(me, from_stage); + LYUCcharset *p_to = HTAnchor_getUCInfoStage(me, to_stage); + + /* + * Can we override? + */ + if (set_by == -1) + set_by = me->UCStages->s[from_stage].lock; + if (set_by == UCT_SETBY_NONE) + set_by = UCT_SETBY_DEFAULT; + if (set_by >= me->UCStages->s[to_stage].lock) { +#ifdef CAN_SWITCH_DISPLAY_CHARSET + int ohandle = me->UCStages->s[to_stage].LYhndl; +#endif + me->UCStages->s[to_stage].lock = set_by; + me->UCStages->s[to_stage].LYhndl = + me->UCStages->s[from_stage].LYhndl; +#ifdef CAN_SWITCH_DISPLAY_CHARSET + /* Allow a switch to a more suitable display charset */ + if (me->UCStages->s[to_stage].LYhndl >= 0 + && me->UCStages->s[to_stage].LYhndl != ohandle + && to_stage == UCT_STAGE_PARSER) + setup_switch_display_charset(me, + me->UCStages->s[to_stage].LYhndl); +#endif + if (p_to != p_from) + MemCpy(p_to, p_from, sizeof(LYUCcharset)); + + return (p_to); + } + } + return (NULL); +} diff --git a/WWW/Library/Implementation/HTAnchor.h b/WWW/Library/Implementation/HTAnchor.h new file mode 100644 index 0000000..3b1e6e6 --- /dev/null +++ b/WWW/Library/Implementation/HTAnchor.h @@ -0,0 +1,412 @@ +/* + * $LynxId: HTAnchor.h,v 1.40 2018/03/11 18:43:50 tom Exp $ + * + * Hypertext "Anchor" Object HTAnchor.h + * ========================== + * + * An anchor represents a region of a hypertext document which is linked + * to another anchor in the same or a different document. + */ + +#ifndef HTANCHOR_H +#define HTANCHOR_H + +/* Version 0 (TBL) written in Objective-C for the NeXT browser */ +/* Version 1 of 24-Oct-1991 (JFG), written in C, browser-independent */ + +#include +#include +#include +#include +#include + +typedef struct _HyperDoc HyperDoc; /* Ready for forward references */ +typedef struct _HTAnchor HTAnchor; +typedef struct _HTParentAnchor HTParentAnchor; +typedef struct _HTParentAnchor0 HTParentAnchor0; + +#include + +#ifdef __cplusplus +extern "C" { +#endif + struct _HTAnchor { + /* Generic anchor */ + HTParentAnchor0 *parent; /* Parent of this anchor (self for adults) */ + }; + +#define HASH_TYPE unsigned short + + struct _HTParentAnchor0 { /* One for adult_table, + * generally not used outside HTAnchor.c */ + /* Common part from the generic anchor structure */ + HTParentAnchor0 *parent; /* (self) */ + + /* ParentAnchor0-specific information */ + char *address; /* Absolute address of this node */ + HTParentAnchor *info; /* additional info, allocated on demand */ + + HTBTree *children; /* Subanchors , sorted by tag */ + HTList sources; /* List of anchors pointing to this, if any */ + + HTList _add_adult; /* - just a memory for list entry:) */ + HASH_TYPE adult_hash; /* adult list number */ + BOOL underway; /* Document about to be attached to it */ + }; + + /* + * Separated from the above to save memory: allocated on demand, + * it is nearly 1:1 to HText (well, sometimes without HText...), + * available for SGML, HTML, and HText stages. + * [being precise, we currently allocate it before HTLoadDocument(), + * in HTAnchor_findAddress() and HTAnchor_parent()]. + */ + struct _HTParentAnchor { + /* Common part from the generic anchor structure */ + HTParentAnchor0 *parent; /* Parent of this anchor */ + + /* ParentAnchor-specific information */ + HTList children_notag; /* Subanchors , tag is NULL */ + HyperDoc *document; /* The document within which this is an anchor */ + + char *address; /* parent->address, a pointer */ + bstring *post_data; /* Posting data */ + char *post_content_type; /* Type of post data */ + char *bookmark; /* Bookmark filename */ + HTFormat format; /* Pointer to node format descriptor */ + char *charset; /* Pointer to character set (kludge, for now */ + BOOL isIndex; /* Acceptance of a keyword search */ + char *isIndexAction; /* URL of isIndex server */ + char *isIndexPrompt; /* Prompt for isIndex query */ + char *title; /* Title of document */ + char *owner; /* Owner of document */ + char *RevTitle; /* TITLE in REV="made" or REV="owner" LINK */ + char *citehost; /* Citehost from REL="citehost" LINK */ +#ifdef USE_COLOR_STYLE + char *style; +#endif + + HTList *methods; /* Methods available as HTAtoms */ + void *protocol; /* Protocol object */ + char *physical; /* Physical address */ + BOOL isISMAPScript; /* Script for clickable image map */ + BOOL isHEAD; /* Document is headers from a HEAD request */ + BOOL safe; /* Safe */ +#ifdef USE_SOURCE_CACHE + char *source_cache_file; + HTChunk *source_cache_chunk; +#endif + char *FileCache; /* Path to a disk-cached copy (see src/HTFWriter.c) */ + char *SugFname; /* Suggested filename */ + char *cache_control; /* Cache-Control */ + BOOL no_cache; /* Cache-Control, Pragma or META "no-cache"? */ + BOOL inHEAD; /* HTMIMEConvert is decoding server-headers */ + BOOL inBASE; /* duplicated from HTStructured (HTML.c/h) */ + HTChunk http_headers; + BOOL no_content_encoding; /* server did not use C-T? */ + char *content_type_params; /* Content-Type (with parameters if any) */ + char *content_type; /* Content-Type */ + char *content_language; /* Content-Language */ + char *content_encoding; /* Compression algorithm */ + char *content_base; /* Content-Base */ + char *content_disposition; /* Content-Disposition */ + char *content_location; /* Content-Location */ + char *content_md5; /* Content-MD5 */ + char *message_id; /* Message-ID */ + char *subject; /* Subject */ + off_t header_length; /* length of headers */ + off_t content_length; /* Content-Length */ + off_t actual_length; /* actual length may differ */ + char *date; /* Date */ + char *expires; /* Expires */ + char *last_modified; /* Last-Modified */ + char *ETag; /* ETag (HTTP1.1 cache validator) */ + char *server; /* Server */ + UCAnchorInfo *UCStages; /* chartrans stages */ + HTList *imaps; /* client side image maps */ + }; + + typedef HTAtom HTLinkType; + + typedef struct { + /* Common part from the generic anchor structure */ + HTParentAnchor0 *parent; /* Parent of this anchor */ + + /* ChildAnchor-specific information */ + char *tag; /* #fragment, relative to the parent */ + + HTAnchor *dest; /* The anchor to which this leads */ + HTLinkType *type; /* Semantics of this link */ + + HTList _add_children_notag; /* - just a memory for list entry:) */ + HTList _add_sources; /* - just a memory for list entry:) */ + } HTChildAnchor; + + /* + * DocAddress structure is used for loading an absolute anchor with all + * needed information including posting data and post content type. + */ + typedef struct _DocAddress { + char *address; + bstring *post_data; + char *post_content_type; + char *bookmark; + BOOL isHEAD; + BOOL safe; + } DocAddress; + + /* "internal" means "within the same document, with certainty". */ + extern HTLinkType *HTInternalLink; + + /* Create or find a child anchor with a possible link + * -------------------------------------------------- + * + * Create new anchor with a given parent and possibly + * a name, and possibly a link to a _relatively_ named anchor. + * (Code originally in ParseHTML.h) + */ + extern HTChildAnchor *HTAnchor_findChildAndLink(HTParentAnchor *parent, /* May not be 0 */ + const char *tag, /* May be "" or 0 */ + const char *href, /* May be "" or 0 */ + HTLinkType *ltype); /* May be 0 */ + + /* Create new or find old parent anchor + * ------------------------------------ + * + * This one is for a reference which is found in a document, and might + * not be already loaded. + * Note: You are not guaranteed a new anchor -- you might get an old one, + * like with fonts. + */ + extern HTParentAnchor *HTAnchor_findAddress(const DocAddress *address); + + /* Create new or find old named anchor - simple form + * ------------------------------------------------- + * + * Like the previous one, but simpler to use for simple cases. + * No post data etc. can be supplied. - kw + */ + extern HTParentAnchor *HTAnchor_findSimpleAddress(const char *url); + + /* Delete an anchor and possibly related things (auto garbage collection) + * -------------------------------------------- + * + * The anchor is only deleted if the corresponding document is not loaded. + * All outgoing links from children are deleted, and children are + * removed from the sources lists of their targets. + * We also try to delete the targets whose documents are not loaded. + * If this anchor's sources list is empty, we delete it and its children. + */ + extern BOOL HTAnchor_delete(HTParentAnchor0 *me); + + /* + * Unnamed children (children_notag) have no sense without HText - + * delete them and their links if we are about to free HText. + * Document currently exists. Called within HText_free(). + */ + extern void HTAnchor_delete_links(HTParentAnchor *me); + +#ifdef USE_SOURCE_CACHE + extern void HTAnchor_clearSourceCache(HTParentAnchor *me); +#endif + + /* Data access functions + * --------------------- + */ + extern HTParentAnchor *HTAnchor_parent(HTAnchor * me); + + extern void HTAnchor_setDocument(HTParentAnchor *me, + HyperDoc *doc); + + extern HyperDoc *HTAnchor_document(HTParentAnchor *me); + + /* Returns the full URI of the anchor, child or parent + * as a malloc'd string to be freed by the caller. + */ + extern char *HTAnchor_address(HTAnchor * me); + + extern char *HTAnchor_short_address(HTAnchor * me); + + extern void HTAnchor_setFormat(HTParentAnchor *me, + HTFormat form); + + extern HTFormat HTAnchor_format(HTParentAnchor *me); + + extern void HTAnchor_setIndex(HTParentAnchor *me, + const char *address); + + extern void HTAnchor_setPrompt(HTParentAnchor *me, + const char *prompt); + + extern BOOL HTAnchor_isIndex(HTParentAnchor *me); + + extern BOOL HTAnchor_isISMAPScript(HTAnchor * me); + +#if defined(USE_COLOR_STYLE) + extern const char *HTAnchor_style(HTParentAnchor *me); + + extern void HTAnchor_setStyle(HTParentAnchor *me, + const char *style); +#endif + + /* Title handling. + */ + extern const char *HTAnchor_title(HTParentAnchor *me); + + extern void HTAnchor_setTitle(HTParentAnchor *me, + const char *title); + + extern void HTAnchor_appendTitle(HTParentAnchor *me, + const char *title); + + /* Bookmark handling. + */ + extern const char *HTAnchor_bookmark(HTParentAnchor *me); + + extern void HTAnchor_setBookmark(HTParentAnchor *me, + const char *bookmark); + + /* Owner handling. + */ + extern const char *HTAnchor_owner(HTParentAnchor *me); + + extern void HTAnchor_setOwner(HTParentAnchor *me, + const char *owner); + + /* TITLE handling in LINKs with REV="made" or REV="owner". - FM + */ + extern const char *HTAnchor_RevTitle(HTParentAnchor *me); + + extern void HTAnchor_setRevTitle(HTParentAnchor *me, + const char *title); + + /* Citehost for bibp links from LINKs with REL="citehost". - RDC + */ + extern const char *HTAnchor_citehost(HTParentAnchor *me); + + extern void HTAnchor_setCitehost(HTParentAnchor *me, + const char *citehost); + + /* Suggested filename handling. - FM + * (will be loaded if we had a Content-Disposition + * header or META element with filename=name.suffix) + */ + extern const char *HTAnchor_SugFname(HTParentAnchor *me); + + /* HTTP Headers. + */ + extern const char *HTAnchor_http_headers(HTParentAnchor *me); + + /* Content-Type handling (parameter list). + */ + extern const char *HTAnchor_content_type_params(HTParentAnchor *me); + + /* Content-Type handling. - FM + */ + extern const char *HTAnchor_content_type(HTParentAnchor *me); + + /* Content-Encoding handling. - FM + * (will be loaded if we had a Content-Encoding + * header.) + */ + extern const char *HTAnchor_content_encoding(HTParentAnchor *me); + + /* Last-Modified header handling. - FM + */ + extern const char *HTAnchor_last_modified(HTParentAnchor *me); + + /* Date header handling. - FM + */ + extern const char *HTAnchor_date(HTParentAnchor *me); + + /* Server header handling. - FM + */ + extern const char *HTAnchor_server(HTParentAnchor *me); + + /* Safe header handling. - FM + */ + extern BOOL HTAnchor_safe(HTParentAnchor *me); + + /* Content-Base header handling. - FM + */ + extern const char *HTAnchor_content_base(HTParentAnchor *me); + + /* Content-Location header handling. - FM + */ + extern const char *HTAnchor_content_location(HTParentAnchor *me); + + /* Message-ID, used for mail replies - kw + */ + extern const char *HTAnchor_messageID(HTParentAnchor *me); + + extern BOOL HTAnchor_setMessageID(HTParentAnchor *me, + const char *messageid); + + /* Subject, used for mail replies - kw + */ + extern const char *HTAnchor_subject(HTParentAnchor *me); + + extern BOOL HTAnchor_setSubject(HTParentAnchor *me, + const char *subject); + + /* Manipulation of links + * --------------------- + */ + extern HTAnchor *HTAnchor_followLink(HTChildAnchor *me); + + extern HTAnchor *HTAnchor_followTypedLink(HTChildAnchor *me, + HTLinkType *type); + + /* Read and write methods + * ---------------------- + */ + extern HTList *HTAnchor_methods(HTParentAnchor *me); + + /* Protocol + * -------- + */ + extern void *HTAnchor_protocol(HTParentAnchor *me); + + extern void HTAnchor_setProtocol(HTParentAnchor *me, + void *protocol); + + /* Physical address + * ---------------- + */ + extern char *HTAnchor_physical(HTParentAnchor *me); + + extern void HTAnchor_setPhysical(HTParentAnchor *me, + char *protocol); + + extern LYUCcharset *HTAnchor_getUCInfoStage(HTParentAnchor *me, + int which_stage); + + extern int HTAnchor_getUCLYhndl(HTParentAnchor *me, + int which_stage); + + extern LYUCcharset *HTAnchor_setUCInfoStage(HTParentAnchor *me, + int LYhndl, + int which_stage, + int set_by); + + extern LYUCcharset *HTAnchor_setUCInfoStage(HTParentAnchor *me, + int LYhndl, + int which_stage, + int set_by); + + extern LYUCcharset *HTAnchor_resetUCInfoStage(HTParentAnchor *me, + int LYhndl, + int which_stage, + int set_by); + + extern LYUCcharset *HTAnchor_copyUCInfoStage(HTParentAnchor *me, + int to_stage, + int from_stage, + int set_by); + + extern void ImageMapList_free(HTList *list); + +#ifdef __cplusplus +} +#endif +#endif /* HTANCHOR_H */ diff --git a/WWW/Library/Implementation/HTAssoc.c b/WWW/Library/Implementation/HTAssoc.c new file mode 100644 index 0000000..831b196 --- /dev/null +++ b/WWW/Library/Implementation/HTAssoc.c @@ -0,0 +1,82 @@ +/* + * $LynxId: HTAssoc.c,v 1.11 2016/11/24 15:29:50 tom Exp $ + * + * MODULE HTAssoc.c + * ASSOCIATION LIST FOR STORING NAME-VALUE PAIRS. + * NAMES NOT CASE SENSITIVE, AND ONLY COMMON LENGTH + * IS CHECKED (allows abbreviations; well, length is + * taken from lookup-up name, so if table contains + * a shorter abbrev it is not found). + * AUTHORS: + * AL Ari Luotonen luotonen@dxcern.cern.ch + * + * HISTORY: + * + * + * BUGS: + * + * + */ + +#include + +#include + +#include + +HTAssocList *HTAssocList_new(void) +{ + return HTList_new(); +} + +void HTAssocList_delete(HTAssocList *alist) +{ + if (alist) { + HTAssocList *cur = alist; + HTAssoc *assoc; + + while (NULL != (assoc = (HTAssoc *) HTList_nextObject(cur))) { + FREE(assoc->name); + FREE(assoc->value); + FREE(assoc); + } + HTList_delete(alist); + alist = NULL; + } +} + +void HTAssocList_add(HTAssocList *alist, + const char *name, + const char *value) +{ + HTAssoc *assoc; + + if (alist) { + if (!(assoc = (HTAssoc *) malloc(sizeof(HTAssoc)))) + outofmem(__FILE__, "HTAssoc_add"); + + assoc->name = NULL; + assoc->value = NULL; + + if (name) + StrAllocCopy(assoc->name, name); + if (value) + StrAllocCopy(assoc->value, value); + HTList_addObject(alist, (void *) assoc); + } else { + CTRACE((tfp, "HTAssoc_add: ERROR: assoc list NULL!!\n")); + } +} + +char *HTAssocList_lookup(HTAssocList *alist, + const char *name) +{ + HTAssocList *cur = alist; + HTAssoc *assoc; + + while (NULL != (assoc = (HTAssoc *) HTList_nextObject(cur))) { + if (!strncasecomp(assoc->name, name, (int) strlen(name))) + return assoc->value; + } + return NULL; +} diff --git a/WWW/Library/Implementation/HTAssoc.h b/WWW/Library/Implementation/HTAssoc.h new file mode 100644 index 0000000..327809c --- /dev/null +++ b/WWW/Library/Implementation/HTAssoc.h @@ -0,0 +1,35 @@ +/* ASSOCIATION LIST FOR STORING NAME-VALUE PAIRS + + Lookups from association list are not case-sensitive. + + */ + +#ifndef HTASSOC_H +#define HTASSOC_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + typedef HTList HTAssocList; + + typedef struct { + char *name; + char *value; + } HTAssoc; + + extern HTAssocList *HTAssocList_new(void); + extern void HTAssocList_delete(HTAssocList *alist); + + extern void HTAssocList_add(HTAssocList *alist, + const char *name, + const char *value); + + extern char *HTAssocList_lookup(HTAssocList *alist, + const char *name); + +#ifdef __cplusplus +} +#endif +#endif /* not HTASSOC_H */ diff --git a/WWW/Library/Implementation/HTAtom.c b/WWW/Library/Implementation/HTAtom.c new file mode 100644 index 0000000..07eca77 --- /dev/null +++ b/WWW/Library/Implementation/HTAtom.c @@ -0,0 +1,107 @@ +/* + * $LynxId: HTAtom.c,v 1.22 2018/03/06 09:46:58 tom Exp $ + * + * Atoms: Names to numbers HTAtom.c + * ======================= + * + * Atoms are names which are given representative pointer values + * so that they can be stored more efficiently, and comparisons + * for equality done more efficiently. + * + * Atoms are kept in a hash table consisting of an array of linked lists. + * + * Authors: + * TBL Tim Berners-Lee, WorldWideWeb project, CERN + * (c) Copyright CERN 1991 - See Copyright.html + * + */ + +#include +#include +#include +#include +#include + +#define HASH_SIZE 101 /* Arbitrary prime. Memory/speed tradeoff */ + +static HTAtom *hash_table[HASH_SIZE]; +static BOOL initialised = NO; + +/* + * To free off all atoms. + */ +#ifdef LY_FIND_LEAKS +static void free_atoms(void); +#endif + +#define HASH_FUNCTION(cp_hash) ((strlen(cp_hash) * UCH(*cp_hash)) % HASH_SIZE) + +HTAtom *HTAtom_for(const char *string) +{ + size_t hash; + HTAtom *a; + + if (!initialised) { + memset(hash_table, 0, sizeof(hash_table)); + initialised = YES; +#ifdef LY_FIND_LEAKS + atexit(free_atoms); +#endif + } + + hash = HASH_FUNCTION(string); + + for (a = hash_table[hash]; a; a = a->next) { + if (0 == strcasecomp(a->name, string)) { + return a; + } + } + + a = (HTAtom *) malloc(sizeof(*a)); + if (a == NULL) + outofmem(__FILE__, "HTAtom_for"); + + a->name = (char *) malloc(strlen(string) + 1); + if (a->name == NULL) + outofmem(__FILE__, "HTAtom_for"); + + strcpy(a->name, string); + a->next = hash_table[hash]; + hash_table[hash] = a; + return a; +} + +#ifdef LY_FIND_LEAKS +/* + * Purpose: Free off all atoms. + * Arguments: void + * Return Value: void + * Remarks/Portability/Dependencies/Restrictions: + * To be used at program exit. + * Revision History: + * 05-29-94 created Lynx 2-3-1 Garrett Arch Blythe + */ +static void free_atoms(void) +{ + auto int i_counter; + HTAtom *HTAp_freeme; + + /* + * Loop through all lists of atoms. + */ + for (i_counter = 0; i_counter < HASH_SIZE; i_counter++) { + /* + * Loop through the list. + */ + while (hash_table[i_counter] != NULL) { + /* + * Free off atoms and any members. + */ + HTAp_freeme = hash_table[i_counter]; + hash_table[i_counter] = HTAp_freeme->next; + FREE(HTAp_freeme->name); + FREE(HTAp_freeme); + } + } +} +#endif /* LY_FIND_LEAKS */ diff --git a/WWW/Library/Implementation/HTAtom.h b/WWW/Library/Implementation/HTAtom.h new file mode 100644 index 0000000..0d787bc --- /dev/null +++ b/WWW/Library/Implementation/HTAtom.h @@ -0,0 +1,53 @@ +/* */ + +/* Atoms: Names to numbers HTAtom.h + * ======================= + * + * Atoms are names which are given representative pointer values + * so that they can be stored more efficiently, and compaisons + * for equality done more efficiently. + * + * HTAtom_for(string) returns a representative value such that it + * will always (within one run of the program) return the same + * value for the same given string. + * + * Authors: + * TBL Tim Berners-Lee, WorldWideWeb project, CERN + * + * (c) Copyright CERN 1991 - See Copyright.html + * + */ + +#ifndef HTATOM_H +#define HTATOM_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + typedef struct _HTAtom HTAtom; + + struct _HTAtom { + HTAtom *next; + char *name; + }; /* struct _HTAtom */ + + extern HTAtom *HTAtom_for(const char *string); + +#define HTAtom_name(a) ((a)->name) + +/* + +The HTFormat type + + We use the HTAtom object for holding representations. This allows faster manipulation + (comparison and copying) that if we stayed with strings. + + */ + typedef HTAtom *HTFormat; + +#ifdef __cplusplus +} +#endif +#endif /* HTATOM_H */ diff --git a/WWW/Library/Implementation/HTBTree.c b/WWW/Library/Implementation/HTBTree.c new file mode 100644 index 0000000..f595bae --- /dev/null +++ b/WWW/Library/Implementation/HTBTree.c @@ -0,0 +1,680 @@ +/* Binary Tree for sorting things + * ============================== + * Author: Arthur Secret + * + * 4 March 94: Bug fixed in the balancing procedure + * + */ + +#include +#include + +#define MAXIMUM(a,b) ((a)>(b)?(a):(b)) + +#include + +/********************************************************* + * This function returns an HTBTree with memory allocated + * for it when given a mean to compare things + */ +HTBTree *HTBTree_new(HTComparer comp) +{ + HTBTree *tree = typeMalloc(HTBTree); + + if (tree == NULL) + outofmem(__FILE__, "HTBTree_new"); + + tree->compare = comp; + tree->top = NULL; + + return tree; +} + +/********************************************************* + * This void will free the memory allocated for one element + */ +static void HTBTElement_free(HTBTElement *element) +{ + if (element) { + if (element->left != NULL) + HTBTElement_free(element->left); + if (element->right != NULL) + HTBTElement_free(element->right); + FREE(element); + } +} + +/************************************************************* + * This void will free the memory allocated for the whole tree + */ +void HTBTree_free(HTBTree *tree) +{ + HTBTElement_free(tree->top); + FREE(tree); +} + +/********************************************************* + * This void will free the memory allocated for one element + */ +static void HTBTElementAndObject_free(HTBTElement *element) +{ + if (element) { /* Just in case nothing was in the tree anyway */ + if (element->left != NULL) + HTBTElementAndObject_free(element->left); + if (element->right != NULL) + HTBTElementAndObject_free(element->right); + FREE(element->object); + FREE(element); + } +} + +/************************************************************* + * This void will free the memory allocated for the whole tree + */ +void HTBTreeAndObject_free(HTBTree *tree) +{ + HTBTElementAndObject_free(tree->top); + FREE(tree); +} + +/********************************************************************* + * Returns a pointer to equivalent object in a tree or NULL if none. + */ +void *HTBTree_search(HTBTree *tree, + void *object) +{ + HTBTElement *cur = tree->top; + int res; + + while (cur != NULL) { + res = tree->compare(object, cur->object); + + if (res == 0) + return cur->object; + else if (res < 0) + cur = cur->left; + else if (res > 0) + cur = cur->right; + } + return NULL; +} + +/********************************************************************* + * This void is the core of HTBTree.c . It will + * 1/ add a new element to the tree at the right place + * so that the tree remains sorted + * 2/ balance the tree to be as fast as possible when reading it + */ +void HTBTree_add(HTBTree *tree, + void *object) +{ + HTBTElement *father_of_element; + HTBTElement *added_element; + HTBTElement *forefather_of_element; + HTBTElement *father_of_forefather; + BOOL father_found, top_found; + int depth, depth2, corrections; + + /* father_of_element is a pointer to the structure that is the father of + * the new object "object". added_element is a pointer to the structure + * that contains or will contain the new object "object". + * father_of_forefather and forefather_of_element are pointers that are + * used to modify the depths of upper elements, when needed. + * + * father_found indicates by a value NO when the future father of "object" + * is found. top_found indicates by a value NO when, in case of a + * difference of depths < 2, the top of the tree is encountered and forbids + * any further try to balance the tree. corrections is an integer used to + * avoid infinite loops in cases such as: + * + * 3 3 + * 4 4 + * 5 5 + * + * 3 is used here to show that it need not be the top of the tree. + */ + + /* + * 1/ Adding of the element to the binary tree + */ + + if (tree->top == NULL) { + tree->top = typeMalloc(HTBTElement); + + if (tree->top == NULL) + outofmem(__FILE__, "HTBTree_add"); + + tree->top->up = NULL; + tree->top->object = object; + tree->top->left = NULL; + tree->top->left_depth = 0; + tree->top->right = NULL; + tree->top->right_depth = 0; + } else { + father_found = YES; + father_of_element = tree->top; + added_element = NULL; + father_of_forefather = NULL; + forefather_of_element = NULL; + while (father_found) { + int res = tree->compare(object, father_of_element->object); + + if (res < 0) { + if (father_of_element->left != NULL) + father_of_element = father_of_element->left; + else { + father_found = NO; + father_of_element->left = typeMalloc(HTBTElement); + + if (father_of_element->left == NULL) + outofmem(__FILE__, "HTBTree_add"); + + added_element = father_of_element->left; + added_element->up = father_of_element; + added_element->object = object; + added_element->left = NULL; + added_element->left_depth = 0; + added_element->right = NULL; + added_element->right_depth = 0; + } + } else { /* res >= 0 */ + if (father_of_element->right != NULL) { + father_of_element = father_of_element->right; + } else { + father_found = NO; + father_of_element->right = typeMalloc(HTBTElement); + + if (father_of_element->right == NULL) + outofmem(__FILE__, "HTBTree_add"); + + added_element = father_of_element->right; + added_element->up = father_of_element; + added_element->object = object; + added_element->left = NULL; + added_element->left_depth = 0; + added_element->right = NULL; + added_element->right_depth = 0; + } + } + } + + /* + * Changing of all depths that need to be changed + */ + father_of_forefather = father_of_element; + forefather_of_element = added_element; + do { + if (father_of_forefather->left == forefather_of_element) { + depth = father_of_forefather->left_depth; + father_of_forefather->left_depth = 1 + + MAXIMUM(forefather_of_element->right_depth, + forefather_of_element->left_depth); + depth2 = father_of_forefather->left_depth; + } else { + depth = father_of_forefather->right_depth; + father_of_forefather->right_depth = 1 + + MAXIMUM(forefather_of_element->right_depth, + forefather_of_element->left_depth); + depth2 = father_of_forefather->right_depth; + } + forefather_of_element = father_of_forefather; + father_of_forefather = father_of_forefather->up; + } while ((depth != depth2) && (father_of_forefather != NULL)); + + /* + * 2/ Balancing the binary tree, if necessary + */ + top_found = YES; + corrections = 0; + while ((top_found) && (corrections < 7)) { + if ((abs(father_of_element->left_depth + - father_of_element->right_depth)) < 2) { + if (father_of_element->up != NULL) + father_of_element = father_of_element->up; + else + top_found = NO; + } else { /* We start the process of balancing */ + + corrections = corrections + 1; + /* + * corrections is an integer used to avoid infinite + * loops in cases such as: + * + * 3 3 + * 4 4 + * 5 5 + * + * 3 is used to show that it need not be the top of the tree + * But let's avoid these two exceptions anyhow + * with the two following conditions (4 March 94 - AS) + */ + + if (father_of_element->left == NULL) { + if ((father_of_element->right != NULL) + && (father_of_element->right->right == NULL) + && (father_of_element->right->left != NULL) + && (father_of_element->right->left->left == NULL) + && (father_of_element->right->left->right == NULL)) { + corrections = 7; + } + } else { + if ((father_of_element->right == NULL) + && (father_of_element->left->left == NULL) + && (father_of_element->left->right != NULL) + && (father_of_element->left->right->right == NULL) + && (father_of_element->left->right->left == NULL)) { + corrections = 7; + } + } + + if ((father_of_element->left != NULL) + && (father_of_element->left_depth > father_of_element->right_depth)) { + added_element = father_of_element->left; + father_of_element->left_depth = added_element->right_depth; + added_element->right_depth = 1 + + MAXIMUM(father_of_element->right_depth, + father_of_element->left_depth); + if (father_of_element->up != NULL) { + /* Bug fixed in March 94 - AS */ + BOOL first_time; + + father_of_forefather = father_of_element->up; + forefather_of_element = added_element; + first_time = YES; + do { + if (father_of_forefather->left + == forefather_of_element->up) { + depth = father_of_forefather->left_depth; + if (first_time) { + father_of_forefather->left_depth = 1 + + MAXIMUM(forefather_of_element->left_depth, + forefather_of_element->right_depth); + first_time = NO; + } else + father_of_forefather->left_depth = 1 + + MAXIMUM(forefather_of_element->up->left_depth, + forefather_of_element->up->right_depth); + + depth2 = father_of_forefather->left_depth; + } else { + depth = father_of_forefather->right_depth; + if (first_time) { + father_of_forefather->right_depth = 1 + + MAXIMUM(forefather_of_element->left_depth, + forefather_of_element->right_depth); + first_time = NO; + } else + father_of_forefather->right_depth = 1 + + MAXIMUM(forefather_of_element->up->left_depth, + forefather_of_element->up->right_depth); + depth2 = father_of_forefather->right_depth; + } + forefather_of_element = forefather_of_element->up; + father_of_forefather = father_of_forefather->up; + } while ((depth != depth2) && + (father_of_forefather != NULL)); + father_of_forefather = father_of_element->up; + if (father_of_forefather->left == father_of_element) { + /* + * 3 3 + * 4 5 + * When tree 5 6 becomes 7 4 + * 7 8 8 6 + * + * 3 is used to show that it may not be the top of the + * tree. + */ + father_of_forefather->left = added_element; + father_of_element->left = added_element->right; + added_element->right = father_of_element; + } + if (father_of_forefather->right == father_of_element) { + /* + * 3 3 + * 4 5 + * When tree 5 6 becomes 7 4 + * 7 8 8 6 + * + * 3 is used to show that it may not be the top of the + * tree + */ + father_of_forefather->right = added_element; + father_of_element->left = added_element->right; + added_element->right = father_of_element; + } + added_element->up = father_of_forefather; + } else { + /* + + * 1 2 + * When tree 2 3 becomes 4 1 + * 4 5 5 3 + * + * 1 is used to show that it is the top of the tree + */ + added_element->up = NULL; + father_of_element->left = added_element->right; + added_element->right = father_of_element; + } + father_of_element->up = added_element; + if (father_of_element->left != NULL) + father_of_element->left->up = father_of_element; + } else if (father_of_element->right != NULL) { + added_element = father_of_element->right; + father_of_element->right_depth = added_element->left_depth; + added_element->left_depth = 1 + + MAXIMUM(father_of_element->right_depth, + father_of_element->left_depth); + if (father_of_element->up != NULL) + /* Bug fixed in March 94 - AS */ + { + BOOL first_time; + + father_of_forefather = father_of_element->up; + forefather_of_element = added_element; + first_time = YES; + do { + if (father_of_forefather->left + == forefather_of_element->up) { + depth = father_of_forefather->left_depth; + if (first_time) { + father_of_forefather->left_depth = 1 + + MAXIMUM(forefather_of_element->left_depth, + forefather_of_element->right_depth); + first_time = NO; + } else + father_of_forefather->left_depth = 1 + + MAXIMUM(forefather_of_element->up->left_depth, + forefather_of_element->up->right_depth); + depth2 = father_of_forefather->left_depth; + } else { + depth = father_of_forefather->right_depth; + if (first_time) { + father_of_forefather->right_depth = 1 + + MAXIMUM(forefather_of_element->left_depth, + forefather_of_element->right_depth); + first_time = NO; + } else + father_of_forefather->right_depth = 1 + + MAXIMUM(forefather_of_element->up->left_depth, + forefather_of_element->up->right_depth); + depth2 = father_of_forefather->right_depth; + } + father_of_forefather = father_of_forefather->up; + forefather_of_element = forefather_of_element->up; + } while ((depth != depth2) && + (father_of_forefather != NULL)); + father_of_forefather = father_of_element->up; + if (father_of_forefather->left == father_of_element) { + /* + * 3 3 + * 4 6 + * When tree 5 6 becomes 4 8 + * 7 8 5 7 + * + * 3 is used to show that it may not be the top of the + * tree. + */ + father_of_forefather->left = added_element; + father_of_element->right = added_element->left; + added_element->left = father_of_element; + } + if (father_of_forefather->right == father_of_element) { + /* + * 3 3 + * 4 6 + * When tree 5 6 becomes 4 8 + * 7 8 5 7 + * + * 3 is used to show that it may not be the top of the + * tree + */ + father_of_forefather->right = added_element; + father_of_element->right = added_element->left; + added_element->left = father_of_element; + } + added_element->up = father_of_forefather; + } else { + /* + + * 1 3 + * When tree 2 3 becomes 1 5 + * 4 5 2 4 + * + * 1 is used to show that it is the top of the tree. + */ + added_element->up = NULL; + father_of_element->right = added_element->left; + added_element->left = father_of_element; + } + father_of_element->up = added_element; + if (father_of_element->right != NULL) + father_of_element->right->up = father_of_element; + } + } + } + while (father_of_element->up != NULL) { + father_of_element = father_of_element->up; + } + tree->top = father_of_element; + } +} + +/************************************************************************* + * this function returns a pointer to the leftmost element if ele is NULL, + * and to the next object to the right otherwise. + * If no elements left, returns a pointer to NULL. + */ +HTBTElement *HTBTree_next(HTBTree *tree, + HTBTElement *ele) +{ + HTBTElement *father_of_element; + HTBTElement *father_of_forefather; + + if (ele == NULL) { + father_of_element = tree->top; + if (father_of_element != NULL) + while (father_of_element->left != NULL) + father_of_element = father_of_element->left; + } else { + father_of_element = ele; + if (father_of_element->right != NULL) { + father_of_element = father_of_element->right; + while (father_of_element->left != NULL) + father_of_element = father_of_element->left; + } else { + father_of_forefather = father_of_element->up; + while (father_of_forefather && + (father_of_forefather->right == father_of_element)) { + father_of_element = father_of_forefather; + father_of_forefather = father_of_element->up; + } + father_of_element = father_of_forefather; + } + } +#ifdef BTREE_TRACE + /* The option -DBTREE_TRACE will give much more information + * about the way the process is running, for debugging matters + */ + if (father_of_element != NULL) { + printf("\nObject = %s\t", (char *) father_of_element->object); + if (father_of_element->up != NULL) + printf("Objet du pere = %s\n", + (char *) father_of_element->up->object); + else + printf("Pas de Pere\n"); + if (father_of_element->left != NULL) + printf("Objet du fils gauche = %s\t", + (char *) father_of_element->left->object); + else + printf("Pas de fils gauche\t"); + if (father_of_element->right != NULL) + printf("Objet du fils droit = %s\n", + (char *) father_of_element->right->object); + else + printf("Pas de fils droit\n"); + printf("Profondeur gauche = %d\t", father_of_element->left_depth); + printf("Profondeur droite = %d\n", father_of_element->right_depth); + printf(" **************\n"); + } +#endif + return father_of_element; +} + +#ifdef TEST +/***************************************************** + * This is just a test to show how to handle HTBTree.c + */ +main() +{ + HTBTree *tree; + HTBTElement *next_element; + + tree = HTBTree_new((HTComparer) strcasecomp); + HTBTree_add(tree, "hypertext"); + HTBTree_add(tree, "Addressing"); + HTBTree_add(tree, "X11"); + HTBTree_add(tree, "Tools"); + HTBTree_add(tree, "Proposal.wn"); + HTBTree_add(tree, "Protocols"); + HTBTree_add(tree, "NeXT"); + HTBTree_add(tree, "Daemon"); + HTBTree_add(tree, "Test"); + HTBTree_add(tree, "Administration"); + HTBTree_add(tree, "LineMode"); + HTBTree_add(tree, "DesignIssues"); + HTBTree_add(tree, "MarkUp"); + HTBTree_add(tree, "Macintosh"); + HTBTree_add(tree, "Proposal.rtf.wn"); + HTBTree_add(tree, "FIND"); + HTBTree_add(tree, "Paper"); + HTBTree_add(tree, "Tcl"); + HTBTree_add(tree, "Talks"); + HTBTree_add(tree, "Architecture"); + HTBTree_add(tree, "VMSHelp"); + HTBTree_add(tree, "Provider"); + HTBTree_add(tree, "Archive"); + HTBTree_add(tree, "SLAC"); + HTBTree_add(tree, "Project"); + HTBTree_add(tree, "News"); + HTBTree_add(tree, "Viola"); + HTBTree_add(tree, "Users"); + HTBTree_add(tree, "FAQ"); + HTBTree_add(tree, "WorkingNotes"); + HTBTree_add(tree, "Windows"); + HTBTree_add(tree, "FineWWW"); + HTBTree_add(tree, "Frame"); + HTBTree_add(tree, "XMosaic"); + HTBTree_add(tree, "People"); + HTBTree_add(tree, "All"); + HTBTree_add(tree, "Curses"); + HTBTree_add(tree, "Erwise"); + HTBTree_add(tree, "Carl"); + HTBTree_add(tree, "MidasWWW"); + HTBTree_add(tree, "XPM"); + HTBTree_add(tree, "MailRobot"); + HTBTree_add(tree, "Illustrations"); + HTBTree_add(tree, "VMClient"); + HTBTree_add(tree, "XPA"); + HTBTree_add(tree, "Clients.html"); + HTBTree_add(tree, "Library"); + HTBTree_add(tree, "CERNLIB_Distribution"); + HTBTree_add(tree, "libHTML"); + HTBTree_add(tree, "WindowsPC"); + HTBTree_add(tree, "tkWWW"); + HTBTree_add(tree, "tk2.3"); + HTBTree_add(tree, "CVS-RCS"); + HTBTree_add(tree, "DecnetSockets"); + HTBTree_add(tree, "SGMLStream"); + HTBTree_add(tree, "NextStep"); + HTBTree_add(tree, "CVSRepository_old"); + HTBTree_add(tree, "ArthurSecret"); + HTBTree_add(tree, "CVSROOT"); + HTBTree_add(tree, "HytelnetGate"); + HTBTree_add(tree, "cern.www.new.src"); + HTBTree_add(tree, "Conditions"); + HTBTree_add(tree, "HTMLGate"); + HTBTree_add(tree, "Makefile"); + HTBTree_add(tree, "Newsgroups.html"); + HTBTree_add(tree, "People.html"); + HTBTree_add(tree, "Bugs.html"); + HTBTree_add(tree, "Summary.html"); + HTBTree_add(tree, "zDesignIssues.wn"); + HTBTree_add(tree, "HT.draw"); + HTBTree_add(tree, "HTandCERN.wn"); + HTBTree_add(tree, "Ideas.wn"); + HTBTree_add(tree, "MarkUp.wn"); + HTBTree_add(tree, "Proposal.html"); + HTBTree_add(tree, "SearchPanel.draw"); + HTBTree_add(tree, "Comments.wn"); + HTBTree_add(tree, "Xanadu.html"); + HTBTree_add(tree, "Storinglinks.html"); + HTBTree_add(tree, "TheW3Book.html"); + HTBTree_add(tree, "Talk_Feb-91.html"); + HTBTree_add(tree, "JFosterEntry.txt"); + HTBTree_add(tree, "Summary.txt"); + HTBTree_add(tree, "Bibliography.html"); + HTBTree_add(tree, "HTandCern.txt"); + HTBTree_add(tree, "Talk.draw"); + HTBTree_add(tree, "zDesignNotes.html"); + HTBTree_add(tree, "Link.html"); + HTBTree_add(tree, "Status.html"); + HTBTree_add(tree, "http.txt"); + HTBTree_add(tree, "People.html~"); + HTBTree_add(tree, "TAGS"); + HTBTree_add(tree, "summary.txt"); + HTBTree_add(tree, "Technical.html"); + HTBTree_add(tree, "Terms.html"); + HTBTree_add(tree, "JANETAccess.html"); + HTBTree_add(tree, "People.txt"); + HTBTree_add(tree, "README.txt"); + HTBTree_add(tree, "CodingStandards.html"); + HTBTree_add(tree, "Copyright.txt"); + HTBTree_add(tree, "Status_old.html"); + HTBTree_add(tree, "patches~"); + HTBTree_add(tree, "RelatedProducts.html"); + HTBTree_add(tree, "Implementation"); + HTBTree_add(tree, "History.html"); + HTBTree_add(tree, "Makefile.bak"); + HTBTree_add(tree, "Makefile.old"); + HTBTree_add(tree, "Policy.html"); + HTBTree_add(tree, "WhatIs.html"); + HTBTree_add(tree, "TheProject.html"); + HTBTree_add(tree, "Notation.html"); + HTBTree_add(tree, "Helping.html"); + HTBTree_add(tree, "Cyber-WWW.sit.Hqx"); + HTBTree_add(tree, "Glossary.html"); + HTBTree_add(tree, "maketags.html"); + HTBTree_add(tree, "IntroCS.html"); + HTBTree_add(tree, "Contrib"); + HTBTree_add(tree, "Help.html"); + HTBTree_add(tree, "CodeManagExec"); + HTBTree_add(tree, "HT-0.1draz"); + HTBTree_add(tree, "Cello"); + HTBTree_add(tree, "TOPUB"); + HTBTree_add(tree, "BUILD"); + HTBTree_add(tree, "BUILDALL"); + HTBTree_add(tree, "Lynx"); + HTBTree_add(tree, "ArthurLibrary"); + HTBTree_add(tree, "RashtyClient"); + HTBTree_add(tree, "#History.html#"); + HTBTree_add(tree, "PerlServers"); + HTBTree_add(tree, "modules"); + HTBTree_add(tree, "NCSA_httpd"); + HTBTree_add(tree, "MAIL2HTML"); + HTBTree_add(tree, "core"); + HTBTree_add(tree, "EmacsWWW"); +#ifdef BTREE_TRACE + printf("\nTreeTopObject=%s\n\n", tree->top->object); +#endif + next_element = HTBTree_next(tree, NULL); + while (next_element != NULL) { +#ifndef BTREE_TRACE + printf("The next element is %s\n", next_element->object); +#endif + next_element = HTBTree_next(tree, next_element); + } + HTBTree_free(tree); +} + +#endif diff --git a/WWW/Library/Implementation/HTBTree.h b/WWW/Library/Implementation/HTBTree.h new file mode 100644 index 0000000..a4f78f9 --- /dev/null +++ b/WWW/Library/Implementation/HTBTree.h @@ -0,0 +1,104 @@ +/* /Net/dxcern/userd/timbl/hypertext/WWW/Library/Implementation/HTBTree.html + BALANCED BINARY TREE FOR SORTING THINGS + + Tree creation, traversal and freeing. User-supplied comparison routine. + + Author: Arthur Secret, CERN. Public domain. Please mail bugs and changes to + www-request@info.cern.ch + + part of libWWW + + */ +#ifndef HTBTREE_H +#define HTBTREE_H 1 + +#ifndef HTUTILS_H +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif +/* + +Data structures + + */ typedef struct _HTBTree_element { + void *object; /* User object */ + struct _HTBTree_element *up; + struct _HTBTree_element *left; + int left_depth; + struct _HTBTree_element *right; + int right_depth; + } HTBTElement; + + typedef int (*HTComparer) (void *a, void *b); + + typedef struct _HTBTree_top { + HTComparer compare; + struct _HTBTree_element *top; + } HTBTree; + +/* + +Create a binary tree given its discrimination routine + + */ + extern HTBTree *HTBTree_new(HTComparer comp); + +/* + +Free storage of the tree but not of the objects + + */ + extern void HTBTree_free(HTBTree *tree); + +/* + +Free storage of the tree and of the objects + + */ + extern void HTBTreeAndObject_free(HTBTree *tree); + +/* + +Add an object to a binary tree + + */ + + extern void HTBTree_add(HTBTree *tree, void *object); + +/* + +Search an object in a binary tree + + returns Pointer to equivalent object in a tree or NULL if none. + */ + + extern void *HTBTree_search(HTBTree *tree, void *object); + +/* + +Find user object for element + + */ +#define HTBTree_object(element) ((element)->object) + +/* + +Find next element in depth-first order + + ON ENTRY, + + ele if NULL, start with leftmost element. if != 0 give next object to + the right. + + returns Pointer to element or NULL if none left. + + */ + extern HTBTElement *HTBTree_next(HTBTree *tree, HTBTElement *ele); + +#ifdef __cplusplus +} +#endif +#endif /* HTBTREE_H */ diff --git a/WWW/Library/Implementation/HTCJK.h b/WWW/Library/Implementation/HTCJK.h new file mode 100644 index 0000000..7edf50b --- /dev/null +++ b/WWW/Library/Implementation/HTCJK.h @@ -0,0 +1,121 @@ +/* + * $LynxId: HTCJK.h,v 1.22 2021/07/01 23:51:38 tom Exp $ + * + * CJK character converter HTCJK.h + * ======================= + * + * Added 11-Jun-96 by FM, based on jiscode.h for + * Yutaka Sato's (ysato@etl.go.jp) SJIS.c, and + * Takuya ASADA's (asada@three-a.co.jp) CJK patches. + * (see SGML.c). + * + */ + +#ifndef HTCJK_H +#define HTCJK_H + +#ifndef HTUTILS_H +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif +/* + * STATUS CHANGE CODES + */ +#define TO_2BCODE '$' +#define TO_1BCODE '(' +#define TO_KANA '\016' +#define TO_KANAOUT '\017' +#define TO_KANJI "\033$B" +#define TO_HANJI "\033$A" +#define TO_HANGUL "\033$(C" +#define TO_ASCII "\033(B" + +#define IS_GBK_LO(lo) ((0xA1 <= (lo)) && ((lo) <= 0xFE)) +#define IS_GBK_HI(hi) ((0xA1 <= (hi)) && ((hi) <= 0xF7)) + +#define IS_SJIS_LO(lo) ((0x40 <= (lo)) && ((lo) != 0x7F) && ((lo) <= 0xFC)) +#define IS_SJIS_HI1(hi) ((0x81 <= (hi)) && ((hi) <= 0x9F)) /* 1st lev. */ +#define IS_SJIS_HI2(hi) ((0xE0 <= (hi)) && ((hi) <= 0xEF)) /* 2nd lev. */ +#define IS_SJIS(hi,lo,in_sjis) (!IS_SJIS_LO(lo) ? 0 : IS_SJIS_HI1(hi) ? (in_sjis=1) : in_sjis && IS_SJIS_HI2(hi)) +#define IS_SJIS_2BYTE(hi,lo) (IS_SJIS_LO(lo) && (IS_SJIS_HI1(hi) || IS_SJIS_HI2(hi))) +#define IS_SJIS_X0201KANA(lo) ((0xA1 <= (lo)) && ((lo) <= 0xDF)) + +#define IS_EUC_LOS(lo) ((0x21 <= (lo)) && ((lo) <= 0x7E)) /* standard */ +#define IS_EUC_LOX(lo) ((0xA1 <= (lo)) && ((lo) <= 0xFE)) /* extended */ +#define IS_EUC_HI(hi) ((0xA1 <= (hi)) && ((hi) <= 0xFE)) +#define IS_EUC_X0201KANA(hi,lo) (((hi) == 0x8E) && (0xA1 <= (lo)) && ((lo) <= 0xDF)) +#define IS_EUC(hi,lo) ((IS_EUC_HI(hi) && IS_EUC_LOX(lo)) || IS_EUC_X0201KANA(hi,lo)) + +#define IS_JAPANESE_2BYTE(hi,lo) (IS_SJIS_2BYTE(hi,lo) || IS_EUC(hi,lo)) + +#define IS_BIG5_LOS(lo) ((0x40 <= (lo)) && ((lo) <= 0x7E)) /* standard */ +#define IS_BIG5_LOX(lo) ((0xA1 <= (lo)) && ((lo) <= 0xFE)) /* extended */ +#define IS_BIG5_HI(hi) ((0xA1 <= (hi)) && ((hi) <= 0xFE)) +#define IS_BIG5(hi,lo) (IS_BIG5_HI(hi) && (IS_BIG5_LOS(lo) || IS_BIG5_LOX(lo))) + + typedef enum { + NOKANJI = 0, EUC, SJIS, JIS + } HTkcode; + typedef enum { + NOCJK = 0, JAPANESE, CHINESE, KOREAN, TAIPEI + } HTCJKlang; + + extern HTCJKlang HTCJK; + +/* + * Function prototypes. + */ + extern void JISx0201TO0208_EUC(unsigned IHI, + unsigned ILO, + unsigned char *OHI, + unsigned char *OLO); + + extern unsigned char *SJIS_TO_JIS1(unsigned HI, + unsigned LO, + unsigned char *JCODE); + + extern unsigned char *JIS_TO_SJIS1(unsigned HI, + unsigned LO, + unsigned char *SJCODE); + + extern unsigned char *EUC_TO_SJIS1(unsigned HI, + unsigned LO, + register unsigned char *SJCODE); + + extern void JISx0201TO0208_SJIS(unsigned I, + unsigned char *OHI, + unsigned char *OLO); + + extern unsigned char *SJIS_TO_EUC1(unsigned HI, + unsigned LO, + unsigned char *EUCp); + + extern unsigned char *SJIS_TO_EUC(unsigned char *src, + unsigned char *dst); + + extern unsigned char *EUC_TO_SJIS(unsigned char *src, + unsigned char *dst); + + extern unsigned char *EUC_TO_JIS(unsigned char *src, + unsigned char *dst, + const char *toK, + const char *toA); + + extern unsigned char *TO_EUC(const unsigned char *jis, + unsigned char *euc); + + extern void TO_SJIS(const unsigned char *any, + unsigned char *sjis); + + extern void TO_JIS(const unsigned char *any, + unsigned char *jis); + + extern char *str_kcode(HTkcode code); + +#ifdef __cplusplus +} +#endif +#endif /* HTCJK_H */ diff --git a/WWW/Library/Implementation/HTChunk.c b/WWW/Library/Implementation/HTChunk.c new file mode 100644 index 0000000..b9490fd --- /dev/null +++ b/WWW/Library/Implementation/HTChunk.c @@ -0,0 +1,335 @@ +/* + * $LynxId: HTChunk.c,v 1.29 2023/04/10 22:58:51 tom Exp $ + * + * Chunk handling: Flexible arrays + * =============================== + * + */ + +#include +#include + +#include + +/* + * Initialize a chunk with a certain allocation unit + */ +void HTChunkInit(HTChunk *ch, int grow) +{ + ch->data = 0; + ch->growby = grow; + ch->size = 0; + ch->allocated = 0; +} + +/* Create a chunk with a certain allocation unit + * -------------- + */ +HTChunk *HTChunkCreate(int grow) +{ + HTChunk *ch = typecalloc(HTChunk); + + if (ch == NULL) + outofmem(__FILE__, "creation of chunk"); + + HTChunkInit(ch, grow); + return ch; +} + +HTChunk *HTChunkCreateMayFail(int grow, int failok) +{ + HTChunk *ch = typecalloc(HTChunk); + + if (ch == NULL) { + if (!failok) { + outofmem(__FILE__, "creation of chunk"); + } else { + return ch; + } + } + + HTChunkInit(ch, grow); + ch->failok = failok; + return ch; +} + +/* Create a chunk with a certain allocation unit and ensured size + * -------------- + */ +HTChunk *HTChunkCreate2(int grow, size_t needed) +{ + HTChunk *ch = typecalloc(HTChunk); + + if (ch == NULL) + outofmem(__FILE__, "HTChunkCreate2"); + + HTChunkInit(ch, grow); + if (needed-- > 0) { + /* Round up */ + ch->allocated = (int) (needed - (needed % (size_t) ch->growby) + + (unsigned) ch->growby); + CTRACE((tfp, "HTChunkCreate2: requested %d, allocate %u\n", + (int) needed, (unsigned) ch->allocated)); + ch->data = typecallocn(char, (unsigned) ch->allocated); + + if (!ch->data) + outofmem(__FILE__, "HTChunkCreate2 data"); + } + return ch; +} + +/* Clear a chunk of all data + * -------------------------- + */ +void HTChunkClear(HTChunk *ch) +{ + FREE(ch->data); + ch->size = 0; + ch->allocated = 0; +} + +/* Free a chunk (and it's chain, if any) + * ------------------------------------- + */ +void HTChunkFree(HTChunk *ch) +{ + HTChunk *next; + + do { + next = ch->next; + FREE(ch->data); + FREE(ch); + ch = next; + } while (ch != NULL); +} + +/* Realloc the chunk + * ----------------- + */ +BOOL HTChunkRealloc(HTChunk *ch, int growby) +{ + char *data; + + ch->allocated = ch->allocated + growby; + + data = (ch->data + ? typeRealloc(char, ch->data, ch->allocated) + : typecallocn(char, ch->allocated)); + + if (data) { + ch->data = data; + } else if (ch->failok) { + HTChunkClear(ch); /* allocation failed, clear all data - kw */ + return FALSE; /* caller should check ch->allocated - kw */ + } else { + outofmem(__FILE__, "HTChunkRealloc"); + } + return TRUE; +} + +/* Append a character + * ------------------ + */ +void HTChunkPutc(HTChunk *ch, unsigned c) +{ + if (ch->size >= ch->allocated) { + if (!HTChunkRealloc(ch, ch->growby)) + return; + } + ch->data[ch->size++] = (char) c; +} + +/* like above but no realloc: extend to another chunk if necessary */ +HTChunk *HTChunkPutc2(HTChunk *ch, int c) +{ + if (ch->size >= ch->allocated) { + HTChunk *chunk = HTChunkCreateMayFail(ch->growby, ch->failok); + + ch->next = chunk; + ch = chunk; + HTChunkPutc(ch, UCH(c)); + } else { + ch->data[ch->size++] = (char) c; + } + return ch; +} + +/* Ensure a certain size + * --------------------- + */ +void HTChunkEnsure(HTChunk *ch, int needed) +{ + if (needed <= ch->allocated) + return; + ch->allocated = needed - 1 - ((needed - 1) % ch->growby) + + ch->growby; /* Round up */ + ch->data = (ch->data + ? typeRealloc(char, ch->data, ch->allocated) + : typecallocn(char, ch->allocated)); + + if (ch->data == NULL) + outofmem(__FILE__, "HTChunkEnsure"); +} + +/* + * Append a block of characters. + */ +void HTChunkPutb(HTChunk *ch, const char *b, int l) +{ + if (l <= 0) + return; + if (ch->size + l > ch->allocated) { + int growby = l - (l % ch->growby) + ch->growby; /* Round up */ + + if (!HTChunkRealloc(ch, growby)) + return; + } + MemCpy(ch->data + ch->size, b, l); + ch->size += l; +} + +/* like above but no realloc: extend to another chunk if necessary */ +HTChunk *HTChunkPutb2(HTChunk *ch, const char *b, int l) +{ + if (l <= 0) + return ch; + if (ch->size + l > ch->allocated) { + HTChunk *chunk; + int m = ch->allocated - ch->size; + + if (m != 0 && b != 0) { + MemCpy(ch->data + ch->size, b, (unsigned) m); + ch->size += m; + } + + chunk = HTChunkCreateMayFail(ch->growby, ch->failok); + ch->next = chunk; + ch = chunk; + if (b != 0) + HTChunkPutb(ch, b + m, l - m); + } else { + MemCpy(ch->data + ch->size, b, (unsigned) l); + ch->size += l; + } + return ch; +} + +#define PUTC(code) ch->data[ch->size++] = (char)(code) +#define PUTC2(code) ch->data[ch->size++] = (char)(0x80|(0x3f &(code))) + +/* + * Append a character encoded as UTF-8. + */ +void HTChunkPutUtf8Char(HTChunk *ch, UCode_t code) +{ + int utflen; + + if (TOASCII(code) < 128) + utflen = 1; + else if (code < 0x800L) { + utflen = 2; + } else if (code < 0x10000L) { + utflen = 3; + } else if (code < 0x200000L) { + utflen = 4; + } else if (code < 0x4000000L) { + utflen = 5; + } else if (code <= 0x7fffffffL) { + utflen = 6; + } else + utflen = 0; + + if (ch->size + utflen > ch->allocated) { + int growby = (ch->growby >= utflen) ? ch->growby : utflen; + + if (!HTChunkRealloc(ch, growby)) + return; + } + + switch (utflen) { + case 0: + return; + case 1: + ch->data[ch->size++] = (char) code; + return; + case 2: + PUTC(0xc0 | (code >> 6)); + break; + case 3: + PUTC(0xe0 | (code >> 12)); + break; + case 4: + PUTC(0xf0 | (code >> 18)); + break; + case 5: + PUTC(0xf8 | (code >> 24)); + break; + case 6: + PUTC(0xfc | (code >> 30)); + break; + } + switch (utflen) { + case 6: + PUTC2(code >> 24); + /* FALLTHRU */ + case 5: + PUTC2(code >> 18); + /* FALLTHRU */ + case 4: + PUTC2(code >> 12); + /* FALLTHRU */ + case 3: + PUTC2(code >> 6); + /* FALLTHRU */ + case 2: + PUTC2(code); + break; + } +} + +/* Terminate a chunk + * ----------------- + */ +void HTChunkTerminate(HTChunk *ch) +{ + HTChunkPutc(ch, (char) 0); +} + +/* Append a string + * --------------- + */ +void HTChunkPuts(HTChunk *ch, const char *s) +{ + const char *p; + + if (s != NULL) { + for (p = s; *p; p++) { + if (ch->size >= ch->allocated) { + if (!HTChunkRealloc(ch, ch->growby)) + break; + } + ch->data[ch->size++] = *p; + } + } +} + +/* like above but no realloc: extend to another chunk if necessary */ +HTChunk *HTChunkPuts2(HTChunk *ch, const char *s) +{ + const char *p; + + if (s != NULL) { + for (p = s; *p; p++) { + if (ch->size >= ch->allocated) { + HTChunk *chunk = HTChunkCreateMayFail(ch->growby, ch->failok); + + ch->next = chunk; + ch = chunk; + HTChunkPuts(ch, p); + break; + } + ch->data[ch->size++] = *p; + } + } + return ch; +} diff --git a/WWW/Library/Implementation/HTChunk.h b/WWW/Library/Implementation/HTChunk.h new file mode 100644 index 0000000..fa51c99 --- /dev/null +++ b/WWW/Library/Implementation/HTChunk.h @@ -0,0 +1,228 @@ +/* + * $LynxId: HTChunk.h,v 1.21 2020/01/21 22:02:43 tom Exp $ + * + * HTChunk: Flexible array handling for libwww + * CHUNK HANDLING: + * FLEXIBLE ARRAYS + * + * This module implements a flexible array. It is a general utility module. A + * chunk is a structure which may be extended. These routines create and + * append data to chunks, automatically reallocating them as necessary. + * + */ +#ifndef HTCHUNK_H +#define HTCHUNK_H 1 + +#ifndef HTUTILS_H +#include +#endif + +#include + +#ifdef __cplusplus +extern "C" { +#endif + typedef struct _HTChunk HTChunk; + + struct _HTChunk { + int size; /* In bytes */ + int growby; /* Allocation unit in bytes */ + int allocated; /* Current size of *data */ + char *data; /* Pointer to malloc'd area or 0 */ + int failok; /* allowed to fail without exiting program? */ + HTChunk *next; /* pointer to the next chunk */ + }; + +/* + * Initialize a chunk's allocation data and allocation-increment. + */ + extern void HTChunkInit(HTChunk *ch, int grow); + +/* + * + * Create new chunk + * + * ON ENTRY, + * + * growby The number of bytes to allocate at a time when the chunk + * is later extended. Arbitrary but normally a trade-off + * of time vs memory. + * + * ON EXIT, + * + * returns A chunk pointer to the new chunk, + * + */ + + extern HTChunk *HTChunkCreate(int growby); + +/* + * Create a chunk for which an allocation error is not a fatal application + * error if failok != 0, but merely resets the chunk. When using a chunk + * created this way, the caller should always check whether the contents + * are ok each time after data have been appended. + * The create call may also fail and will return NULL in that case. - kw + */ + extern HTChunk *HTChunkCreateMayFail(int growby, int failok); + +/* + * Like HTChunkCreate but with initial allocation - kw + * + */ + extern HTChunk *HTChunkCreate2(int growby, size_t needed); + +/* + * + * Free a chunk + * + * ON ENTRY, + * + * ch A valid chunk pointer made by HTChunkCreate() + * + * ON EXIT, + * + * ch is invalid and may not be used. + * + */ + + extern void HTChunkFree(HTChunk *ch); + +/* + * + * Clear a chunk + * + * ON ENTRY, + * + * ch A valid chunk pointer made by HTChunkCreate() + * + * ON EXIT, + * + * *ch The size of the chunk is zero. + * + */ + + extern void HTChunkClear(HTChunk *ch); + +/* + * + * Realloc a chunk + * + * ON ENTRY, + * + * ch A valid chunk pointer made by HTChunkCreate() + * + * growby growby + * + * ON EXIT, + * + * *ch Expanded by growby + * + */ + + extern BOOL HTChunkRealloc(HTChunk *ch, int growby); + +/* + * + * Ensure a chunk has a certain space in + * + * ON ENTRY, + * + * ch A valid chunk pointer made by HTChunkCreate() + * + * s The size required + * + * ON EXIT, + * + * *ch Has size at least s + * + */ + + extern void HTChunkEnsure(HTChunk *ch, int s); + +/* + * + * Append a character to a chunk + * + * ON ENTRY, + * + * ch A valid chunk pointer made by HTChunkCreate() + * + * c The character to be appended + * + * ON EXIT, + * + * *ch Is one character bigger + * + */ + extern void HTChunkPutc(HTChunk *ch, unsigned c); + + extern void HTChunkPutb(HTChunk *ch, const char *b, int l); + + extern void HTChunkPutUtf8Char(HTChunk *ch, UCode_t code); + +/* + * Append a string to a chunk + * + * ON ENTRY, + * + * ch A valid chunk pointer made by HTChunkCreate() + * + * str Points to a zero-terminated string to be appended + * + * ON EXIT, + * + * *ch Is bigger by strlen(str) + * + */ + + extern void HTChunkPuts(HTChunk *ch, const char *str); + +/* + * + * Append a zero character to a chunk + * + */ + +/* + * + * ON ENTRY, + * + * ch A valid chunk pointer made by HTChunkCreate() + * + * ON EXIT, + * + * *ch Is one character bigger + * + */ + + extern void HTChunkTerminate(HTChunk *ch); + +/* like the above but no realloc: extend to another chunk if necessary */ +/* + * + * Append a character (string, data) to a chunk + * + * ON ENTRY, + * + * ch A valid chunk pointer made by HTChunkCreate() + * + * c The character to be appended + * + * ON EXIT, + * + * returns original chunk or a pointer to the new chunk + * (original chunk is referenced to the new one + * by the field 'next') + * + */ + extern HTChunk *HTChunkPutc2(HTChunk *ch, int c); + extern HTChunk *HTChunkPuts2(HTChunk *ch, const char *str); + extern HTChunk *HTChunkPutb2(HTChunk *ch, const char *b, int l); + +/* New pool infrastructure: UNlike the above, store data using alignment */ + extern HTChunk *HTChunkPutb0(HTChunk *ch, const char *b, int l); + +#ifdef __cplusplus +} +#endif +#endif /* HTCHUNK_H */ diff --git a/WWW/Library/Implementation/HTDOS.c b/WWW/Library/Implementation/HTDOS.c new file mode 100644 index 0000000..84bff79 --- /dev/null +++ b/WWW/Library/Implementation/HTDOS.c @@ -0,0 +1,241 @@ +/* + * $LynxId: HTDOS.c,v 1.40 2013/11/28 11:11:05 tom Exp $ + * DOS specific routines + */ + +#include +#include +#include +#include + +#include + +#ifdef _WINDOWS +#include +#include +#endif + +/* + * Make a copy of the source argument in the result, allowing some extra + * space so we can append directly onto the result without reallocating. + */ +static char *copy_plus(char **result, const char *source) +{ + int length = (int) strlen(source); + int extra = 10; + int n; + + for (n = 0; n < length; ++n) { + if (source[n] == ' ') + ++extra; + } + + HTSprintf0(result, "%-*s", length + extra, source); + (*result)[length] = 0; + return (*result); +} + +/* PUBLIC HTDOS_wwwName() + * CONVERTS DOS Name into WWW Name + * ON ENTRY: + * dosname DOS file specification (NO NODE) + * + * ON EXIT: + * returns WWW file specification + * + */ +const char *HTDOS_wwwName(const char *dosname) +{ + static char *wwwname = NULL; + char *cp_url = copy_plus(&wwwname, dosname); + int wwwname_len; + char ch; + + while ((ch = *dosname) != '\0') { + switch (ch) { + case '\\': + /* convert dos backslash to unix-style */ + *cp_url++ = '/'; + break; + case ' ': + *cp_url++ = '%'; + *cp_url++ = '2'; + *cp_url++ = '0'; + break; + default: + *cp_url++ = ch; + break; + } + dosname++; + } + *cp_url = '\0'; + + wwwname_len = (int) strlen(wwwname); + if (wwwname_len > 1) + cp_url--; /* point last char */ + + if (wwwname_len > 3 && *cp_url == '/') { + cp_url++; + *cp_url = '\0'; + } + return (wwwname); +} + +/* + * Convert slashes from Unix to DOS + */ +char *HTDOS_slashes(char *path) +{ + char *s; + + for (s = path; *s != '\0'; ++s) { + if (*s == '/') { + *s = '\\'; + } + } + return path; +} + +/* PUBLIC HTDOS_name() + * CONVERTS WWW name into a DOS name + * ON ENTRY: + * wwwname WWW file name + * + * ON EXIT: + * returns DOS file specification + */ +char *HTDOS_name(const char *wwwname) +{ + static char *result = NULL; + int joe; + +#if defined(SH_EX) /* 2000/03/07 (Tue) 18:32:42 */ + if (unsafe_filename(wwwname)) { + HTUserMsg2("unsafe filename : %s", wwwname); + copy_plus(&result, "BAD_LOCAL_FILE_NAME"); + } else { + copy_plus(&result, wwwname); + } +#else + copy_plus(&result, wwwname); +#endif +#ifdef __DJGPP__ + if (result[0] == '/' + && result[1] == 'd' + && result[2] == 'e' + && result[3] == 'v' + && result[4] == '/' + && isalpha(result[5])) { + return (result); + } +#endif /* __DJGPP__ */ + + (void) HTDOS_slashes(result); + + /* pesky leading slash, rudiment from file://localhost/ */ + /* the rest of path may be with or without drive letter */ + if ((result[1] != '\\') && (result[0] == '\\')) { + for (joe = 0; (result[joe] = result[joe + 1]) != 0; joe++) ; + } + /* convert '|' after the drive letter to ':' */ + if (isalpha(UCH(result[0])) && result[1] == '|') { + result[1] = ':'; + } +#ifdef _WINDOWS /* 1998/04/02 (Thu) 08:59:48 */ + if (LYLastPathSep(result) != NULL + && !LYIsDosDrive(result)) { + char temp_buff[LY_MAXPATH]; + + sprintf(temp_buff, "%.3s\\%.*s", windows_drive, + (int) (sizeof(temp_buff) - 5), result); + StrAllocCopy(result, temp_buff); + } +#endif + /* + * If we have only a device, add a trailing slash. Otherwise it just + * refers to the current directory on the given device. + */ + if (LYLastPathSep(result) == NULL + && LYIsDosDrive(result)) + LYAddPathSep0(result); + + CTRACE((tfp, "HTDOS_name changed `%s' to `%s'\n", wwwname, result)); + return (result); +} + +#ifdef WIN_EX +char *HTDOS_short_name(const char *path) +{ + static char sbuf[LY_MAXPATH]; + char *ret; + DWORD r; + + if (StrChr(path, '/')) + path = HTDOS_name(path); + r = GetShortPathName(path, sbuf, sizeof sbuf); + if (r >= sizeof(sbuf) || r == 0) { + ret = LYStrNCpy(sbuf, path, sizeof(sbuf)); + } else { + ret = sbuf; + } + return ret; +} +#endif + +#if defined(DJGPP) +/* + * Poll tcp/ip lib and yield to DPMI-host while nothing in + * keyboard buffer (head = tail) (simpler than kbhit). + * This is required to be able to finish off dead sockets, + * answer pings etc. + */ +#include +#include +#include +#include + +void djgpp_idle_loop(void) +{ + while (_farpeekw(_dos_ds, 0x41a) == _farpeekw(_dos_ds, 0x41c)) { + tcp_tick(NULL); + __dpmi_yield(); +#if defined(USE_SLANG) + if (SLang_input_pending(1)) + break; +#endif + } +} + +/* PUBLIC getxkey() + * Replaces libc's getxkey() with polling of tcp/ip + * library (WatTcp or Watt-32). * + * ON EXIT: + * returns extended keypress. + */ + +/* Copyright (C) 1995 DJ Delorie, see COPYING.DJ for details */ + +int getxkey(void) +{ +#if defined(DJGPP_KEYHANDLER) + __dpmi_regs r; + + djgpp_idle_loop(); + + r.h.ah = 0x10; + __dpmi_int(0x16, &r); + + if (r.h.al == 0x00) + return 0x0100 | r.h.ah; + if (r.h.al == 0xe0) + return 0x0200 | r.h.ah; + return r.h.al; + +#elif defined(USE_SLANG) + djgpp_idle_loop(); + return SLkp_getkey(); +#else + /* PDcurses uses myGetChar() in LYString.c */ +#endif +} +#endif /* DJGPP */ diff --git a/WWW/Library/Implementation/HTDOS.h b/WWW/Library/Implementation/HTDOS.h new file mode 100644 index 0000000..e1613cb --- /dev/null +++ b/WWW/Library/Implementation/HTDOS.h @@ -0,0 +1,56 @@ +/* + * $LynxId: HTDOS.h,v 1.14 2009/09/09 00:16:06 tom Exp $ + * + * DOS specific routines + */ + +#ifndef HTDOS_H +#define HTDOS_H + +#ifndef HTUTILS_H +#include +#endif /* HTUTILS_H */ + +/* PUBLIC HTDOS_wwwName() + * CONVERTS DOS Name into WWW Name + * ON ENTRY: + * dosname DOS file specification (NO NODE) + * + * ON EXIT: + * returns WWW file specification + * + */ +const char *HTDOS_wwwName(const char *dosname); + +/* + * Converts Unix slashes to DOS + */ +char *HTDOS_slashes(char *path); + +/* PUBLIC HTDOS_name() + * CONVERTS WWW name into a DOS name + * ON ENTRY: + * wwwname WWW file name + * + * ON EXIT: + * returns DOS file specification + * + * Bug: Returns pointer to static -- non-reentrant + */ +char *HTDOS_name(const char *wwwname); + +#ifdef WIN_EX +char *HTDOS_short_name(const char *fn); + +#else +#define HTDOS_short_name(fn) fn +#endif + +#ifdef DJGPP +/* + * Poll tcp/ip lib and yield to DPMI-host while nothing in + * keyboard buffer (head = tail) (simpler than kbhit). + */ +void djgpp_idle_loop(void); +#endif +#endif /* HTDOS_H */ diff --git a/WWW/Library/Implementation/HTFTP.c b/WWW/Library/Implementation/HTFTP.c new file mode 100644 index 0000000..decf559 --- /dev/null +++ b/WWW/Library/Implementation/HTFTP.c @@ -0,0 +1,4177 @@ +/* + * $LynxId: HTFTP.c,v 1.148 2023/01/05 09:17:15 tom Exp $ + * + * File Transfer Protocol (FTP) Client + * for a WorldWideWeb browser + * =================================== + * + * A cache of control connections is kept. + * + * Note: Port allocation + * + * It is essential that the port is allocated by the system, rather + * than chosen in rotation by us (POLL_PORTS), or the following + * problem occurs. + * + * It seems that an attempt by the server to connect to a port which has + * been used recently by a listen on the same socket, or by another + * socket this or another process causes a hangup of (almost exactly) + * one minute. Therefore, we have to use a rotating port number. + * The problem remains that if the application is run twice in quick + * succession, it will hang for what remains of a minute. + * + * Authors + * TBL Tim Berners-lee + * DD Denis DeLaRoca 310 825-4580 + * LM Lou Montulli + * FM Foteos Macrides + * History: + * 2 May 91 Written TBL, as a part of the WorldWideWeb project. + * 15 Jan 92 Bug fix: close() was used for NETCLOSE for control soc + * 10 Feb 92 Retry if cached connection times out or breaks + * 8 Dec 92 Bug fix 921208 TBL after DD + * 17 Dec 92 Anon FTP password now just WWWuser@ suggested by DD + * fails on princeton.edu! + * 27 Dec 93 (FM) Fixed up so FTP now works with VMS hosts. Path + * must be Unix-style and cannot include the device + * or top directory. + * ?? ??? ?? (LM) Added code to prompt and send passwords for non + * anonymous FTP + * 25 Mar 94 (LM) Added code to recognize different ftp server types + * and code to parse dates and sizes on most hosts. + * 27 Mar 93 (FM) Added code for getting dates and sizes on VMS hosts. + * + * Notes: + * Portions Copyright 1994 Trustees of Dartmouth College + * Code for recognizing different FTP servers and + * parsing "ls -l" output taken from Macintosh Fetch + * program with permission from Jim Matthews, + * Dartmouth Software Development Team. + */ + +/* + * BUGS: @@@ Limit connection cache size! + * Error reporting to user. + * 400 & 500 errors are ack'ed by user with windows. + * Use configuration file for user names + * + * Note for portability this version does not use select() and + * so does not watch the control and data channels at the + * same time. + */ + +#include + +#include + +#include /* Implemented here */ +#include +#include +#include + +#define REPEAT_PORT /* Give the port number for each file */ +#define REPEAT_LISTEN /* Close each listen socket and open a new one */ + +/* define POLL_PORTS If allocation does not work, poll ourselves.*/ +#define LISTEN_BACKLOG 2 /* Number of pending connect requests (TCP) */ + +#define FIRST_TCP_PORT 1024 /* Region to try for a listening port */ +#define LAST_TCP_PORT 5999 + +#define LINE_LENGTH 256 + +#include +#include +#include /* For HTFileFormat() */ +#include +#include +#ifndef IPPORT_FTP +#define IPPORT_FTP 21 +#endif /* !IPORT_FTP */ + +#include +#include +#include +#include + +typedef struct _connection { + struct _connection *next; /* Link on list */ + int socket; /* Socket number for communication */ + BOOL is_binary; /* Binary mode? */ +} connection; + +/* Hypertext object building machinery +*/ +#include + +/* + * socklen_t is the standard, but there are many pre-standard variants. + * This ifdef works around a few of those cases. + * + * Information was obtained from header files on these platforms: + * AIX 4.3.2, 5.1 + * HPUX 10.20, 11.00, 11.11 + * IRIX64 6.5 + * Tru64 4.0G, 4.0D, 5.1 + */ +#if defined(SYS_IRIX64) + /* IRIX64 6.5 socket.h may use socklen_t if SGI_SOURCE is not defined */ +# if _NO_XOPEN4 && _NO_XOPEN5 +# define LY_SOCKLEN socklen_t +# elif _ABIAPI +# define LY_SOCKLEN int +# elif _XOPEN5 +# if (_MIPS_SIM != _ABIO32) +# define LY_SOCKLEN socklen_t +# else +# define LY_SOCKLEN int +# endif +# else +# define LY_SOCKLEN size_t +# endif +#elif defined(SYS_HPUX) +# if defined(_XOPEN_SOURCE_EXTENDED) && defined(SO_PROTOTYPE) +# define LY_SOCKLEN socklen_t +# else /* HPUX 10.20, etc. */ +# define LY_SOCKLEN int +# endif +#elif defined(SYS_TRU64) +# if defined(_POSIX_PII_SOCKET) +# define LY_SOCKLEN socklen_t +# elif defined(_XOPEN_SOURCE_EXTENDED) +# define LY_SOCKLEN size_t +# else +# define LY_SOCKLEN int +# endif +#else +# define LY_SOCKLEN socklen_t +#endif + +#define PUTC(c) (*target->isa->put_character) (target, c) +#define PUTS(s) (*target->isa->put_string) (target, s) +#define START(e) (*target->isa->start_element) (target, e, 0, 0, -1, 0) +#define END(e) (*target->isa->end_element) (target, e, 0) +#define FREE_TARGET (*target->isa->_free) (target) +#define ABORT_TARGET (*target->isa->_free) (target) + +#define TRACE_ENTRY(tag, entry_info) \ + CTRACE((tfp, "HTFTP: %s filename: %s date: %s size: %" PRI_off_t "\n", \ + tag, \ + entry_info->filename, \ + NonNull(entry_info->date), \ + CAST_off_t(entry_info->size))) + +struct _HTStructured { + const HTStructuredClass *isa; + /* ... */ +}; + +/* Global Variables + * --------------------- + */ +int HTfileSortMethod = FILE_BY_NAME; + +#ifndef DISABLE_FTP /*This disables everything to end-of-file */ +static char ThisYear[8]; +static char LastYear[8]; +static int TheDate; +static BOOLEAN HaveYears = FALSE; + +/* Module-Wide Variables + * --------------------- + */ +static connection *connections = NULL; /* Linked list of connections */ +static char response_text[LINE_LENGTH + 1]; /* Last response from ftp host */ +static connection *control = NULL; /* Current connection */ +static int data_soc = -1; /* Socket for data transfer =invalid */ +static char *user_entered_password = NULL; +static char *last_username_and_host = NULL; + +/* + * Some ftp servers are known to have a broken implementation of RETR. If + * asked to retrieve a directory, they get confused and fail subsequent + * commands such as CWD and LIST. + */ +static int Broken_RETR = FALSE; + +/* + * Some ftp servers are known to have a broken implementation of EPSV. The + * server will hang for a long time when we attempt to connect after issuing + * this command. + */ +#ifdef INET6 +static int Broken_EPSV = FALSE; +#endif + +typedef enum { + GENERIC_SERVER + ,MACHTEN_SERVER + ,UNIX_SERVER + ,VMS_SERVER + ,CMS_SERVER + ,DCTS_SERVER + ,TCPC_SERVER + ,PETER_LEWIS_SERVER + ,NCSA_SERVER + ,WINDOWS_NT_SERVER + ,WINDOWS_2K_SERVER + ,MS_WINDOWS_SERVER + ,MSDOS_SERVER + ,APPLESHARE_SERVER + ,NETPRESENZ_SERVER + ,DLS_SERVER +} eServerType; + +static eServerType server_type = GENERIC_SERVER; /* the type of ftp host */ +static int unsure_type = FALSE; /* sure about the type? */ +static BOOLEAN use_list = FALSE; /* use the LIST command? */ + +static int interrupted_in_next_data_char = FALSE; + +#ifdef POLL_PORTS +static PortNumber port_number = FIRST_TCP_PORT; +#endif /* POLL_PORTS */ + +static BOOL have_socket = FALSE; /* true if master_socket is valid */ +static LYNX_FD master_socket; /* Listening socket = invalid */ + +static char *port_command; /* Command for setting the port */ +static fd_set open_sockets; /* Mask of active channels */ +static LYNX_FD num_sockets; /* Number of sockets to scan */ +static PortNumber passive_port; /* Port server specified for data */ + +#define NEXT_CHAR HTGetCharacter() /* Use function in HTFormat.c */ + +#define DATA_BUFFER_SIZE 2048 +static char data_buffer[DATA_BUFFER_SIZE]; /* Input data buffer */ +static char *data_read_pointer; +static char *data_write_pointer; + +#define NEXT_DATA_CHAR next_data_char() +static int close_connection(connection * con); + +#ifndef HAVE_ATOLL +off_t LYatoll(const char *value) +{ + off_t result = 0; + + while (*value != '\0') { + result = (result * 10) + (off_t) (*value++ - '0'); + } + return result; +} +#endif + +#ifdef LY_FIND_LEAKS +/* + * This function frees module globals. - FM + */ +static void free_FTPGlobals(void) +{ + FREE(user_entered_password); + FREE(last_username_and_host); + if (control) { + if (control->socket != -1) + close_connection(control); + FREE(control); + } +} +#endif /* LY_FIND_LEAKS */ + +/* PUBLIC HTVMS_name() + * CONVERTS WWW name into a VMS name + * ON ENTRY: + * nn Node Name (optional) + * fn WWW file name + * + * ON EXIT: + * returns vms file specification + * + * Bug: Returns pointer to static -- non-reentrant + */ +char *HTVMS_name(const char *nn, + const char *fn) +{ + /* We try converting the filename into Files-11 syntax. That is, we assume + * first that the file is, like us, on a VMS node. We try remote (or + * local) DECnet access. Files-11, VMS, VAX and DECnet are trademarks of + * Digital Equipment Corporation. The node is assumed to be local if the + * hostname WITHOUT DOMAIN matches the local one. @@@ + */ + static char *vmsname; + char *filename = (char *) malloc(strlen(fn) + 1); + char *nodename = (char *) malloc(strlen(nn) + 2 + 1); /* Copies to hack */ + char *second; /* 2nd slash */ + char *last; /* last slash */ + + const char *hostname = HTHostName(); + + if (!filename || !nodename) + outofmem(__FILE__, "HTVMSname"); + + strcpy(filename, fn); + strcpy(nodename, ""); /* On same node? Yes if node names match */ + if (StrNCmp(nn, "localhost", 9)) { + const char *p; + const char *q; + + for (p = hostname, q = nn; + *p && *p != '.' && *q && *q != '.'; p++, q++) { + if (TOUPPER(*p) != TOUPPER(*q)) { + char *r; + + strcpy(nodename, nn); + r = StrChr(nodename, '.'); /* Mismatch */ + if (r) + *r = '\0'; /* Chop domain */ + strcat(nodename, "::"); /* Try decnet anyway */ + break; + } + } + } + + second = StrChr(filename + 1, '/'); /* 2nd slash */ + last = strrchr(filename, '/'); /* last slash */ + + if (!second) { /* Only one slash */ + HTSprintf0(&vmsname, "%s%s", nodename, filename + 1); + } else if (second == last) { /* Exactly two slashes */ + *second = '\0'; /* Split filename from disk */ + HTSprintf0(&vmsname, "%s%s:%s", nodename, filename + 1, second + 1); + *second = '/'; /* restore */ + } else { /* More than two slashes */ + char *p; + + *second = '\0'; /* Split disk from directories */ + *last = '\0'; /* Split dir from filename */ + HTSprintf0(&vmsname, "%s%s:[%s]%s", + nodename, filename + 1, second + 1, last + 1); + *second = *last = '/'; /* restore filename */ + if ((p = StrChr(vmsname, '[')) != 0) { + while (*p != '\0' && *p != ']') { + if (*p == '/') + *p = '.'; /* Convert dir sep. to dots */ + ++p; + } + } + } + FREE(nodename); + FREE(filename); + return vmsname; +} + +/* Procedure: Read a character from the data connection + * ---------------------------------------------------- + */ +static int next_data_char(void) +{ + int status; + + if (data_read_pointer >= data_write_pointer) { + status = NETREAD(data_soc, data_buffer, DATA_BUFFER_SIZE); + if (status == HT_INTERRUPTED) + interrupted_in_next_data_char = 1; + if (status <= 0) + return EOF; + data_write_pointer = data_buffer + status; + data_read_pointer = data_buffer; + } +#ifdef NOT_ASCII + { + char c = *data_read_pointer++; + + return FROMASCII(c); + } +#else + return UCH(*data_read_pointer++); +#endif /* NOT_ASCII */ +} + +/* Close an individual connection + * + */ +static int close_connection(connection * con) +{ + connection *scan; + int status; + + CTRACE((tfp, "HTFTP: Closing control socket %d\n", con->socket)); + status = NETCLOSE(con->socket); + if (TRACE && status != 0) { +#ifdef UNIX + CTRACE((tfp, "HTFTP:close_connection: %s", LYStrerror(errno))); +#else + if (con->socket != INVSOC) + HTInetStatus("HTFTP:close_connection"); +#endif + } + con->socket = -1; + if (connections == con) { + connections = con->next; + return status; + } + for (scan = connections; scan; scan = scan->next) { + if (scan->next == con) { + scan->next = con->next; /* Unlink */ + if (control == con) + control = (connection *) 0; + return status; + } /*if */ + } /* for */ + return -1; /* very strange -- was not on list. */ +} + +static char *help_message_buffer = NULL; /* global :( */ + +static void init_help_message_cache(void) +{ + FREE(help_message_buffer); +} + +static void help_message_cache_add(char *string) +{ + if (help_message_buffer) + StrAllocCat(help_message_buffer, string); + else + StrAllocCopy(help_message_buffer, string); + + CTRACE((tfp, "Adding message to help cache: %s\n", string)); +} + +static char *help_message_cache_non_empty(void) +{ + return (help_message_buffer); +} + +static char *help_message_cache_contents(void) +{ + return (help_message_buffer); +} + +/* Send One Command + * ---------------- + * + * This function checks whether we have a control connection, and sends + * one command if given. + * + * On entry, + * control points to the connection which is established. + * cmd points to a command, or is zero to just get the response. + * + * The command should already be terminated with the CRLF pair. + * + * On exit, + * returns: 1 for success, + * or negative for communication failure (in which case + * the control connection will be closed). + */ +static int write_cmd(const char *cmd) +{ + int status; + + if (!control) { + CTRACE((tfp, "HTFTP: No control connection set up!!\n")); + return HT_NO_CONNECTION; + } + + if (cmd) { + CTRACE((tfp, " Tx: %s", cmd)); +#ifdef NOT_ASCII + { + char *p; + + for (p = cmd; *p; p++) { + *p = TOASCII(*p); + } + } +#endif /* NOT_ASCII */ + status = (int) NETWRITE(control->socket, cmd, (unsigned) strlen(cmd)); + if (status < 0) { + CTRACE((tfp, + "HTFTP: Error %d sending command: closing socket %d\n", + status, control->socket)); + close_connection(control); + return status; + } + } + return 1; +} + +/* + * For each string in the list, check if it is found in the response text. + * If so, return TRUE. + */ +static BOOL find_response(HTList *list) +{ + BOOL result = FALSE; + HTList *p = list; + char *value; + + while ((value = (char *) HTList_nextObject(p)) != NULL) { + if (LYstrstr(response_text, value)) { + result = TRUE; + break; + } + } + return result; +} + +/* Execute Command and get Response + * -------------------------------- + * + * See the state machine illustrated in RFC959, p57. This implements + * one command/reply sequence. It also interprets lines which are to + * be continued, which are marked with a "-" immediately after the + * status code. + * + * Continuation then goes on until a line with a matching reply code + * an a space after it. + * + * On entry, + * control points to the connection which is established. + * cmd points to a command, or is zero to just get the response. + * + * The command must already be terminated with the CRLF pair. + * + * On exit, + * returns: The first digit of the reply type, + * or negative for communication failure. + */ +static int response(const char *cmd) +{ + int result; /* Three-digit decimal code */ + int continuation_response = -1; + int status; + + if ((status = write_cmd(cmd)) < 0) + return status; + + do { + char *p = response_text; + + for (;;) { + int ich = NEXT_CHAR; + + if (((*p++ = (char) ich) == LF) + || (p == &response_text[LINE_LENGTH])) { + + char continuation; + + if (interrupted_in_htgetcharacter) { + CTRACE((tfp, + "HTFTP: Interrupted in HTGetCharacter, apparently.\n")); + NETCLOSE(control->socket); + control->socket = -1; + return HT_INTERRUPTED; + } + + *p = '\0'; /* Terminate the string */ + CTRACE((tfp, " Rx: %s", response_text)); + + /* Check for login or help messages */ + if (!StrNCmp(response_text, "230-", 4) || + !StrNCmp(response_text, "250-", 4) || + !StrNCmp(response_text, "220-", 4)) + help_message_cache_add(response_text + 4); + + sscanf(response_text, "%d%c", &result, &continuation); + if (continuation_response == -1) { + if (continuation == '-') /* start continuation */ + continuation_response = result; + } else { /* continuing */ + if (continuation_response == result && + continuation == ' ') + continuation_response = -1; /* ended */ + } + if (result == 220 && find_response(broken_ftp_retr)) { + Broken_RETR = TRUE; + CTRACE((tfp, "This server is broken (RETR)\n")); + } +#ifdef INET6 + if (result == 220 && find_response(broken_ftp_epsv)) { + Broken_EPSV = TRUE; + CTRACE((tfp, "This server is broken (EPSV)\n")); + } +#endif + break; + } + /* if end of line */ + if (interrupted_in_htgetcharacter) { + CTRACE((tfp, + "HTFTP: Interrupted in HTGetCharacter, apparently.\n")); + NETCLOSE(control->socket); + control->socket = -1; + return HT_INTERRUPTED; + } + + if (ich == EOF) { + CTRACE((tfp, "Error on rx: closing socket %d\n", + control->socket)); + strcpy(response_text, "000 *** TCP read error on response\n"); + close_connection(control); + return -1; /* End of file on response */ + } + } /* Loop over characters */ + + } while (continuation_response != -1); + + if (result == 421) { + CTRACE((tfp, "HTFTP: They close so we close socket %d\n", + control->socket)); + close_connection(control); + return -1; + } + if ((result == 255 && server_type == CMS_SERVER) && + (0 == strncasecomp(cmd, "CWD", 3) || + 0 == strcasecomp(cmd, "CDUP"))) { + /* + * Alas, CMS returns 255 on failure to CWD to parent of root. - PG + */ + result = 555; + } + return result / 100; +} + +static int send_cmd_1(const char *verb) +{ + char command[80]; + + sprintf(command, "%.*s%c%c", (int) sizeof(command) - 4, verb, CR, LF); + return response(command); +} + +static int send_cmd_2(const char *verb, const char *param) +{ + char *command = 0; + int status; + + HTSprintf0(&command, "%s %s%c%c", verb, param, CR, LF); + status = response(command); + FREE(command); + + return status; +} + +#define send_cwd(path) send_cmd_2("CWD", path) + +/* + * This function should try to set the macintosh server into binary mode. Some + * servers need an additional letter after the MACB command. + */ +static int set_mac_binary(eServerType ServerType) +{ + /* try to set mac binary mode */ + if (ServerType == APPLESHARE_SERVER || + ServerType == NETPRESENZ_SERVER) { + /* + * Presumably E means "Enable". - KW + */ + return (2 == response("MACB E\r\n")); + } else { + return (2 == response("MACB\r\n")); + } +} + +/* This function gets the current working directory to help + * determine what kind of host it is + */ + +static void get_ftp_pwd(eServerType *ServerType, BOOLEAN *UseList) +{ + char *cp; + + /* get the working directory (to see what it looks like) */ + int status = response("PWD\r\n"); + + if (status < 0) { + return; + } else { + cp = StrChr(response_text + 5, '"'); + if (cp) + *cp = '\0'; + if (*ServerType == TCPC_SERVER) { + *ServerType = ((response_text[5] == '/') ? + NCSA_SERVER : TCPC_SERVER); + CTRACE((tfp, "HTFTP: Treating as %s server.\n", + ((*ServerType == NCSA_SERVER) ? + "NCSA" : "TCPC"))); + } else if (response_text[5] == '/') { + /* path names beginning with / imply Unix, + * right? + */ + if (set_mac_binary(*ServerType)) { + *ServerType = NCSA_SERVER; + CTRACE((tfp, "HTFTP: Treating as NCSA server.\n")); + } else { + *ServerType = UNIX_SERVER; + *UseList = TRUE; + CTRACE((tfp, "HTFTP: Treating as Unix server.\n")); + } + return; + } else if (response_text[strlen(response_text) - 1] == ']') { + /* path names ending with ] imply VMS, right? */ + *ServerType = VMS_SERVER; + *UseList = TRUE; + CTRACE((tfp, "HTFTP: Treating as VMS server.\n")); + } else { + *ServerType = GENERIC_SERVER; + CTRACE((tfp, "HTFTP: Treating as Generic server.\n")); + } + + if ((*ServerType == NCSA_SERVER) || + (*ServerType == TCPC_SERVER) || + (*ServerType == PETER_LEWIS_SERVER) || + (*ServerType == NETPRESENZ_SERVER)) + set_mac_binary(*ServerType); + } +} + +/* This function turns MSDOS-like directory output off for + * Windows NT servers. + */ + +static void set_unix_dirstyle(eServerType *ServerType, BOOLEAN *UseList) +{ + char *cp; + + /* This is a toggle. It seems we have to toggle in order to see + * the current state (after toggling), so we may end up toggling + * twice. - kw + */ + int status = response("SITE DIRSTYLE\r\n"); + + if (status != 2) { + *ServerType = GENERIC_SERVER; + CTRACE((tfp, "HTFTP: DIRSTYLE failed, treating as Generic server.\n")); + return; + } else { + *UseList = TRUE; + /* Expecting one of: + * 200 MSDOS-like directory output is off + * 200 MSDOS-like directory output is on + * The following code doesn't look for the full exact string - + * who knows how the wording may change in some future version. + * If the first response isn't recognized, we toggle again + * anyway, under the assumption that it's more likely that + * the MSDOS setting was "off" originally. - kw + */ + cp = strstr(response_text + 4, "MSDOS"); + if (cp && strstr(cp, " off")) { + return; /* already off now. */ + } else { + response("SITE DIRSTYLE\r\n"); + } + } +} + +#define CheckForInterrupt(msg) \ + if (status == HT_INTERRUPTED) { \ + CTRACE((tfp, "HTFTP: Interrupted %s.\n", msg)); \ + _HTProgress(CONNECTION_INTERRUPTED); \ + NETCLOSE(control->socket); \ + control->socket = -1; \ + return HT_INTERRUPTED; \ + } + +/* Get a valid connection to the host + * ---------------------------------- + * + * On entry, + * arg points to the name of the host in a hypertext address + * On exit, + * returns <0 if error + * socket number if success + * + * This routine takes care of managing timed-out connections, and + * limiting the number of connections in use at any one time. + * + * It ensures that all connections are logged in if they exist. + * It ensures they have the port number transferred. + */ +static int get_connection(const char *arg, + HTParentAnchor *anchor) +{ + int status; + char *command = 0; + connection *con; + char *username = NULL; + char *password = NULL; + static BOOLEAN firstuse = TRUE; + + if (firstuse) { + /* + * Set up freeing at exit. - FM + */ +#ifdef LY_FIND_LEAKS + atexit(free_FTPGlobals); +#endif + firstuse = FALSE; + } + + if (control != 0) { + connection *next = control->next; + + if (control->socket != -1) { + NETCLOSE(control->socket); + } + memset(con = control, 0, sizeof(*con)); + con->next = next; + } else { + con = typecalloc(connection); + if (con == NULL) + outofmem(__FILE__, "get_connection"); + } + con->socket = -1; + + if (isEmpty(arg)) { + free(con); + return -1; /* Bad if no name specified */ + } + + /* Get node name: + */ + CTRACE((tfp, "get_connection(%s)\n", arg)); + { + char *p1 = HTParse(arg, "", PARSE_HOST); + char *p2 = strrchr(p1, '@'); /* user? */ + char *pw = NULL; + + if (p2 != NULL) { + username = p1; + *p2 = '\0'; /* terminate */ + p1 = p2 + 1; /* point to host */ + pw = StrChr(username, ':'); + if (pw != NULL) { + *pw++ = '\0'; + password = HTUnEscape(pw); + } + if (*username) + HTUnEscape(username); + + /* + * If the password doesn't exist then we are going to have to ask + * the user for it. The only problem is that we don't want to ask + * for it every time, so we will store away in a primitive fashion. + */ + if (!password) { + char *tmp = NULL; + + HTSprintf0(&tmp, "%s@%s", username, p1); + /* + * If the user@host is not equal to the last time through or + * user_entered_password has no data then we need to ask the + * user for the password. + */ + if (!last_username_and_host || + strcmp(tmp, last_username_and_host) || + !user_entered_password) { + + StrAllocCopy(last_username_and_host, tmp); + HTSprintf0(&tmp, gettext("Enter password for user %s@%s:"), + username, p1); + FREE(user_entered_password); + user_entered_password = HTPromptPassword(tmp, NULL); + + } /* else we already know the password */ + password = user_entered_password; + FREE(tmp); + } + } + + if (!username) + FREE(p1); + } /* scope of p1 */ + + status = HTDoConnect(arg, "FTP", IPPORT_FTP, (int *) &con->socket); + + if (status < 0) { + if (status == HT_INTERRUPTED) { + CTRACE((tfp, "HTFTP: Interrupted on connect\n")); + } else { + CTRACE((tfp, "HTFTP: Unable to connect to remote host for `%s'.\n", + arg)); + } + if (status == HT_INTERRUPTED) { + _HTProgress(CONNECTION_INTERRUPTED); + status = HT_NOT_LOADED; + } else { + HTAlert(gettext("Unable to connect to FTP host.")); + } + if (con->socket != -1) { + NETCLOSE(con->socket); + } + + FREE(username); + if (control == con) + control = NULL; + FREE(con); + return status; /* Bad return */ + } + + CTRACE((tfp, "FTP connected, socket %d control %p\n", + con->socket, (void *) con)); + control = con; /* Current control connection */ + + /* Initialise buffering for control connection */ + HTInitInput(control->socket); + init_help_message_cache(); /* Clear the login message buffer. */ + + /* Now we log in Look up username, prompt for pw. + */ + status = response(NULL); /* Get greeting */ + CheckForInterrupt("at beginning of login"); + + server_type = GENERIC_SERVER; /* reset */ + if (status == 2) { /* Send username */ + char *cp; /* look at greeting text */ + + /* don't gettext() this -- incoming text: */ + if (strlen(response_text) > 4) { + if ((cp = strstr(response_text, " awaits your command")) || + (cp = strstr(response_text, " ready."))) { + *cp = '\0'; + } + cp = response_text + 4; + if (!strncasecomp(cp, "NetPresenz", 10)) + server_type = NETPRESENZ_SERVER; + } else { + cp = response_text; + } + StrAllocCopy(anchor->server, cp); + + status = send_cmd_2("USER", (username && *username) + ? username + : "anonymous"); + + CheckForInterrupt("while sending username"); + } + if (status == 3) { /* Send password */ + if (non_empty(password)) { + HTSprintf0(&command, "PASS %s%c%c", password, CR, LF); + } else { + /* + * No password was given; use mail-address. + */ + const char *the_address; + char *user = NULL; + const char *host = NULL; + char *cp; + + the_address = anonftp_password; + if (isEmpty(the_address)) + the_address = personal_mail_address; + if (isEmpty(the_address)) + the_address = LYGetEnv("USER"); + if (isEmpty(the_address)) + the_address = "WWWuser"; + + StrAllocCopy(user, the_address); + if ((cp = StrChr(user, '@')) != NULL) { + *cp++ = '\0'; + if (*cp == '\0') + host = HTHostName(); + else + host = cp; + } else { + host = HTHostName(); + } + + /* + * If host is not fully qualified, suppress it + * as ftp.uu.net prefers a blank to a bad name + */ + if (!(host) || StrChr(host, '.') == NULL) + host = ""; + + HTSprintf0(&command, "PASS %s@%s%c%c", user, host, CR, LF); + FREE(user); + } + status = response(command); + FREE(command); + CheckForInterrupt("while sending password"); + } + FREE(username); + + if (status == 3) { + status = send_cmd_1("ACCT noaccount"); + CheckForInterrupt("while sending password"); + } + if (status != 2) { + CTRACE((tfp, "HTFTP: Login fail: %s", response_text)); + /* if (control->socket > 0) close_connection(control->socket); */ + return -1; /* Bad return */ + } + CTRACE((tfp, "HTFTP: Logged in.\n")); + + /* Check for host type */ + if (server_type != NETPRESENZ_SERVER) + server_type = GENERIC_SERVER; /* reset */ + use_list = FALSE; /* reset */ + if (response("SYST\r\n") == 2) { + /* we got a line -- what kind of server are we talking to? */ + if (StrNCmp(response_text + 4, + "UNIX Type: L8 MAC-OS MachTen", 28) == 0) { + server_type = MACHTEN_SERVER; + use_list = TRUE; + CTRACE((tfp, "HTFTP: Treating as MachTen server.\n")); + + } else if (strstr(response_text + 4, "UNIX") != NULL || + strstr(response_text + 4, "Unix") != NULL) { + server_type = UNIX_SERVER; + unsure_type = FALSE; /* to the best of out knowledge... */ + use_list = TRUE; + CTRACE((tfp, "HTFTP: Treating as Unix server.\n")); + + } else if (strstr(response_text + 4, "MSDOS") != NULL) { + server_type = MSDOS_SERVER; + use_list = TRUE; + CTRACE((tfp, "HTFTP: Treating as MSDOS (Unix emulation) server.\n")); + + } else if (StrNCmp(response_text + 4, "VMS", 3) == 0) { + char *tilde = strstr(arg, "/~"); + + use_list = TRUE; + if (tilde != 0 + && tilde[2] != 0 + && strstr(response_text + 4, "MadGoat") != 0) { + server_type = UNIX_SERVER; + CTRACE((tfp, "HTFTP: Treating VMS as UNIX server.\n")); + } else { + server_type = VMS_SERVER; + CTRACE((tfp, "HTFTP: Treating as VMS server.\n")); + } + + } else if ((StrNCmp(response_text + 4, "VM/CMS", 6) == 0) || + (StrNCmp(response_text + 4, "VM ", 3) == 0)) { + server_type = CMS_SERVER; + use_list = TRUE; + CTRACE((tfp, "HTFTP: Treating as CMS server.\n")); + + } else if (StrNCmp(response_text + 4, "DCTS", 4) == 0) { + server_type = DCTS_SERVER; + CTRACE((tfp, "HTFTP: Treating as DCTS server.\n")); + + } else if (strstr(response_text + 4, "MAC-OS TCP/Connect II") != NULL) { + server_type = TCPC_SERVER; + CTRACE((tfp, "HTFTP: Looks like a TCPC server.\n")); + get_ftp_pwd(&server_type, &use_list); + unsure_type = TRUE; + + } else if (server_type == NETPRESENZ_SERVER) { /* already set above */ + use_list = TRUE; + set_mac_binary(server_type); + CTRACE((tfp, "HTFTP: Treating as NetPresenz (MACOS) server.\n")); + + } else if (StrNCmp(response_text + 4, "MACOS Peter's Server", 20) == 0) { + server_type = PETER_LEWIS_SERVER; + use_list = TRUE; + set_mac_binary(server_type); + CTRACE((tfp, "HTFTP: Treating as Peter Lewis (MACOS) server.\n")); + + } else if (StrNCmp(response_text + 4, "Windows_NT", 10) == 0) { + server_type = WINDOWS_NT_SERVER; + CTRACE((tfp, "HTFTP: Treating as Window_NT server.\n")); + set_unix_dirstyle(&server_type, &use_list); + + } else if (StrNCmp(response_text + 4, "Windows2000", 11) == 0) { + server_type = WINDOWS_2K_SERVER; + CTRACE((tfp, "HTFTP: Treating as Window_2K server.\n")); + set_unix_dirstyle(&server_type, &use_list); + + } else if (StrNCmp(response_text + 4, "MS Windows", 10) == 0) { + server_type = MS_WINDOWS_SERVER; + use_list = TRUE; + CTRACE((tfp, "HTFTP: Treating as MS Windows server.\n")); + + } else if (StrNCmp(response_text + 4, + "MACOS AppleShare IP FTP Server", 30) == 0) { + server_type = APPLESHARE_SERVER; + use_list = TRUE; + set_mac_binary(server_type); + CTRACE((tfp, "HTFTP: Treating as AppleShare server.\n")); + + } else { + server_type = GENERIC_SERVER; + CTRACE((tfp, "HTFTP: Ugh! A Generic server.\n")); + get_ftp_pwd(&server_type, &use_list); + unsure_type = TRUE; + } + } else { + /* SYST fails :( try to get the type from the PWD command */ + get_ftp_pwd(&server_type, &use_list); + } + + return con->socket; /* Good return */ +} + +static void reset_master_socket(void) +{ + have_socket = FALSE; +} + +static void set_master_socket(int value) +{ + have_socket = (BOOLEAN) (value >= 0); + if (have_socket) + master_socket = (LYNX_FD) value; +} + +/* Close Master (listening) socket + * ------------------------------- + * + * + */ +static int close_master_socket(void) +{ + int status; + + if (have_socket) + FD_CLR(master_socket, &open_sockets); + + status = NETCLOSE((int) master_socket); + CTRACE((tfp, "HTFTP: Closed master socket %u\n", (unsigned) master_socket)); + + reset_master_socket(); + + if (status < 0) + return HTInetStatus(gettext("close master socket")); + else + return status; +} + +/* Open a master socket for listening on + * ------------------------------------- + * + * When data is transferred, we open a port, and wait for the server to + * connect with the data. + * + * On entry, + * have_socket Must be false, if master_socket is not setup already + * master_socket Must be negative if not set up already. + * On exit, + * Returns socket number if good + * less than zero if error. + * master_socket is socket number if good, else negative. + * port_number is valid if good. + */ +static int get_listen_socket(void) +{ + LY_SOCKADDR soc_A; + +#ifdef INET6 + unsigned short af; + LY_SOCKLEN slen; +#endif /* INET6 */ + int new_socket; /* Will be master_socket */ + + FD_ZERO(&open_sockets); /* Clear our record of open sockets */ + num_sockets = 0; + + FREE(port_command); +#ifndef REPEAT_LISTEN + if (have_socket) + return master_socket; /* Done already */ +#endif /* !REPEAT_LISTEN */ + +#ifdef INET6 + /* query address family of control connection */ + memset(&soc_A, 0, sizeof(soc_A)); + slen = (LY_SOCKLEN) sizeof(soc_A); + if (getsockname(control->socket, SOCKADDR_OF(soc_A), &slen) < 0) { + return HTInetStatus("getsockname failed"); + } + af = SOCKADDR_OF(soc_A)->sa_family; +#endif /* INET6 */ + +/* Create internet socket +*/ +#ifdef INET6 + new_socket = socket(af, SOCK_STREAM, IPPROTO_TCP); +#else + new_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); +#endif /* INET6 */ + + if (new_socket < 0) + return HTInetStatus(gettext("socket for master socket")); + + CTRACE((tfp, "HTFTP: Opened master socket number %d\n", new_socket)); + +/* Search for a free port. +*/ +#ifdef INET6 + memset(&soc_A, 0, sizeof(soc_A)); + SOCKADDR_OF(soc_A)->sa_family = (unsigned short) af; + switch (af) { + case AF_INET: +#ifdef SIN6_LEN + SOCKADDR_OF(soc_A)->sa_len = sizeof(struct sockaddr_in); +#endif /* SIN6_LEN */ + break; + case AF_INET6: +#ifdef SIN6_LEN + SOCKADDR_OF(soc_A)->sa_len = sizeof(struct sockaddr_in6); +#endif /* SIN6_LEN */ + break; + default: + HTInetStatus("AF"); + } +#else + soc_A.soc_in.sin_family = AF_INET; /* Family = internet, host order */ + soc_A.soc_in.sin_addr.s_addr = INADDR_ANY; /* Any peer address */ +#endif /* INET6 */ +#ifdef POLL_PORTS + { + PortNumber old_port_number = port_number; + + for (port_number = (old_port_number + 1);; port_number++) { + int status; + + if (port_number > LAST_TCP_PORT) + port_number = FIRST_TCP_PORT; + if (port_number == old_port_number) { + return HTInetStatus("bind"); + } +#ifdef INET6 + soc_A.soc_in.sin_port = htons(port_number); +#else + soc_A.sin_port = htons(port_number); +#endif /* INET6 */ +#ifdef SOCKS + if (socks_flag) + if ((status = Rbind(new_socket, + SOCKADDR_OF(soc_A), + SOCKADDR_LEN(soc_A))) == 0) { + break; + } else +#endif /* SOCKS */ + if ((status = bind(new_socket, + SOCKADDR_OF(soc_A), + SOCKADDR_LEN(soc_A) + )) == 0) { + break; + } + CTRACE((tfp, "TCP bind attempt to port %d yields %d, errno=%d\n", + port_number, status, SOCKET_ERRNO)); + } /* for */ + } +#else + { + int status; + LY_SOCKLEN address_length = (LY_SOCKLEN) sizeof(soc_A); + +#ifdef SOCKS + if (socks_flag) + status = Rgetsockname(control->socket, + SOCKADDR_OF(soc_A), + &address_length); + else +#endif /* SOCKS */ + status = getsockname(control->socket, + SOCKADDR_OF(soc_A), + &address_length); + if (status < 0) { + close(new_socket); + return HTInetStatus("getsockname"); + } + CTRACE((tfp, "HTFTP: This host is %s\n", + HTInetString((void *) &soc_A.soc_in))); + + soc_A.soc_in.sin_port = 0; /* Unspecified: please allocate */ +#ifdef SOCKS + if (socks_flag) + status = Rbind(new_socket, + SOCKADDR_OF(soc_A), + sizeof(soc_A)); + else +#endif /* SOCKS */ + status = bind(new_socket, + SOCKADDR_OF(soc_A), + SOCKADDR_LEN(soc_A)); + if (status < 0) { + close(new_socket); + return HTInetStatus("bind"); + } + + address_length = sizeof(soc_A); +#ifdef SOCKS + if (socks_flag) + status = Rgetsockname(new_socket, + SOCKADDR_OF(soc_A), + &address_length); + else +#endif /* SOCKS */ + status = getsockname(new_socket, + SOCKADDR_OF(soc_A), + &address_length); + if (status < 0) { + close(new_socket); + return HTInetStatus("getsockname"); + } + } +#endif /* POLL_PORTS */ + + CTRACE((tfp, "HTFTP: bound to port %d on %s\n", + (int) ntohs(soc_A.soc_in.sin_port), + HTInetString((void *) &soc_A.soc_in))); + +#ifdef REPEAT_LISTEN + if (have_socket) + (void) close_master_socket(); +#endif /* REPEAT_LISTEN */ + + set_master_socket(new_socket); + +/* Now we must find out who we are to tell the other guy +*/ + (void) HTHostName(); /* Make address valid - doesn't work */ +#ifdef INET6 + switch (SOCKADDR_OF(soc_A)->sa_family) { + case AF_INET: +#endif /* INET6 */ + HTSprintf0(&port_command, "PORT %d,%d,%d,%d,%d,%d%c%c", + (int) *((unsigned char *) (&soc_A.soc_in.sin_addr) + 0), + (int) *((unsigned char *) (&soc_A.soc_in.sin_addr) + 1), + (int) *((unsigned char *) (&soc_A.soc_in.sin_addr) + 2), + (int) *((unsigned char *) (&soc_A.soc_in.sin_addr) + 3), + (int) *((unsigned char *) (&soc_A.soc_in.sin_port) + 0), + (int) *((unsigned char *) (&soc_A.soc_in.sin_port) + 1), + CR, LF); + +#ifdef INET6 + break; + + case AF_INET6: + { + char hostbuf[MAXHOSTNAMELEN]; + char portbuf[MAXHOSTNAMELEN]; + + getnameinfo(SOCKADDR_OF(soc_A), + SOCKADDR_LEN(soc_A), + hostbuf, + (socklen_t) sizeof(hostbuf), + portbuf, + (socklen_t) sizeof(portbuf), + NI_NUMERICHOST | NI_NUMERICSERV); + HTSprintf0(&port_command, "EPRT |%d|%s|%s|%c%c", 2, hostbuf, portbuf, + CR, LF); + break; + } + default: + HTSprintf0(&port_command, "JUNK%c%c", CR, LF); + break; + } +#endif /* INET6 */ + if (port_command == NULL) + return -1; + + /* Inform TCP that we will accept connections + */ + { + int status; + +#ifdef SOCKS + if (socks_flag) + status = Rlisten((int) master_socket, 1); + else +#endif /* SOCKS */ + status = listen((int) master_socket, 1); + if (status < 0) { + reset_master_socket(); + return HTInetStatus("listen"); + } + } + CTRACE((tfp, "TCP: Master socket(), bind() and listen() all OK\n")); + FD_SET(master_socket, &open_sockets); + if ((master_socket + 1) > num_sockets) + num_sockets = master_socket + 1; + + return (int) master_socket; /* Good */ + +} /* get_listen_socket */ + +static const char *months[12] = +{ + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" +}; + +/* Procedure: Set the current and last year strings and date integer + * ----------------------------------------------------------------- + * + * Bug: + * This code is for sorting listings by date, if that option + * is selected in Lynx, and doesn't take into account time + * zones or ensure resetting at midnight, so the sort may not + * be perfect, but the actual date isn't changed in the display, + * i.e., the date is still correct. - FM + */ +static void set_years_and_date(void) +{ + char day[8], month[8], date[12]; + time_t NowTime; + int i; + char *printable; + + NowTime = time(NULL); + printable = ctime(&NowTime); + LYStrNCpy(day, printable + 8, 2); + if (day[0] == ' ') { + day[0] = '0'; + } + LYStrNCpy(month, printable + 4, 3); + for (i = 0; i < 12; i++) { + if (!strcasecomp(month, months[i])) { + break; + } + } + i++; + sprintf(date, "9999%02d%.2s", i % 100, day); + TheDate = atoi(date); + LYStrNCpy(ThisYear, printable + 20, 4); + sprintf(LastYear, "%d", (atoi(ThisYear) - 1) % 10000); + HaveYears = TRUE; +} + +typedef struct _EntryInfo { + char *filename; + char *linkname; /* symbolic link, if any */ + char *type; + char *date; + off_t size; + BOOLEAN display; /* show this entry? */ +#ifdef LONG_LIST + unsigned long file_links; + char *file_mode; + char *file_user; + char *file_group; +#endif +} EntryInfo; + +static void free_entryinfo_struct_contents(EntryInfo *entry_info) +{ + if (entry_info) { +#ifdef LONG_LIST + FREE(entry_info->file_mode); + FREE(entry_info->file_user); + FREE(entry_info->file_group); +#endif + FREE(entry_info->filename); + FREE(entry_info->linkname); + FREE(entry_info->type); + FREE(entry_info->date); + } + /* don't free the struct */ +} + +/* + * is_ls_date() -- + * Return TRUE if s points to a string of the form: + * "Sep 1 1990 " or + * "Sep 11 11:59 " or + * "Dec 12 1989 " or + * "FCv 23 1990 " ... + */ +static BOOLEAN is_ls_date(char *s) +{ + /* must start with three alpha characters */ + if (!isalpha(UCH(*s++)) || !isalpha(UCH(*s++)) || !isalpha(UCH(*s++))) + return FALSE; + + /* space or HT_NON_BREAK_SPACE */ + if (!(*s == ' ' || *s == HT_NON_BREAK_SPACE)) { + return FALSE; + } + s++; + + /* space or digit */ + if (!(*s == ' ' || isdigit(UCH(*s)))) { + return FALSE; + } + s++; + + /* digit */ + if (!isdigit(UCH(*s++))) + return FALSE; + + /* space */ + if (*s++ != ' ') + return FALSE; + + /* space or digit */ + if (!(*s == ' ' || isdigit(UCH(*s)))) { + return FALSE; + } + s++; + + /* digit */ + if (!isdigit(UCH(*s++))) + return FALSE; + + /* colon or digit */ + if (!(*s == ':' || isdigit(UCH(*s)))) { + return FALSE; + } + s++; + + /* digit */ + if (!isdigit(UCH(*s++))) + return FALSE; + + /* space or digit */ + if (!(*s == ' ' || isdigit(UCH(*s)))) { + return FALSE; + } + s++; + + /* space */ + if (*s != ' ') + return FALSE; + + return TRUE; +} /* is_ls_date() */ + +/* + * Extract the name, size, and date from an EPLF line. - 08-06-96 DJB + */ +static void parse_eplf_line(char *line, + EntryInfo *info) +{ + char *cp = line; + char ct[26]; + off_t size; + time_t secs; + static time_t base; /* time() value on this OS in 1970 */ + static int flagbase = 0; + + if (!flagbase) { + struct tm t; + + t.tm_year = 70; + t.tm_mon = 0; + t.tm_mday = 0; + t.tm_hour = 0; + t.tm_min = 0; + t.tm_sec = 0; + t.tm_isdst = -1; + base = mktime(&t); /* could return -1 */ + flagbase = 1; + } + + while (*cp) { + switch (*cp) { + case '\t': + StrAllocCopy(info->filename, cp + 1); + return; + case 's': + size = 0; + while (*(++cp) && (*cp != ',')) + size = (size * 10) + (off_t) (*cp - '0'); + info->size = size; + break; + case 'm': + secs = 0; + while (*(++cp) && (*cp != ',')) + secs = (secs * 10) + (*cp - '0'); + secs += base; /* assumes that time_t is #seconds */ + LYStrNCpy(ct, ctime(&secs), 24); + StrAllocCopy(info->date, ct); + break; + case '/': + StrAllocCopy(info->type, ENTRY_IS_DIRECTORY); + /* FALLTHRU */ + default: + while (*cp) { + if (*cp++ == ',') + break; + } + break; + } + } +} /* parse_eplf_line */ + +/* + * Extract the name, size, and date from an ls -l line. + */ +static void parse_ls_line(char *line, + EntryInfo *entry) +{ +#ifdef LONG_LIST + char *next; + char *cp; +#endif + int i, j; + off_t base = 1; + off_t size_num = 0; + + for (i = (int) strlen(line) - 1; + (i > 13) && (!isspace(UCH(line[i])) || !is_ls_date(&line[i - 12])); + i--) { + ; /* null body */ + } + line[i] = '\0'; + if (i > 13) { + StrAllocCopy(entry->date, &line[i - 12]); + /* replace the 4th location with nbsp if it is a space or zero */ + if (entry->date[4] == ' ' || entry->date[4] == '0') + entry->date[4] = HT_NON_BREAK_SPACE; + /* make sure year or time is flush right */ + if (entry->date[11] == ' ') { + for (j = 11; j > 6; j--) { + entry->date[j] = entry->date[j - 1]; + } + } + } + j = i - 14; + while (isdigit(UCH(line[j]))) { + size_num += ((off_t) (line[j] - '0') * base); + base *= 10; + j--; + } + entry->size = size_num; + StrAllocCopy(entry->filename, &line[i + 1]); + +#ifdef LONG_LIST + line[j] = '\0'; + + /* + * Extract the file-permissions, as a string. + */ + if ((cp = StrChr(line, ' ')) != 0) { + if ((cp - line) == 10) { + *cp = '\0'; + StrAllocCopy(entry->file_mode, line); + *cp = ' '; + } + + /* + * Next is the link-count. + */ + next = 0; + entry->file_links = (unsigned long) strtol(cp, &next, 10); + if (next == 0 || *next != ' ') { + entry->file_links = 0; + next = cp; + } else { + cp = next; + } + /* + * Next is the user-name. + */ + while (isspace(UCH(*cp))) + ++cp; + if ((next = StrChr(cp, ' ')) != 0) + *next = '\0'; + if (*cp != '\0') + StrAllocCopy(entry->file_user, cp); + /* + * Next is the group-name (perhaps). + */ + if (next != NULL) { + cp = (next + 1); + while (isspace(UCH(*cp))) + ++cp; + if ((next = StrChr(cp, ' ')) != 0) + *next = '\0'; + if (*cp != '\0') + StrAllocCopy(entry->file_group, cp); + } + } +#endif +} + +/* + * Extract the name and size info and whether it refers to a directory from a + * LIST line in "dls" format. + */ +static void parse_dls_line(char *line, + EntryInfo *entry_info, + char **pspilledname) +{ + short j; + int base = 1; + off_t size_num = 0; + int len; + char *cps = NULL; + + /* README 763 Information about this server\0 + bin/ - \0 + etc/ = \0 + ls-lR 0 \0 + ls-lR.Z 3 \0 + pub/ = Public area\0 + usr/ - \0 + morgan 14 -> ../real/morgan\0 + TIMIT.mostlikely.Z\0 + 79215 \0 + */ + + len = (int) strlen(line); + if (len == 0) { + FREE(*pspilledname); + entry_info->display = FALSE; + return; + } + cps = LYSkipNonBlanks(line); + if (*cps == '\0') { /* only a filename, save it and return. */ + StrAllocCopy(*pspilledname, line); + entry_info->display = FALSE; + return; + } + if (len < 24 || line[23] != ' ' || + (isspace(UCH(line[0])) && !*pspilledname)) { + /* this isn't the expected "dls" format! */ + if (!isspace(UCH(line[0]))) + *cps = '\0'; + if (*pspilledname && !*line) { + entry_info->filename = *pspilledname; + *pspilledname = NULL; + if (entry_info->filename[strlen(entry_info->filename) - 1] == '/') + StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY); + else + StrAllocCopy(entry_info->type, ""); + } else { + StrAllocCopy(entry_info->filename, line); + if (cps != line && *(cps - 1) == '/') + StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY); + else + StrAllocCopy(entry_info->type, ""); + FREE(*pspilledname); + } + return; + } + + j = 22; + if (line[j] == '=' || line[j] == '-') { + StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY); + } else { + while (isdigit(UCH(line[j]))) { + size_num += (line[j] - '0') * base; + base *= 10; + j--; + } + } + entry_info->size = size_num; + + cps = LYSkipBlanks(&line[23]); + if (!StrNCmp(cps, "-> ", 3) && cps[3] != '\0' && cps[3] != ' ') { + StrAllocCopy(entry_info->type, ENTRY_IS_SYMBOLIC_LINK); + StrAllocCopy(entry_info->linkname, LYSkipBlanks(cps + 3)); + entry_info->size = 0; /* don't display size */ + } + + if (j > 0) + line[j] = '\0'; + + LYTrimTrailing(line); + + len = (int) strlen(line); + if (len == 0 && *pspilledname && **pspilledname) { + line = *pspilledname; + len = (int) strlen(*pspilledname); + } + if (len > 0 && line[len - 1] == '/') { + /* + * It's a dir, remove / and mark it as such. + */ + if (len > 1) + line[len - 1] = '\0'; + if (!entry_info->type) + StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY); + } + + StrAllocCopy(entry_info->filename, line); + FREE(*pspilledname); +} /* parse_dls_line() */ + +/* + * parse_vms_dir_entry() + * Format the name, date, and size from a VMS LIST line + * into the EntryInfo structure - FM + */ +static void parse_vms_dir_entry(char *line, + EntryInfo *entry_info) +{ + int i, j; + off_t ialloc; + char *cp, *cpd, *cps, date[16]; + const char *sp = " "; + + /* Get rid of blank lines, and information lines. Valid lines have the ';' + * version number token. + */ + if (!strlen(line) || (cp = StrChr(line, ';')) == NULL) { + entry_info->display = FALSE; + return; + } + + /* Cut out file or directory name at VMS version number. */ + *cp++ = '\0'; + StrAllocCopy(entry_info->filename, line); + + /* Cast VMS non-README file and directory names to lowercase. */ + if (strstr(entry_info->filename, "READ") == NULL) { + LYLowerCase(entry_info->filename); + i = (int) strlen(entry_info->filename); + } else { + i = (int) ((strstr(entry_info->filename, "READ") + - entry_info->filename) + + 4); + if (!StrNCmp(&entry_info->filename[i], "ME", 2)) { + i += 2; + while (entry_info->filename[i] && entry_info->filename[i] != '.') { + i++; + } + } else if (!StrNCmp(&entry_info->filename[i], ".ME", 3)) { + i = (int) strlen(entry_info->filename); + } else { + i = 0; + } + LYLowerCase(entry_info->filename + i); + } + + /* Uppercase terminal .z's or _z's. */ + if ((--i > 2) && + entry_info->filename[i] == 'z' && + (entry_info->filename[i - 1] == '.' || + entry_info->filename[i - 1] == '_')) + entry_info->filename[i] = 'Z'; + + /* Convert any tabs in rest of line to spaces. */ + cps = cp - 1; + while ((cps = StrChr(cps + 1, '\t')) != NULL) + *cps = ' '; + + /* Collapse serial spaces. */ + i = 0; + j = 1; + cps = cp; + while (cps[j] != '\0') { + if (cps[i] == ' ' && cps[j] == ' ') + j++; + else + cps[++i] = cps[j++]; + } + cps[++i] = '\0'; + + /* Set the years and date, if we don't have them yet. * */ + if (!HaveYears) { + set_years_and_date(); + } + + /* Track down the date. */ + if ((cpd = StrChr(cp, '-')) != NULL && + strlen(cpd) > 9 && isdigit(UCH(*(cpd - 1))) && + isalpha(UCH(*(cpd + 1))) && *(cpd + 4) == '-') { + + /* Month */ + *(cpd + 2) = (char) TOLOWER(*(cpd + 2)); + *(cpd + 3) = (char) TOLOWER(*(cpd + 3)); + sprintf(date, "%.3s ", cpd + 1); + + /* Day */ + if (isdigit(UCH(*(cpd - 2)))) + sprintf(date + 4, "%.2s ", cpd - 2); + else + sprintf(date + 4, "%c%.1s ", HT_NON_BREAK_SPACE, cpd - 1); + + /* Time or Year */ + if (!StrNCmp(ThisYear, cpd + 5, 4) && + strlen(cpd) > 15 && *(cpd + 12) == ':') { + sprintf(date + 7, "%.5s", cpd + 10); + } else { + sprintf(date + 7, " %.4s", cpd + 5); + } + + StrAllocCopy(entry_info->date, date); + } + + /* Track down the size */ + if ((cpd = StrChr(cp, '/')) != NULL) { + /* Appears be in used/allocated format */ + cps = cpd; + while (isdigit(UCH(*(cps - 1)))) + cps--; + if (cps < cpd) + *cpd = '\0'; + entry_info->size = LYatoll(cps); + cps = cpd + 1; + while (isdigit(UCH(*cps))) + cps++; + *cps = '\0'; + ialloc = LYatoll(cpd + 1); + /* Check if used is in blocks or bytes */ + if (entry_info->size <= ialloc) + entry_info->size *= 512; + + } else if (strtok(cp, sp) != NULL) { + /* We just initialized on the version number */ + /* Now let's hunt for a lone, size number */ + while ((cps = strtok(NULL, sp)) != NULL) { + cpd = cps; + while (isdigit(UCH(*cpd))) + cpd++; + if (*cpd == '\0') { + /* Assume it's blocks */ + entry_info->size = (LYatoll(cps) * 512); + break; + } + } + } + + TRACE_ENTRY("VMS", entry_info); + return; +} /* parse_vms_dir_entry() */ + +/* + * parse_ms_windows_dir_entry() -- + * Format the name, date, and size from an MS_WINDOWS LIST line into + * the EntryInfo structure (assumes Chameleon NEWT format). - FM + */ +static void parse_ms_windows_dir_entry(char *line, + EntryInfo *entry_info) +{ + char *cp = line; + char *cps, *cpd, date[16]; + char *end = line + strlen(line); + + /* Get rid of blank or junk lines. */ + cp = LYSkipBlanks(cp); + if (!(*cp)) { + entry_info->display = FALSE; + return; + } + + /* Cut out file or directory name. */ + cps = LYSkipNonBlanks(cp); + *cps++ = '\0'; + cpd = cps; + StrAllocCopy(entry_info->filename, cp); + + /* Track down the size */ + if (cps < end) { + cps = LYSkipBlanks(cps); + cpd = LYSkipNonBlanks(cps); + *cpd++ = '\0'; + if (isdigit(UCH(*cps))) { + entry_info->size = LYatoll(cps); + } else { + StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY); + } + } else { + StrAllocCopy(entry_info->type, ""); + } + + /* Set the years and date, if we don't have them yet. * */ + if (!HaveYears) { + set_years_and_date(); + } + + /* Track down the date. */ + if (cpd < end) { + cpd = LYSkipBlanks(cpd); + if (strlen(cpd) > 17) { + *(cpd + 6) = '\0'; /* Month and Day */ + *(cpd + 11) = '\0'; /* Year */ + *(cpd + 17) = '\0'; /* Time */ + if (strcmp(ThisYear, cpd + 7)) + /* Not this year, so show the year */ + sprintf(date, "%.6s %.4s", cpd, (cpd + 7)); + else + /* Is this year, so show the time */ + sprintf(date, "%.6s %.5s", cpd, (cpd + 12)); + StrAllocCopy(entry_info->date, date); + if (entry_info->date[4] == ' ' || entry_info->date[4] == '0') { + entry_info->date[4] = HT_NON_BREAK_SPACE; + } + } + } + + TRACE_ENTRY("MS Windows", entry_info); + return; +} /* parse_ms_windows_dir_entry */ + +/* + * parse_windows_nt_dir_entry() -- + * Format the name, date, and size from a WINDOWS_NT LIST line into + * the EntryInfo structure (assumes Chameleon NEWT format). - FM + */ +#ifdef NOTDEFINED +static void parse_windows_nt_dir_entry(char *line, + EntryInfo *entry_info) +{ + char *cp = line; + char *cps, *cpd, date[16]; + char *end = line + strlen(line); + int i; + + /* Get rid of blank or junk lines. */ + cp = LYSkipBlanks(cp); + if (!(*cp)) { + entry_info->display = FALSE; + return; + } + + /* Cut out file or directory name. */ + cpd = cp; + cps = LYSkipNonBlanks(end - 1); + cp = (cps + 1); + if (!strcmp(cp, ".") || !strcmp(cp, "..")) { + entry_info->display = FALSE; + return; + } + StrAllocCopy(entry_info->filename, cp); + if (cps < cpd) + return; + *cp = '\0'; + end = cp; + + /* Set the years and date, if we don't have them yet. * */ + if (!HaveYears) { + set_years_and_date(); + } + + /* Cut out the date. */ + cp = cps = cpd; + cps = LYSkipNonBlanks(cps); + *cps++ = '\0'; + if (cps > end) { + entry_info->display = FALSE; + return; + } + cps = LYSkipBlanks(cps); + cpd = LYSkipNonBlanks(cps); + *cps++ = '\0'; + if (cps > end || cpd == cps || strlen(cpd) < 7) { + entry_info->display = FALSE; + return; + } + if (strlen(cp) == 8 && + isdigit(*cp) && isdigit(*(cp + 1)) && *(cp + 2) == '-' && + isdigit(*(cp + 3)) && isdigit(*(cp + 4)) && *(cp + 5) == '-') { + *(cp + 2) = '\0'; /* Month */ + i = atoi(cp) - 1; + *(cp + 5) = '\0'; /* Day */ + sprintf(date, "%.3s %.2s", months[i], (cp + 3)); + if (date[4] == '0') + date[4] = ' '; + cp += 6; /* Year */ + if (strcmp((ThisYear + 2), cp)) { + /* Not this year, so show the year */ + if (atoi(cp) < 70) { + sprintf(&date[6], " 20%.2s", cp); + } else { + sprintf(&date[6], " 19%.2s", cp); + } + } else { + /* Is this year, so show the time */ + *(cpd + 2) = '\0'; /* Hour */ + i = atoi(cpd); + if (*(cpd + 5) == 'P' || *(cpd + 5) == 'p') + i += 12; + sprintf(&date[6], " %02d:%.2s", i, (cpd + 3)); + } + StrAllocCopy(entry_info->date, date); + if (entry_info->date[4] == ' ' || entry_info->date[4] == '0') { + entry_info->date[4] = HT_NON_BREAK_SPACE; + } + } + + /* Track down the size */ + if (cps < end) { + cps = LYSkipBlanks(cps); + cpd = LYSkipNonBlanks(cps); + *cpd = '\0'; + if (isdigit(*cps)) { + entry_info->size = LYatoll(cps); + } else { + StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY); + } + } else { + StrAllocCopy(entry_info->type, ""); + } + + /* Wrap it up */ + CTRACE((tfp, "HTFTP: Windows NT filename: %s date: %s size: %d\n", + entry_info->filename, + NonNull(entry_info->date), + entry_info->size)); + return; +} /* parse_windows_nt_dir_entry */ +#endif /* NOTDEFINED */ + +/* + * parse_cms_dir_entry() -- + * Format the name, date, and size from a VM/CMS line into + * the EntryInfo structure. - FM + */ +static void parse_cms_dir_entry(char *line, + EntryInfo *entry_info) +{ + char *cp = line; + char *cps, *cpd, date[16]; + char *end = line + strlen(line); + int RecordLength = 0; + int Records = 0; + int i; + + /* Get rid of blank or junk lines. */ + cp = LYSkipBlanks(cp); + if (!(*cp)) { + entry_info->display = FALSE; + return; + } + + /* Cut out file or directory name. */ + cps = LYSkipNonBlanks(cp); + *cps++ = '\0'; + StrAllocCopy(entry_info->filename, cp); + if (StrChr(entry_info->filename, '.') != NULL) + /* If we already have a dot, we did an NLST. */ + return; + cp = LYSkipBlanks(cps); + if (!(*cp)) { + /* If we don't have more, we've misparsed. */ + FREE(entry_info->filename); + FREE(entry_info->type); + entry_info->display = FALSE; + return; + } + cps = LYSkipNonBlanks(cp); + *cps++ = '\0'; + if ((0 == strcasecomp(cp, "DIR")) && (cp - line) > 17) { + /* It's an SFS directory. */ + StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY); + entry_info->size = 0; + } else { + /* It's a file. */ + cp--; + *cp = '.'; + StrAllocCat(entry_info->filename, cp); + + /* Track down the VM/CMS RECFM or type. */ + cp = cps; + if (cp < end) { + cp = LYSkipBlanks(cp); + cps = LYSkipNonBlanks(cp); + *cps++ = '\0'; + /* Check cp here, if it's relevant someday. */ + } + } + + /* Track down the record length or dash. */ + cp = cps; + if (cp < end) { + cp = LYSkipBlanks(cp); + cps = LYSkipNonBlanks(cp); + *cps++ = '\0'; + if (isdigit(UCH(*cp))) { + RecordLength = atoi(cp); + } + } + + /* Track down the number of records or the dash. */ + cp = cps; + if (cps < end) { + cp = LYSkipBlanks(cp); + cps = LYSkipNonBlanks(cp); + *cps++ = '\0'; + if (isdigit(UCH(*cp))) { + Records = atoi(cp); + } + if (Records > 0 && RecordLength > 0) { + /* Compute an approximate size. */ + entry_info->size = ((off_t) Records * (off_t) RecordLength); + } + } + + /* Set the years and date, if we don't have them yet. */ + if (!HaveYears) { + set_years_and_date(); + } + + /* Track down the date. */ + cpd = cps; + if (((cps < end) && + (cps = StrChr(cpd, ':')) != NULL) && + (cps < (end - 3) && + isdigit(UCH(*(cps + 1))) && isdigit(UCH(*(cps + 2))) && *(cps + 3) == ':')) { + cps += 3; + *cps = '\0'; + if ((cps - cpd) >= 14) { + cpd = (cps - 14); + *(cpd + 2) = '\0'; /* Month */ + *(cpd + 5) = '\0'; /* Day */ + *(cpd + 8) = '\0'; /* Year */ + cps -= 5; /* Time */ + if (*cpd == ' ') + *cpd = '0'; + i = atoi(cpd) - 1; + sprintf(date, "%.3s %.2s", months[i], (cpd + 3)); + if (date[4] == '0') + date[4] = ' '; + cpd += 6; /* Year */ + if (strcmp((ThisYear + 2), cpd)) { + /* Not this year, so show the year. */ + if (atoi(cpd) < 70) { + sprintf(&date[6], " 20%.2s", cpd); + } else { + sprintf(&date[6], " 19%.2s", cpd); + } + } else { + /* Is this year, so show the time. */ + *(cps + 2) = '\0'; /* Hour */ + i = atoi(cps); + sprintf(&date[6], " %02d:%.2s", i, (cps + 3)); + } + StrAllocCopy(entry_info->date, date); + if (entry_info->date[4] == ' ' || entry_info->date[4] == '0') { + entry_info->date[4] = HT_NON_BREAK_SPACE; + } + } + } + + TRACE_ENTRY("VM/CMS", entry_info); + return; +} /* parse_cms_dir_entry */ + +/* + * Given a line of LIST/NLST output in entry, return results and a file/dir + * name in entry_info struct + * + * If first is true, this is the first name in a directory. + */ +static EntryInfo *parse_dir_entry(char *entry, + BOOLEAN *first, + char **pspilledname) +{ + EntryInfo *entry_info; + int i; + int len; + BOOLEAN remove_size = FALSE; + char *cp; + + entry_info = typecalloc(EntryInfo); + + if (entry_info == NULL) + outofmem(__FILE__, "parse_dir_entry"); + + entry_info->display = TRUE; + + switch (server_type) { + case DLS_SERVER: + + /* + * Interpret and edit LIST output from a Unix server in "dls" format. + * This one must have claimed to be Unix in order to get here; if the + * first line looks fishy, we revert to Unix and hope that fits better + * (this recovery is untested). - kw + */ + + if (*first) { + len = (int) strlen(entry); + if (!len || entry[0] == ' ' || + (len >= 24 && entry[23] != ' ') || + (len < 24 && StrChr(entry, ' '))) { + server_type = UNIX_SERVER; + CTRACE((tfp, + "HTFTP: Falling back to treating as Unix server.\n")); + } else { + *first = FALSE; + } + } + + if (server_type == DLS_SERVER) { + /* if still unchanged... */ + parse_dls_line(entry, entry_info, pspilledname); + + if (isEmpty(entry_info->filename)) { + entry_info->display = FALSE; + return (entry_info); + } + if (!strcmp(entry_info->filename, "..") || + !strcmp(entry_info->filename, ".")) + entry_info->display = FALSE; + if (entry_info->type && *entry_info->type == '\0') { + FREE(entry_info->type); + return (entry_info); + } + /* + * Goto the bottom and get real type. + */ + break; + } + /* fall through if server_type changed for *first == TRUE ! */ + /* FALLTHRU */ + case UNIX_SERVER: + case PETER_LEWIS_SERVER: + case MACHTEN_SERVER: + case MSDOS_SERVER: + case WINDOWS_NT_SERVER: + case WINDOWS_2K_SERVER: + case APPLESHARE_SERVER: + case NETPRESENZ_SERVER: + /* + * Check for EPLF output (local times). + */ + if (*entry == '+') { + parse_eplf_line(entry, entry_info); + break; + } + + /* + * Interpret and edit LIST output from Unix server. + */ + len = (int) strlen(entry); + if (*first) { + /* don't gettext() this -- incoming text: */ + if (!strcmp(entry, "can not access directory .")) { + /* + * Don't reset *first, nothing real will follow. - KW + */ + entry_info->display = FALSE; + return (entry_info); + } + *first = FALSE; + if (!StrNCmp(entry, "total ", 6) || + strstr(entry, "not available") != NULL) { + entry_info->display = FALSE; + return (entry_info); + } else if (unsure_type) { + /* this isn't really a unix server! */ + server_type = GENERIC_SERVER; + entry_info->display = FALSE; + return (entry_info); + } + } + + /* + * Check first character of ls -l output. + */ + if (TOUPPER(entry[0]) == 'D') { + /* + * It's a directory. + */ + StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY); + remove_size = TRUE; /* size is not useful */ + } else if (entry[0] == 'l') { + /* + * It's a symbolic link, does the user care about knowing if it is + * symbolic? I think so since it might be a directory. + */ + StrAllocCopy(entry_info->type, ENTRY_IS_SYMBOLIC_LINK); + remove_size = TRUE; /* size is not useful */ + + /* + * Strip off " -> pathname". + */ + for (i = len - 1; (i > 3) && + (!isspace(UCH(entry[i])) || + (entry[i - 1] != '>') || + (entry[i - 2] != '-') || + (entry[i - 3] != ' ')); i--) ; /* null body */ + if (i > 3) { + entry[i - 3] = '\0'; + StrAllocCopy(entry_info->linkname, LYSkipBlanks(entry + i)); + } + } + /* link */ + parse_ls_line(entry, entry_info); + + if (!strcmp(entry_info->filename, "..") || + !strcmp(entry_info->filename, ".")) + entry_info->display = FALSE; + /* + * Goto the bottom and get real type. + */ + break; + + case VMS_SERVER: + /* + * Interpret and edit LIST output from VMS server and convert + * information lines to zero length. + */ + parse_vms_dir_entry(entry, entry_info); + + /* + * Get rid of any junk lines. + */ + if (!entry_info->display) + return (entry_info); + + /* + * Trim off VMS directory extensions. + */ + len = (int) strlen(entry_info->filename); + if ((len > 4) && !strcmp(&entry_info->filename[len - 4], ".dir")) { + entry_info->filename[len - 4] = '\0'; + StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY); + remove_size = TRUE; /* size is not useful */ + } + /* + * Goto the bottom and get real type. + */ + break; + + case MS_WINDOWS_SERVER: + /* + * Interpret and edit LIST output from MS_WINDOWS server and convert + * information lines to zero length. + */ + parse_ms_windows_dir_entry(entry, entry_info); + + /* + * Get rid of any junk lines. + */ + if (!entry_info->display) + return (entry_info); + if (entry_info->type && *entry_info->type == '\0') { + FREE(entry_info->type); + return (entry_info); + } + /* + * Goto the bottom and get real type. + */ + break; + +#ifdef NOTDEFINED + case WINDOWS_NT_SERVER: + /* + * Interpret and edit LIST output from MS_WINDOWS server and convert + * information lines to zero length. + */ + parse_windows_nt_dir_entry(entry, entry_info); + + /* + * Get rid of any junk lines. + */ + if (!entry_info->display) + return (entry_info); + if (entry_info->type && *entry_info->type == '\0') { + FREE(entry_info->type); + return (entry_info); + } + /* + * Goto the bottom and get real type. + */ + break; +#endif /* NOTDEFINED */ + + case CMS_SERVER: + { + /* + * Interpret and edit LIST output from VM/CMS server and convert + * any information lines to zero length. + */ + parse_cms_dir_entry(entry, entry_info); + + /* + * Get rid of any junk lines. + */ + if (!entry_info->display) + return (entry_info); + if (entry_info->type && *entry_info->type == '\0') { + FREE(entry_info->type); + return (entry_info); + } + /* + * Goto the bottom and get real type. + */ + break; + } + + case NCSA_SERVER: + case TCPC_SERVER: + /* + * Directories identified by trailing "/" characters. + */ + StrAllocCopy(entry_info->filename, entry); + len = (int) strlen(entry); + if (entry[len - 1] == '/') { + /* + * It's a dir, remove / and mark it as such. + */ + entry[len - 1] = '\0'; + StrAllocCopy(entry_info->type, ENTRY_IS_DIRECTORY); + remove_size = TRUE; /* size is not useful */ + } + /* + * Goto the bottom and get real type. + */ + break; + + default: + /* + * We can't tell if it is a directory since we only did an NLST :( List + * bad file types anyways? NOT! + */ + StrAllocCopy(entry_info->filename, entry); + return (entry_info); /* mostly empty info */ + + } /* switch (server_type) */ + +#ifdef LONG_LIST + (void) remove_size; +#else + if (remove_size && entry_info->size) { + entry_info->size = 0; + } +#endif + + if (isEmpty(entry_info->filename)) { + entry_info->display = FALSE; + return (entry_info); + } + if (strlen(entry_info->filename) > 3) { + if (((cp = strrchr(entry_info->filename, '.')) != NULL && + 0 == strncasecomp(cp, ".me", 3)) && + (cp[3] == '\0' || cp[3] == ';')) { + /* + * Don't treat this as application/x-Troff-me if it's a Unix server + * but has the string "read.me", or if it's not a Unix server. - + * FM + */ + if ((server_type != UNIX_SERVER) || + (cp > (entry_info->filename + 3) && + 0 == strncasecomp((cp - 4), "read.me", 7))) { + StrAllocCopy(entry_info->type, STR_PLAINTEXT); + } + } + } + + /* + * Get real types eventually. + */ + if (!entry_info->type) { + const char *cp2; + HTFormat format; + HTAtom *encoding; /* @@ not used at all */ + + format = HTFileFormat(entry_info->filename, &encoding, &cp2); + + if (cp2 == NULL) { + if (!StrNCmp(HTAtom_name(format), "application", 11)) { + cp2 = HTAtom_name(format) + 12; + if (!StrNCmp(cp2, "x-", 2)) + cp2 += 2; + } else { + cp2 = HTAtom_name(format); + } + } + + StrAllocCopy(entry_info->type, cp2); + } + + return (entry_info); +} + +static void formatDate(char target[16], EntryInfo *entry) +{ + char temp[8], month[4]; + int i; + + /* + * Set up for sorting in reverse chronological order. - FM + */ + if (entry->date[9] == ':') { + strcpy(target, "9999"); + LYStrNCpy(temp, &entry->date[7], 5); + if (temp[0] == ' ') { + temp[0] = '0'; + } + } else { + LYStrNCpy(target, &entry->date[8], 4); + strcpy(temp, "00:00"); + } + LYStrNCpy(month, entry->date, 3); + for (i = 0; i < 12; i++) { + if (!strcasecomp(month, months[i])) { + break; + } + } + i++; + sprintf(month, "%02d", i % 100); + strcat(target, month); + StrNCat(target, &entry->date[4], 2); + if (target[6] == ' ' || target[6] == HT_NON_BREAK_SPACE) { + target[6] = '0'; + } + + /* If no year given, assume last year if it would otherwise be in the + * future by more than one day. The one day tolerance is to account for a + * possible timezone difference. - kw + */ + if (target[0] == '9' && atoi(target) > TheDate + 1) { + for (i = 0; i < 4; i++) { + target[i] = LastYear[i]; + } + } + strcat(target, temp); +} + +static int compare_EntryInfo_structs(EntryInfo *entry1, EntryInfo *entry2) +{ + int status; + char date1[16], date2[16]; + int result = strcmp(entry1->filename, entry2->filename); + + switch (HTfileSortMethod) { + case FILE_BY_SIZE: + /* both equal or both 0 */ + if (entry1->size > entry2->size) + result = 1; + else if (entry1->size < entry2->size) + result = -1; + break; + + case FILE_BY_TYPE: + if (entry1->type && entry2->type) { + status = strcasecomp(entry1->type, entry2->type); + if (status) + result = status; + } + break; + + case FILE_BY_DATE: + if (entry1->date && entry2->date && + strlen(entry1->date) == 12 && + strlen(entry2->date) == 12) { + /* + * Set the years and date, if we don't have them yet. + */ + if (!HaveYears) { + set_years_and_date(); + } + formatDate(date1, entry1); + formatDate(date2, entry2); + /* + * Do the comparison. - FM + */ + status = strcasecomp(date2, date1); + if (status) + result = status; + } + break; + + case FILE_BY_NAME: + default: + break; + } + return result; +} + +#ifdef LONG_LIST +static char *FormatStr(char **bufp, + char *start, + const char *value) +{ + char fmt[512]; + + if (*start) { + sprintf(fmt, "%%%.*ss", (int) sizeof(fmt) - 3, start); + HTSprintf(bufp, fmt, value); + } else if (*bufp && !(value && *value)) { + ; + } else if (value) { + StrAllocCat(*bufp, value); + } + return *bufp; +} + +static char *FormatSize(char **bufp, + char *start, + off_t value) +{ + char fmt[512]; + + if (*start) { + sprintf(fmt, "%%%.*s" PRI_off_t, + (int) sizeof(fmt) - DigitsOf(start) - 3, start); + + HTSprintf(bufp, fmt, value); + } else { + sprintf(fmt, "%" PRI_off_t, CAST_off_t (value)); + + StrAllocCat(*bufp, fmt); + } + return *bufp; +} + +static char *FormatNum(char **bufp, + char *start, + unsigned long value) +{ + char fmt[512]; + + if (*start) { + sprintf(fmt, "%%%.*sld", + (int) sizeof(fmt) - DigitsOf(start) - 3, start); + HTSprintf(bufp, fmt, value); + } else { + sprintf(fmt, "%lu", value); + StrAllocCat(*bufp, fmt); + } + return *bufp; +} + +static void FlushParse(HTStructured * target, char **buf) +{ + if (*buf && **buf) { + PUTS(*buf); + **buf = '\0'; + } +} + +static void LYListFmtParse(const char *fmtstr, + EntryInfo *data, + HTStructured * target, + char *tail) +{ + char c; + char *s; + char *end; + char *start; + char *str = NULL; + char *buf = NULL; + BOOL is_directory = (BOOL) (data->file_mode != 0 && + (TOUPPER(data->file_mode[0]) == 'D')); + BOOL is_symlinked = (BOOL) (data->file_mode != 0 && + (TOUPPER(data->file_mode[0]) == 'L')); + BOOL remove_size = (BOOL) (is_directory || is_symlinked); + + StrAllocCopy(str, fmtstr); + s = str; + end = str + strlen(str); + while (*s) { + start = s; + while (*s) { + if (*s == '%') { + if (*(s + 1) == '%') /* literal % */ + s++; + else + break; + } + s++; + } + /* s is positioned either at a % or at \0 */ + *s = '\0'; + if (s > start) { /* some literal chars. */ + StrAllocCat(buf, start); + } + if (s == end) + break; + start = ++s; + while (isdigit(UCH(*s)) || *s == '.' || *s == '-' || *s == ' ' || + *s == '#' || *s == '+' || *s == '\'') + s++; + c = *s; /* the format char. or \0 */ + *s = '\0'; + + switch (c) { + case '\0': + StrAllocCat(buf, start); + continue; + + case 'A': + case 'a': /* anchor */ + FlushParse(target, &buf); + HTDirEntry(target, tail, data->filename); + FormatStr(&buf, start, data->filename); + PUTS(buf); + END(HTML_A); + if (buf != 0) + *buf = '\0'; + if (c != 'A' && data->linkname != 0) { + PUTS(" -> "); + PUTS(data->linkname); + } + break; + + case 'T': /* MIME type */ + case 't': /* MIME type description */ + if (is_directory) { + if (c != 'T') { + FormatStr(&buf, start, ENTRY_IS_DIRECTORY); + } else { + FormatStr(&buf, start, ""); + } + } else if (is_symlinked) { + if (c != 'T') { + FormatStr(&buf, start, ENTRY_IS_SYMBOLIC_LINK); + } else { + FormatStr(&buf, start, ""); + } + } else { + const char *cp2; + HTFormat format; + + format = HTFileFormat(data->filename, NULL, &cp2); + + if (c != 'T') { + if (cp2 == NULL) { + if (!StrNCmp(HTAtom_name(format), + "application", 11)) { + cp2 = HTAtom_name(format) + 12; + if (!StrNCmp(cp2, "x-", 2)) + cp2 += 2; + } else { + cp2 = HTAtom_name(format); + } + } + FormatStr(&buf, start, cp2); + } else { + FormatStr(&buf, start, HTAtom_name(format)); + } + } + break; + + case 'd': /* date */ + if (data->date) { + FormatStr(&buf, start, data->date); + } else { + FormatStr(&buf, start, " * "); + } + break; + + case 's': /* size in bytes */ + FormatSize(&buf, start, data->size); + break; + + case 'K': /* size in Kilobytes but not for directories */ + if (remove_size) { + FormatStr(&buf, start, ""); + StrAllocCat(buf, " "); + break; + } + /* FALL THROUGH */ + case 'k': /* size in Kilobytes */ + /* FIXME - this is inconsistent with HTFile.c, but historical */ + if (data->size < 1024) { + FormatSize(&buf, start, data->size); + StrAllocCat(buf, " bytes"); + } else { + FormatSize(&buf, start, data->size / 1024); + StrAllocCat(buf, "Kb"); + } + break; + +#ifdef LONG_LIST + case 'p': /* unix-style permission bits */ + FormatStr(&buf, start, NonNull(data->file_mode)); + break; + + case 'o': /* owner */ + FormatStr(&buf, start, NonNull(data->file_user)); + break; + + case 'g': /* group */ + FormatStr(&buf, start, NonNull(data->file_group)); + break; + + case 'l': /* link count */ + FormatNum(&buf, start, data->file_links); + break; +#endif + + case '%': /* literal % with flags/width */ + FormatStr(&buf, start, "%"); + break; + + default: + fprintf(stderr, + "Unknown format character `%c' in list format\n", c); + break; + } + + s++; + } + if (buf) { + LYTrimTrailing(buf); + FlushParse(target, &buf); + FREE(buf); + } + PUTC('\n'); + FREE(str); +} +#endif /* LONG_LIST */ + +/* Read a directory into an hypertext object from the data socket + * -------------------------------------------------------------- + * + * On entry, + * anchor Parent anchor to link the this node to + * address Address of the directory + * On exit, + * returns HT_LOADED if OK + * <0 if error. + */ +static int read_directory(HTParentAnchor *parent, + const char *address, + HTFormat format_out, + HTStream *sink) +{ + int status; + BOOLEAN WasInterrupted = FALSE; + HTStructured *target = HTML_new(parent, format_out, sink); + char *filename = HTParse(address, "", PARSE_PATH + PARSE_PUNCTUATION); + EntryInfo *entry_info; + BOOLEAN first = TRUE; + char *lastpath = NULL; /* prefix for link, either "" (for root) or xxx */ + BOOL tildeIsTop = FALSE; + +#ifndef LONG_LIST + char string_buffer[64]; +#endif + + _HTProgress(gettext("Receiving FTP directory.")); + + /* + * Force the current Date and Year (TheDate, ThisYear, and LastYear) to be + * recalculated for each directory request. Otherwise we have a problem + * with long-running sessions assuming the wrong date for today. - kw + */ + HaveYears = FALSE; + /* + * Check whether we always want the home directory treated as Welcome. - + * FM + */ + if (server_type == VMS_SERVER) + tildeIsTop = TRUE; + + /* + * This should always come back FALSE, since the flag is set only for local + * directory listings if LONG_LIST was defined on compilation, but we could + * someday set up an equivalent listing for Unix ftp servers. - FM + */ + (void) HTDirTitles(target, parent, format_out, tildeIsTop); + + data_read_pointer = data_write_pointer = data_buffer; + + if (*filename == '\0') { /* Empty filename: use root. */ + StrAllocCopy(lastpath, "/"); + } else if (!strcmp(filename, "/")) { /* Root path. */ + StrAllocCopy(lastpath, "/foo/.."); + } else { + char *p = strrchr(filename, '/'); /* Find the lastslash. */ + char *cp; + + if (server_type == CMS_SERVER) { + StrAllocCopy(lastpath, filename); /* Use absolute path for CMS. */ + } else { + StrAllocCopy(lastpath, p + 1); /* Take slash off the beginning. */ + } + if ((cp = strrchr(lastpath, ';')) != NULL) { /* Trim type= param. */ + if (!strncasecomp((cp + 1), "type=", 5)) { + if (TOUPPER(*(cp + 6)) == 'D' || + TOUPPER(*(cp + 6)) == 'A' || + TOUPPER(*(cp + 6)) == 'I') + *cp = '\0'; + } + } + } + FREE(filename); + + { + HTBTree *bt = HTBTree_new((HTComparer) compare_EntryInfo_structs); + int ic; + HTChunk *chunk = HTChunkCreate(128); + int BytesReceived = 0; + int BytesReported = 0; + char NumBytes[64]; + char *spilledname = NULL; + + PUTC('\n'); /* prettier LJM */ + for (ic = 0; ic != EOF;) { /* For each entry in the directory */ + HTChunkClear(chunk); + + if (HTCheckForInterrupt()) { + CTRACE((tfp, + "read_directory: interrupted after %d bytes\n", + BytesReceived)); + WasInterrupted = TRUE; + if (BytesReceived) { + goto unload_btree; /* unload btree */ + } else { + ABORT_TARGET; + HTBTreeAndObject_free(bt); + FREE(spilledname); + HTChunkFree(chunk); + return HT_INTERRUPTED; + } + } + + /* read directory entry + */ + interrupted_in_next_data_char = FALSE; + for (;;) { /* Read in one line as filename */ + ic = NEXT_DATA_CHAR; + AgainForMultiNet: + if (interrupted_in_next_data_char) { + CTRACE((tfp, + "read_directory: interrupted_in_next_data_char after %d bytes\n", + BytesReceived)); + WasInterrupted = TRUE; + if (BytesReceived) { + goto unload_btree; /* unload btree */ + } else { + ABORT_TARGET; + HTBTreeAndObject_free(bt); + FREE(spilledname); + HTChunkFree(chunk); + return HT_INTERRUPTED; + } + } else if ((char) ic == CR || (char) ic == LF) { /* Terminator? */ + if (chunk->size != 0) { /* got some text */ + /* Deal with MultiNet's wrapping of long lines */ + if (server_type == VMS_SERVER) { + /* Deal with MultiNet's wrapping of long lines - F.M. */ + if (data_read_pointer < data_write_pointer && + *(data_read_pointer + 1) == ' ') + data_read_pointer++; + else if (data_read_pointer >= data_write_pointer) { + status = NETREAD(data_soc, data_buffer, + DATA_BUFFER_SIZE); + if (status == HT_INTERRUPTED) { + interrupted_in_next_data_char = 1; + goto AgainForMultiNet; + } + if (status <= 0) { + ic = EOF; + break; + } + data_write_pointer = data_buffer + status; + data_read_pointer = data_buffer; + if (*data_read_pointer == ' ') + data_read_pointer++; + else + break; + } else + break; + } else + break; /* finish getting one entry */ + } + } else if (ic == EOF) { + break; /* End of file */ + } else { + HTChunkPutc(chunk, UCH(ic)); + } + } + HTChunkTerminate(chunk); + + BytesReceived += chunk->size; + if (BytesReceived > BytesReported + 1024) { +#ifdef _WINDOWS + sprintf(NumBytes, gettext("Transferred %d bytes (%5d)"), + BytesReceived, ws_read_per_sec); +#else + sprintf(NumBytes, TRANSFERRED_X_BYTES, BytesReceived); +#endif + HTProgress(NumBytes); + BytesReported = BytesReceived; + } + + if (ic == EOF && chunk->size == 1) + /* 1 means empty: includes terminating 0 */ + break; + CTRACE((tfp, "HTFTP: Line in %s is %s\n", + lastpath, chunk->data)); + + entry_info = parse_dir_entry(chunk->data, &first, &spilledname); + if (entry_info->display) { + FREE(spilledname); + CTRACE((tfp, "Adding file to BTree: %s\n", + entry_info->filename)); + HTBTree_add(bt, entry_info); + } else { + free_entryinfo_struct_contents(entry_info); + FREE(entry_info); + } + + } /* next entry */ + + unload_btree: + + HTChunkFree(chunk); + FREE(spilledname); + + /* print out the handy help message if it exists :) */ + if (help_message_cache_non_empty()) { + START(HTML_PRE); + START(HTML_HR); + PUTC('\n'); + PUTS(help_message_cache_contents()); + init_help_message_cache(); /* to free memory */ + START(HTML_HR); + PUTC('\n'); + } else { + START(HTML_PRE); + PUTC('\n'); + } + + /* Run through tree printing out in order + */ + { +#ifndef LONG_LIST +#ifdef SH_EX /* 1997/10/18 (Sat) 14:14:28 */ + char *p, name_buff[256]; + int name_len, dot_len; + +#define FNAME_WIDTH 30 +#define FILE_GAP 1 + +#endif + int i; +#endif + HTBTElement *ele; + + for (ele = HTBTree_next(bt, NULL); + ele != NULL; + ele = HTBTree_next(bt, ele)) { + entry_info = (EntryInfo *) HTBTree_object(ele); + +#ifdef LONG_LIST + LYListFmtParse(ftp_format, + entry_info, + target, + lastpath); +#else + if (entry_info->date) { + PUTS(entry_info->date); + PUTS(" "); + } else { + PUTS(" * "); + } + + if (entry_info->type) { + for (i = 0; entry_info->type[i] != '\0' && i < 16; i++) + PUTC(entry_info->type[i]); + for (; i < 17; i++) + PUTC(' '); + } + /* start the anchor */ + HTDirEntry(target, lastpath, entry_info->filename); +#ifdef SH_EX /* 1997/10/18 (Sat) 16:00 */ + name_len = strlen(entry_info->filename); + + sprintf(name_buff, "%-*s", FNAME_WIDTH, entry_info->filename); + + if (name_len < FNAME_WIDTH) { + dot_len = FNAME_WIDTH - FILE_GAP - name_len; + if (dot_len > 0) { + p = name_buff + name_len + 1; + while (dot_len-- > 0) + *p++ = '.'; + } + } else { + name_buff[FNAME_WIDTH] = '\0'; + } + + PUTS(name_buff); +#else + PUTS(entry_info->filename); +#endif + END(HTML_A); + + if (entry_info->size) { +#ifdef SH_EX /* 1998/02/02 (Mon) 16:34:52 */ + if (entry_info->size < 1024) + sprintf(string_buffer, "%6ld bytes", + entry_info->size); + else + sprintf(string_buffer, "%6ld Kb", + entry_info->size / 1024); +#else + if (entry_info->size < 1024) + sprintf(string_buffer, " %lu bytes", + (unsigned long) entry_info->size); + else + sprintf(string_buffer, " %luKb", + (unsigned long) entry_info->size / 1024); +#endif + PUTS(string_buffer); + } else if (entry_info->linkname != 0) { + PUTS(" -> "); + PUTS(entry_info->linkname); + } + + PUTC('\n'); /* end of this entry */ +#endif + + free_entryinfo_struct_contents(entry_info); + } + } + END(HTML_PRE); + END(HTML_BODY); + FREE_TARGET; + HTBTreeAndObject_free(bt); + } + + FREE(lastpath); + + if (WasInterrupted || data_soc != -1) { /* should always be true */ + /* + * Without closing the data socket first, the response(NULL) later may + * hang. Some servers expect the client to fin/ack the close of the + * data connection before proceeding with the conversation on the + * control connection. - kw + */ + CTRACE((tfp, "HTFTP: Closing data socket %d\n", data_soc)); + status = NETCLOSE(data_soc); + if (status == -1) + HTInetStatus("close"); /* Comment only */ + data_soc = -1; + } + + if (WasInterrupted || HTCheckForInterrupt()) { + _HTProgress(TRANSFER_INTERRUPTED); + } + return HT_LOADED; +} + +/* + * Setup an FTP connection. + */ +static int setup_connection(const char *name, + HTParentAnchor *anchor) +{ + int retry; /* How many times tried? */ + int status = HT_NO_CONNECTION; + + CTRACE((tfp, "setup_connection(%s)\n", name)); + + /* set use_list to NOT since we don't know what kind of server + * this is yet. And set the type to GENERIC + */ + use_list = FALSE; + server_type = GENERIC_SERVER; + Broken_RETR = FALSE; + +#ifdef INET6 + Broken_EPSV = FALSE; +#endif + + for (retry = 0; retry < 2; retry++) { /* For timed out/broken connections */ + status = get_connection(name, anchor); + if (status < 0) { + break; + } + + if (!ftp_local_passive) { + status = get_listen_socket(); + if (status < 0) { + NETCLOSE(control->socket); + control->socket = -1; +#ifdef INET6 + if (have_socket) + (void) close_master_socket(); +#else + close_master_socket(); +#endif /* INET6 */ + /* HT_INTERRUPTED would fall through, if we could interrupt + somehow in the middle of it, which we currently can't. */ + break; + } +#ifdef REPEAT_PORT + /* Inform the server of the port number we will listen on + */ + status = response(port_command); + FREE(port_command); + if (status == HT_INTERRUPTED) { + CTRACE((tfp, "HTFTP: Interrupted in response (port_command)\n")); + _HTProgress(CONNECTION_INTERRUPTED); + NETCLOSE(control->socket); + control->socket = -1; + close_master_socket(); + status = HT_INTERRUPTED; + break; + } + if (status != 2) { /* Could have timed out */ + if (status < 0) + continue; /* try again - net error */ + status = -status; /* bad reply */ + break; + } + CTRACE((tfp, "HTFTP: Port defined.\n")); +#endif /* REPEAT_PORT */ + } else { /* Tell the server to be passive */ + char *command = NULL; + const char *p = "?"; + int h0, h1, h2, h3, p0, p1; /* Parts of reply */ + +#ifdef INET6 + char dst[LINE_LENGTH + 1]; +#endif + + data_soc = status; + +#ifdef INET6 + /* see RFC 2428 */ + if (Broken_EPSV) + status = 1; + else + status = send_cmd_1(p = "EPSV"); + if (status < 0) /* retry or Bad return */ + continue; + else if (status != 2) { + status = send_cmd_1(p = "PASV"); + if (status < 0) { /* retry or Bad return */ + continue; + } else if (status != 2) { + status = -status; /* bad reply */ + break; + } + } + + if (strcmp(p, "PASV") == 0) { + for (p = response_text; *p && *p != ','; p++) { + ; /* null body */ + } + + while (--p > response_text && '0' <= *p && *p <= '9') { + ; /* null body */ + } + status = sscanf(p + 1, "%d,%d,%d,%d,%d,%d", + &h0, &h1, &h2, &h3, &p0, &p1); + if (status < 4) { + fprintf(tfp, "HTFTP: PASV reply has no inet address!\n"); + status = HT_NO_CONNECTION; + break; + } + passive_port = (PortNumber) ((p0 << 8) + p1); + sprintf(dst, "%d.%d.%d.%d", h0, h1, h2, h3); + } else if (strcmp(p, "EPSV") == 0) { + char c0, c1, c2, c3; + LY_SOCKADDR ss; + LY_SOCKLEN sslen; + + /* + * EPSV bla (|||port|) + */ + for (p = response_text; *p && !isspace(UCH(*p)); p++) { + ; /* null body */ + } + for ( /*nothing */ ; + *p && *p != '('; + p++) { /*) */ + ; /* null body */ + } + status = sscanf(p, "(%c%c%c%d%c)", &c0, &c1, &c2, &p0, &c3); + if (status != 5) { + fprintf(tfp, "HTFTP: EPSV reply has invalid format!\n"); + status = HT_NO_CONNECTION; + break; + } + passive_port = (PortNumber) p0; + + sslen = (LY_SOCKLEN) sizeof(ss); + if (getpeername(control->socket, SOCKADDR_OF(ss), &sslen) < 0) { + fprintf(tfp, "HTFTP: getpeername(control) failed\n"); + status = HT_NO_CONNECTION; + break; + } + if (getnameinfo(SOCKADDR_OF(ss), + sslen, + dst, + (socklen_t) sizeof(dst), + NULL, 0, NI_NUMERICHOST)) { + fprintf(tfp, "HTFTP: getnameinfo failed\n"); + status = HT_NO_CONNECTION; + break; + } + } +#else + status = send_cmd_1("PASV"); + if (status != 2) { + if (status < 0) + continue; /* retry or Bad return */ + status = -status; /* bad reply */ + break; + } + for (p = response_text; *p && *p != ','; p++) { + ; /* null body */ + } + + while (--p > response_text && '0' <= *p && *p <= '9') { + ; /* null body */ + } + + status = sscanf(p + 1, "%d,%d,%d,%d,%d,%d", + &h0, &h1, &h2, &h3, &p0, &p1); + if (status < 4) { + fprintf(tfp, "HTFTP: PASV reply has no inet address!\n"); + status = HT_NO_CONNECTION; + break; + } + passive_port = (PortNumber) ((p0 << 8) + p1); +#endif /* INET6 */ + CTRACE((tfp, "HTFTP: Server is listening on port %d\n", + passive_port)); + + /* Open connection for data: */ + +#ifdef INET6 + HTSprintf0(&command, "%s//%s:%d/", STR_FTP_URL, dst, passive_port); +#else + HTSprintf0(&command, "%s//%d.%d.%d.%d:%d/", + STR_FTP_URL, h0, h1, h2, h3, passive_port); +#endif + status = HTDoConnect(command, "FTP data", passive_port, &data_soc); + FREE(command); + + if (status < 0) { + (void) HTInetStatus(gettext("connect for data")); + NETCLOSE(data_soc); + break; + } + + CTRACE((tfp, "FTP data connected, socket %d\n", data_soc)); + } + status = 0; + break; /* No more retries */ + + } /* for retries */ + CTRACE((tfp, "setup_connection returns %d\n", status)); + return status; +} + +/* Retrieve File from Server + * ------------------------- + * + * On entry, + * name WWW address of a file: document, including hostname + * On exit, + * returns Socket number for file if good. + * <0 if bad. + */ +int HTFTPLoad(const char *name, + HTParentAnchor *anchor, + HTFormat format_out, + HTStream *sink) +{ + BOOL isDirectory = NO; + HTAtom *encoding = NULL; + int status, final_status; + int outstanding = 1; /* outstanding control connection responses + + that we are willing to wait for, if we + get to the point of reading data - kw */ + HTFormat format; + + CTRACE((tfp, "HTFTPLoad(%s) %s connection\n", + name, + (ftp_local_passive + ? "passive" + : "normal"))); + + HTReadProgress((off_t) 0, (off_t) 0); + + status = setup_connection(name, anchor); + if (status < 0) + return status; /* Failed with this code */ + + /* Ask for the file: + */ + { + char *filename = HTParse(name, "", PARSE_PATH + PARSE_PUNCTUATION); + char *fname = filename; /* Save for subsequent free() */ + char *vmsname = NULL; + BOOL binary; + const char *type = NULL; + char *types = NULL; + char *cp; + + if (server_type == CMS_SERVER) { + /* If the unescaped path has a %2f, reject it as illegal. - FM */ + if (((cp = strstr(filename, "%2")) != NULL) && + TOUPPER(cp[2]) == 'F') { + FREE(fname); + init_help_message_cache(); /* to free memory */ + NETCLOSE(control->socket); + control->socket = -1; + CTRACE((tfp, + "HTFTP: Rejecting path due to illegal escaped slash.\n")); + return -1; + } + } + + if (!*filename) { + StrAllocCopy(filename, "/"); + type = "D"; + } else if ((type = types = strrchr(filename, ';')) != NULL) { + /* + * Check and trim the type= parameter. - FM + */ + if (!strncasecomp((type + 1), "type=", 5)) { + switch (TOUPPER(*(type + 6))) { + case 'D': + *types = '\0'; + type = "D"; + break; + case 'A': + *types = '\0'; + type = "A"; + break; + case 'I': + *types = '\0'; + type = "I"; + break; + default: + type = ""; + break; + } + if (!*filename) { + *filename = '/'; + *(filename + 1) = '\0'; + } + } + if (*type != '\0') { + CTRACE((tfp, "HTFTP: type=%s\n", type)); + } + } + HTUnEscape(filename); + CTRACE((tfp, "HTFTP: UnEscaped %s\n", filename)); + if (filename[1] == '~') { + /* + * Check if translation of HOME as tilde is supported, + * and adjust filename if so. - FM + */ + char *cp2 = NULL; + char *fn = NULL; + + if ((cp2 = StrChr((filename + 1), '/')) != NULL) { + *cp2 = '\0'; + } + status = send_cmd_1("PWD"); + if (status == 2 && response_text[5] == '/') { + status = send_cwd(filename + 1); + if (status == 2) { + StrAllocCopy(fn, (filename + 1)); + if (cp2) { + *cp2 = '/'; + if (fn[strlen(fn) - 1] != '/') { + StrAllocCat(fn, cp2); + } else { + StrAllocCat(fn, (cp2 + 1)); + } + cp2 = NULL; + } + FREE(fname); + fname = filename = fn; + } + } + if (cp2) { + *cp2 = '/'; + } + } + if (strlen(filename) > 3) { + char *cp2; + + if (((cp2 = strrchr(filename, '.')) != NULL && + 0 == strncasecomp(cp2, ".me", 3)) && + (cp2[3] == '\0' || cp2[3] == ';')) { + /* + * Don't treat this as application/x-Troff-me if it's a Unix + * server but has the string "read.me", or if it's not a Unix + * server. - FM + */ + if ((server_type != UNIX_SERVER) || + (cp2 > (filename + 3) && + 0 == strncasecomp((cp2 - 4), "read.me", 7))) { + *cp2 = '\0'; + format = HTFileFormat(filename, &encoding, NULL); + *cp2 = '.'; + } else { + format = HTFileFormat(filename, &encoding, NULL); + } + } else { + format = HTFileFormat(filename, &encoding, NULL); + } + } else { + format = HTFileFormat(filename, &encoding, NULL); + } + format = HTCharsetFormat(format, anchor, -1); + binary = (BOOL) (encoding != WWW_ENC_8BIT && + encoding != WWW_ENC_7BIT); + if (!binary && + /* + * Force binary if we're in source, download or dump mode and this is + * not a VM/CMS server, so we don't get CRLF instead of LF (or CR) for + * newlines in text files. Can't do this for VM/CMS or we'll get raw + * EBCDIC. - FM + */ + (format_out == WWW_SOURCE || + format_out == WWW_DOWNLOAD || + format_out == WWW_DUMP) && + (server_type != CMS_SERVER)) + binary = TRUE; + if (!binary && type && *type == 'I') { + /* + * Force binary if we had ;type=I - FM + */ + binary = TRUE; + } else if (binary && type && *type == 'A') { + /* + * Force ASCII if we had ;type=A - FM + */ + binary = FALSE; + } + if (binary != control->is_binary) { + /* + * Act on our setting if not already set. - FM + */ + const char *mode = binary ? "I" : "A"; + + status = send_cmd_2("TYPE", mode); + if (status != 2) { + init_help_message_cache(); /* to free memory */ + return ((status < 0) ? status : -status); + } + control->is_binary = binary; + } + switch (server_type) { + /* + * Handle what for Lynx are special case servers, e.g., for which + * we respect RFC 1738, or which have known conflicts in suffix + * mappings. - FM + */ + case VMS_SERVER: + { + char *cp1, *cp2; + BOOL included_device = FALSE; + BOOL found_tilde = FALSE; + + /* Accept only Unix-style filename */ + if (StrChr(filename, ':') != NULL || + StrChr(filename, '[') != NULL) { + FREE(fname); + init_help_message_cache(); /* to free memory */ + NETCLOSE(control->socket); + control->socket = -1; + CTRACE((tfp, + "HTFTP: Rejecting path due to non-Unix-style syntax.\n")); + return -1; + } + /* Handle any unescaped "/%2F" path */ + if (!StrNCmp(filename, "//", 2)) { + int i; + + included_device = TRUE; + for (i = 0; filename[(i + 1)]; i++) + filename[i] = filename[(i + 1)]; + filename[i] = '\0'; + CTRACE((tfp, "HTFTP: Trimmed '%s'\n", filename)); + cp = HTVMS_name("", filename); + CTRACE((tfp, "HTFTP: VMSized '%s'\n", cp)); + if ((cp1 = strrchr(cp, ']')) != NULL) { + strcpy(filename, ++cp1); + CTRACE((tfp, "HTFTP: Filename '%s'\n", filename)); + *cp1 = '\0'; + status = send_cwd(cp); + if (status != 2) { + char *dotslash = 0; + + if ((cp1 = StrChr(cp, '[')) != NULL) { + *cp1++ = '\0'; + status = send_cwd(cp); + if (status != 2) { + FREE(fname); + init_help_message_cache(); /* to free memory */ + NETCLOSE(control->socket); + control->socket = -1; + return ((status < 0) ? status : -status); + } + HTSprintf0(&dotslash, "[.%s", cp1); + status = send_cwd(dotslash); + FREE(dotslash); + if (status != 2) { + FREE(fname); + init_help_message_cache(); /* to free memory */ + NETCLOSE(control->socket); + control->socket = -1; + return ((status < 0) ? status : -status); + } + } else { + FREE(fname); + init_help_message_cache(); /* to free memory */ + NETCLOSE(control->socket); + control->socket = -1; + return ((status < 0) ? status : -status); + } + } + } else if ((cp1 = StrChr(cp, ':')) != NULL && + StrChr(cp, '[') == NULL && + StrChr(cp, ']') == NULL) { + cp1++; + if (*cp1 != '\0') { + int cplen = (int) (cp1 - cp); + + strcpy(filename, cp1); + CTRACE((tfp, "HTFTP: Filename '%s'\n", filename)); + HTSprintf0(&vmsname, "%.*s[%s]", cplen, cp, filename); + status = send_cwd(vmsname); + if (status != 2) { + HTSprintf(&vmsname, "%.*s[000000]", cplen, cp); + status = send_cwd(vmsname); + if (status != 2) { + HTSprintf(&vmsname, "%.*s", cplen, cp); + status = send_cwd(vmsname); + if (status != 2) { + FREE(fname); + init_help_message_cache(); + NETCLOSE(control->socket); + control->socket = -1; + return ((status < 0) ? status : -status); + } + } + } else { + HTSprintf0(&vmsname, "000000"); + filename = vmsname; + } + } + } else if (0 == strcmp(cp, (filename + 1))) { + status = send_cwd(cp); + if (status != 2) { + HTSprintf0(&vmsname, "%s:", cp); + status = send_cwd(vmsname); + if (status != 2) { + FREE(fname); + init_help_message_cache(); /* to free memory */ + NETCLOSE(control->socket); + control->socket = -1; + return ((status < 0) ? status : -status); + } + } + HTSprintf0(&vmsname, "000000"); + filename = vmsname; + } + } + /* Trim trailing slash if filename is not the top directory */ + if (strlen(filename) > 1 && filename[strlen(filename) - 1] == '/') + filename[strlen(filename) - 1] = '\0'; + +#ifdef MAINTAIN_CONNECTION /* Don't need this if always new connection - F.M. */ + if (!included_device) { + /* Get the current default VMS device:[directory] */ + status = send_cmd_1("PWD"); + if (status != 2) { + FREE(fname); + init_help_message_cache(); /* to free memory */ + NETCLOSE(control->socket); + control->socket = -1; + return ((status < 0) ? status : -status); + } + /* Go to the VMS account's top directory */ + if ((cp = StrChr(response_text, '[')) != NULL && + (cp1 = strrchr(response_text, ']')) != NULL) { + char *tmp = 0; + unsigned len = 4; + + StrAllocCopy(tmp, cp); + if ((cp2 = StrChr(cp, '.')) != NULL && cp2 < cp1) { + len += (cp2 - cp); + } else { + len += (cp1 - cp); + } + tmp[len] = 0; + StrAllocCat(tmp, "]"); + + status = send_cwd(tmp); + FREE(tmp); + + if (status != 2) { + FREE(fname); + init_help_message_cache(); /* to free memory */ + NETCLOSE(control->socket); + control->socket = -1; + return ((status < 0) ? status : -status); + } + } + } +#endif /* MAINTAIN_CONNECTION */ + + /* If we want the VMS account's top directory, list it now */ + if (!(strcmp(filename, "/~")) || + (included_device && 0 == strcmp(filename, "000000")) || + (strlen(filename) == 1 && *filename == '/')) { + isDirectory = YES; + status = send_cmd_1("LIST"); + FREE(fname); + if (status != 1) { + /* Action not started */ + init_help_message_cache(); /* to free memory */ + NETCLOSE(control->socket); + control->socket = -1; + return ((status < 0) ? status : -status); + } + /* Big goto! */ + goto listen; + } + /* Otherwise, go to appropriate directory and doctor filename */ + if (!StrNCmp(filename, "/~", 2)) { + filename += 2; + found_tilde = TRUE; + } + CTRACE((tfp, "check '%s' to translate x/y/ to [.x.y]\n", filename)); + if (!included_device && + (cp = StrChr(filename, '/')) != NULL && + (cp1 = strrchr(cp, '/')) != NULL && + (cp1 - cp) > 1) { + char *tmp = 0; + + HTSprintf0(&tmp, "[.%.*s]", (int) (cp1 - cp - 1), cp + 1); + + CTRACE((tfp, "change path '%s'\n", tmp)); + while ((cp2 = strrchr(tmp, '/')) != NULL) + *cp2 = '.'; + CTRACE((tfp, "...to path '%s'\n", tmp)); + + status = send_cwd(tmp); + FREE(tmp); + + if (status != 2) { + FREE(fname); + init_help_message_cache(); /* to free memory */ + NETCLOSE(control->socket); + control->socket = -1; + return ((status < 0) ? status : -status); + } + filename = cp1 + 1; + } else { + if (!included_device && !found_tilde) { + filename += 1; + } + } + break; + } + case CMS_SERVER: + { + /* + * If we want the CMS account's top directory, or a base SFS or + * anonymous directory path (i.e., without a slash), list it + * now. FM + */ + if ((strlen(filename) == 1 && *filename == '/') || + ((0 == strncasecomp((filename + 1), "vmsysu:", 7)) && + (cp = StrChr((filename + 1), '.')) != NULL && + StrChr(cp, '/') == NULL) || + (0 == strncasecomp(filename + 1, "anonymou.", 9) && + StrChr(filename + 1, '/') == NULL)) { + if (filename[1] != '\0') { + status = send_cwd(filename + 1); + if (status != 2) { + /* Action not started */ + init_help_message_cache(); /* to free memory */ + NETCLOSE(control->socket); + control->socket = -1; + return ((status < 0) ? status : -status); + } + } + isDirectory = YES; + if (use_list) + status = send_cmd_1("LIST"); + else + status = send_cmd_1("NLST"); + FREE(fname); + if (status != 1) { + /* Action not started */ + init_help_message_cache(); /* to free memory */ + NETCLOSE(control->socket); + control->socket = -1; + return ((status < 0) ? status : -status); + } + /* Big goto! */ + goto listen; + } + filename++; + + /* Otherwise, go to appropriate directory and adjust filename */ + while ((cp = StrChr(filename, '/')) != NULL) { + *cp++ = '\0'; + status = send_cwd(filename); + if (status == 2) { + if (*cp == '\0') { + isDirectory = YES; + if (use_list) + status = send_cmd_1("LIST"); + else + status = send_cmd_1("NLST"); + FREE(fname); + if (status != 1) { + /* Action not started */ + init_help_message_cache(); /* to free memory */ + NETCLOSE(control->socket); + control->socket = -1; + return ((status < 0) ? status : -status); + } + /* Clear any messages from the login directory */ + init_help_message_cache(); + /* Big goto! */ + goto listen; + } + filename = cp; + } + } + break; + } + default: + /* Shift for any unescaped "/%2F" path */ + if (!StrNCmp(filename, "//", 2)) + filename++; + break; + } + /* + * Act on a file or listing request, or try to figure out which we're + * dealing with if we don't know yet. - FM + */ + if (!(type) || (type && *type != 'D')) { + /* + * If we are retrieving a file we will (except for CMS) use + * binary mode, which lets us use the size command supported by + * ftp servers which implement RFC 3659. Knowing the size lets + * us in turn display ETA in the progress message -TD + */ + if (control->is_binary) { + int code; + + status = send_cmd_2("SIZE", filename); + if (status == 2) { +#if !defined(HAVE_LONG_LONG) && defined(GUESS_PRI_off_t) + long size; + + if (sscanf(response_text, "%d %ld", &code, &size) == 2) { + anchor->content_length = (off_t) size; + } +#else + off_t size; + if (sscanf(response_text, "%d %" SCN_off_t, &code, &size) + == 2) { + anchor->content_length = size; + } +#endif + } + } + status = send_cmd_2("RETR", filename); + if (status >= 5) { + int check; + + if (Broken_RETR) { + CTRACE((tfp, "{{reconnecting...\n")); + close_connection(control); + check = setup_connection(name, anchor); + CTRACE((tfp, "...done }}reconnecting\n")); + if (check < 0) + return check; + } + } + } else { + status = 5; /* Failed status set as flag. - FM */ + } + if (status != 1) { /* Failed : try to CWD to it */ + /* Clear any login messages if this isn't the login directory */ + if (strcmp(filename, "/")) + init_help_message_cache(); + + status = send_cwd(filename); + if (status == 2) { /* Succeeded : let's NAME LIST it */ + isDirectory = YES; + if (use_list) + status = send_cmd_1("LIST"); + else + status = send_cmd_1("NLST"); + } + } + FREE(fname); + FREE(vmsname); + if (status != 1) { + init_help_message_cache(); /* to free memory */ + NETCLOSE(control->socket); + control->socket = -1; + if (status < 0) + return status; + else + return -status; + } + } + + listen: + if (!ftp_local_passive) { + /* Wait for the connection */ + LY_SOCKADDR soc_A; + LY_SOCKLEN soc_addrlen = (LY_SOCKLEN) sizeof(soc_A); + +#ifdef SOCKS + if (socks_flag) + status = Raccept((int) master_socket, + SOCKADDR_OF(soc_A), + &soc_addrlen); + else +#endif /* SOCKS */ + status = accept((int) master_socket, + SOCKADDR_OF(soc_A), + &soc_addrlen); + if (status < 0) { + init_help_message_cache(); /* to free memory */ + return HTInetStatus("accept"); + } + CTRACE((tfp, "TCP: Accepted new socket %d\n", status)); + data_soc = status; + } + + if (isDirectory) { + if (server_type == UNIX_SERVER && !unsure_type && + !strcmp(response_text, + "150 Opening ASCII mode data connection for /bin/dl.\n")) { + CTRACE((tfp, "HTFTP: Treating as \"dls\" server.\n")); + server_type = DLS_SERVER; + } + final_status = read_directory(anchor, name, format_out, sink); + if (final_status > 0) { + if (server_type != CMS_SERVER) + if (outstanding-- > 0) { + status = response(NULL); + if (status < 0 || + (status == 2 && !StrNCmp(response_text, "221", 3))) + outstanding = 0; + } + } else { /* HT_INTERRUPTED */ + /* User may have pressed 'z' to give up because no + packets got through, so let's not make them wait + any longer - kw */ + outstanding = 0; + } + + if (data_soc != -1) { /* normally done in read_directory */ + CTRACE((tfp, "HTFTP: Closing data socket %d\n", data_soc)); + status = NETCLOSE(data_soc); + if (status == -1) + HTInetStatus("close"); /* Comment only */ + } + status = final_status; + } else { + int rv; + char *FileName = HTParse(name, "", PARSE_PATH + PARSE_PUNCTUATION); + + /* Clear any login messages */ + init_help_message_cache(); + + /* Fake a Content-Encoding for compressed files. - FM */ + HTUnEscape(FileName); + if (!IsUnityEnc(encoding)) { + /* + * We already know from the call to HTFileFormat above that this is + * a compressed file, no need to look at the filename again. - kw + */ + StrAllocCopy(anchor->content_type, format->name); + StrAllocCopy(anchor->content_encoding, HTAtom_name(encoding)); + format = HTAtom_for("www/compressed"); + + } else { + int rootlen; + CompressFileType cft = HTCompressFileType(FileName, "._-", &rootlen); + + if (cft != cftNone) { + FileName[rootlen] = '\0'; + format = HTFileFormat(FileName, &encoding, NULL); + format = HTCharsetFormat(format, anchor, -1); + StrAllocCopy(anchor->content_type, format->name); + format = HTAtom_for("www/compressed"); + } + + switch (cft) { + case cftCompress: + StrAllocCopy(anchor->content_encoding, "x-compress"); + break; + case cftGzip: + StrAllocCopy(anchor->content_encoding, "x-gzip"); + break; + case cftDeflate: + StrAllocCopy(anchor->content_encoding, "x-deflate"); + break; + case cftBzip2: + StrAllocCopy(anchor->content_encoding, "x-bzip2"); + break; + case cftBrotli: + StrAllocCopy(anchor->content_encoding, "x-brotli"); + break; + case cftNone: + break; + } + } + FREE(FileName); + + _HTProgress(gettext("Receiving FTP file.")); + rv = HTParseSocket(format, format_out, anchor, data_soc, sink); + + HTInitInput(control->socket); + /* Reset buffering to control connection DD 921208 */ + + if (rv < 0) { + if (rv == -2) /* weird error, don't expect much response */ + outstanding--; + else if (rv == HT_INTERRUPTED || rv == -1) + /* User may have pressed 'z' to give up because no + packets got through, so let's not make them wait + longer - kw */ + outstanding = 0; + CTRACE((tfp, "HTFTP: Closing data socket %d\n", data_soc)); + status = NETCLOSE(data_soc); + } else { + status = 2; /* data_soc already closed in HTCopy - kw */ + } + + if (status < 0 && rv != HT_INTERRUPTED && rv != -1) { + (void) HTInetStatus("close"); /* Comment only */ + } else { + if (rv != HT_LOADED && outstanding--) { + status = response(NULL); /* Pick up final reply */ + if (status != 2 && rv != HT_INTERRUPTED && rv != -1) { + data_soc = -1; /* invalidate it */ + init_help_message_cache(); /* to free memory */ + return HTLoadError(sink, 500, response_text); + } else if (status == 2 && !StrNCmp(response_text, "221", 3)) { + outstanding = 0; + } + } + } + final_status = HT_LOADED; + } + while (outstanding-- > 0 && + (status > 0)) { + status = response(NULL); + if (status == 2 && !StrNCmp(response_text, "221", 3)) + break; + } + data_soc = -1; /* invalidate it */ + CTRACE((tfp, "HTFTPLoad: normal end; ")); + if (control->socket < 0) { + CTRACE((tfp, "control socket is %d\n", control->socket)); + } else { + CTRACE((tfp, "closing control socket %d\n", control->socket)); + status = NETCLOSE(control->socket); + if (status == -1) + HTInetStatus("control connection close"); /* Comment only */ + } + control->socket = -1; + init_help_message_cache(); /* to free memory */ + /* returns HT_LOADED (always for file if we get here) or error */ + return final_status; +} /* open_file_read */ + +/* + * This function frees any user entered password, so that + * it must be entered again for a future request. - FM + */ +void HTClearFTPPassword(void) +{ + /* + * Need code to check cached documents from non-anonymous ftp accounts and + * do something to ensure that they no longer can be accessed without a new + * retrieval. - FM + */ + + /* + * Now free the current user entered password, if any. - FM + */ + FREE(user_entered_password); +} + +#endif /* ifndef DISABLE_FTP */ diff --git a/WWW/Library/Implementation/HTFTP.h b/WWW/Library/Implementation/HTFTP.h new file mode 100644 index 0000000..a903bbb --- /dev/null +++ b/WWW/Library/Implementation/HTFTP.h @@ -0,0 +1,70 @@ +/* FTP access module for libwww + FTP ACCESS FUNCTIONS + + This isn't really a valid protocol module -- it is lumped together with HTFile . That + could be changed easily. + + Author: Tim Berners-Lee. Public Domain. Please mail changes to timbl@info.cern.ch + + */ +#ifndef HTFTP_H +#define HTFTP_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif +#define FILE_BY_NAME 0 +#define FILE_BY_TYPE 1 +#define FILE_BY_SIZE 2 +#define FILE_BY_DATE 3 + extern int HTfileSortMethod; /* specifies the method of sorting */ + +/* PUBLIC HTVMS_name() + * CONVERTS WWW name into a VMS name + * ON ENTRY: + * nn Node Name (optional) + * fn WWW file name + * + * ON EXIT: + * returns vms file specification + * + * Bug: Returns pointer to static -- non-reentrant + */ + extern char *HTVMS_name(const char *nn, + const char *fn); + +/* + +Retrieve File from Server + + ON EXIT, + + returns Socket number for file if good.<0 if bad. + + */ + extern int HTFTPLoad(const char *name, + HTParentAnchor *anchor, + HTFormat format_out, + HTStream *sink); + +/* + * This function frees any user entered password, so that + * it must be entered again for a future request. - FM + */ + extern void HTClearFTPPassword(void); + +/* + +Return Host Name + + */ + extern const char *HTHostName(void); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/WWW/Library/Implementation/HTFWriter.h b/WWW/Library/Implementation/HTFWriter.h new file mode 100644 index 0000000..015ea15 --- /dev/null +++ b/WWW/Library/Implementation/HTFWriter.h @@ -0,0 +1,30 @@ +/* File Writer for libwww + C FILE WRITER + + It is useful to have both FWriter and Writer for environments in which fdopen() doesn't + exist for example. + + */ +#ifndef HTFWRITE_H +#define HTFWRITE_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + extern HTStream *HTFWriter_new(FILE *fp); + + extern HTStream *HTSaveAndExecute(HTPresentation *pres, + HTParentAnchor *anchor, /* Not used */ + HTStream *sink); + + extern HTStream *HTSaveLocally(HTPresentation *pres, + HTParentAnchor *anchor, /* Not used */ + HTStream *sink); + +#ifdef __cplusplus +} +#endif +#endif /* HTFWRITE_H */ diff --git a/WWW/Library/Implementation/HTFile.c b/WWW/Library/Implementation/HTFile.c new file mode 100644 index 0000000..8fdaa2c --- /dev/null +++ b/WWW/Library/Implementation/HTFile.c @@ -0,0 +1,3395 @@ +/* + * $LynxId: HTFile.c,v 1.158 2022/07/25 23:52:05 tom Exp $ + * + * File Access HTFile.c + * =========== + * + * This is unix-specific code in general, with some VMS bits. + * These are routines for file access used by browsers. + * Development of this module for Unix DIRED_SUPPORT in Lynx + * regrettably has has been conducted in a manner with now + * creates a major impediment for hopes of adapting Lynx to + * a newer version of the library. + * + * History: + * Feb 91 Written Tim Berners-Lee CERN/CN + * Apr 91 vms-vms access included using DECnet syntax + * 26 Jun 92 (JFG) When running over DECnet, suppressed FTP. + * Fixed access bug for relative names on VMS. + * Sep 93 (MD) Access to VMS files allows sharing. + * 15 Nov 93 (MD) Moved HTVMSname to HTVMSUTILS.C + * 27 Dec 93 (FM) FTP now works with VMS hosts. + * FTP path must be Unix-style and cannot include + * the device or top directory. + */ + +#include + +#ifndef VMS +#if defined(DOSPATH) +#undef LONG_LIST +#define LONG_LIST /* Define this for long style unix listings (ls -l), + the actual style is configurable from lynx.cfg */ +#endif +/* #define NO_PARENT_DIR_REFERENCE */ +/* Define this for no parent links */ +#endif /* !VMS */ + +#if defined(DOSPATH) +#define HAVE_READDIR 1 +#define USE_DIRENT +#endif + +#if defined(USE_DOS_DRIVES) +#include +#endif + +#include /* Implemented here */ + +#ifdef VMS +#include +#endif /* VMS */ + +#if defined (USE_ZLIB) || defined (USE_BZLIB) +#include +#endif + +#define MULTI_SUFFIX ".multi" /* Extension for scanning formats */ + +#include +#include +#ifndef DECNET +#include +#endif /* !DECNET */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifdef USE_PRETTYSRC +# include +#endif + +#include + +typedef struct _HTSuffix { + char *suffix; + HTAtom *rep; + HTAtom *encoding; + char *desc; + float quality; +} HTSuffix; + +typedef struct { + struct stat file_info; + char sort_tags; + char file_name[1]; /* on the end of the struct, since its length varies */ +} DIRED; + +#ifndef NGROUPS +#ifdef NGROUPS_MAX +#define NGROUPS NGROUPS_MAX +#else +#define NGROUPS 32 +#endif /* NGROUPS_MAX */ +#endif /* NGROUPS */ + +#ifndef GETGROUPS_T +#define GETGROUPS_T int +#endif + +#include /* For directory object building */ + +#define PUTC(c) (*target->isa->put_character)(target, c) +#define PUTS(s) (*target->isa->put_string)(target, s) +#define START(e) (*target->isa->start_element)(target, e, 0, 0, -1, 0) +#define END(e) (*target->isa->end_element)(target, e, 0) +#define MAYBE_END(e) if (HTML_dtd.tags[e].contents != SGML_EMPTY) \ + (*target->isa->end_element)(target, e, 0) +#define FREE_TARGET (*target->isa->_free)(target) +#define ABORT_TARGET (*targetClass._abort)(target, NULL); + +struct _HTStructured { + const HTStructuredClass *isa; + /* ... */ +}; + +/* + * Controlling globals. + */ +int HTDirAccess = HT_DIR_OK; + +#ifdef DIRED_SUPPORT +int HTDirReadme = HT_DIR_README_NONE; + +#else +int HTDirReadme = HT_DIR_README_TOP; +#endif /* DIRED_SUPPORT */ + +static const char *HTMountRoot = "/Net/"; /* Where to find mounts */ + +#ifdef VMS +static const char *HTCacheRoot = "/WWW$SCRATCH"; /* Where to cache things */ + +#else +static const char *HTCacheRoot = "/tmp/W3_Cache_"; /* Where to cache things */ +#endif /* VMS */ + +static char s_no_suffix[] = "*"; +static char s_unknown_suffix[] = "*.*"; + +/* + * Suffix registration. + */ +static HTList *HTSuffixes = 0; + +static HTSuffix no_suffix = +{ + s_no_suffix, NULL, NULL, NULL, 1.0 +}; + +static HTSuffix unknown_suffix = +{ + s_unknown_suffix, NULL, NULL, NULL, 1.0 +}; + +/* To free up the suffixes at program exit. + * ---------------------------------------- + */ +#ifdef LY_FIND_LEAKS +static void free_suffixes(void); +#endif + +#define FindSearch(filename) strchr(filename, '?') + +#ifdef LONG_LIST +static char *FormatStr(char **bufp, + char *start, + const char *entry) +{ + char fmt[512]; + + if (*start) { + sprintf(fmt, "%%%.*ss", (int) sizeof(fmt) - 3, start); + HTSprintf0(bufp, fmt, entry); + } else if (*bufp && !(entry && *entry)) { + **bufp = '\0'; + } else if (entry) { + StrAllocCopy(*bufp, entry); + } + return *bufp; +} + +static char *FormatSize(char **bufp, + char *start, + off_t entry) +{ + char fmt[512]; + + if (*start) { + sprintf(fmt, "%%%.*s" PRI_off_t, + (int) sizeof(fmt) - DigitsOf(start) - 3, start); + + HTSprintf0(bufp, fmt, entry); + } else { + sprintf(fmt, "%" PRI_off_t, CAST_off_t (entry)); + + StrAllocCopy(*bufp, fmt); + } + return *bufp; +} + +static char *FormatNum(char **bufp, + char *start, + int entry) +{ + char fmt[512]; + + if (*start) { + sprintf(fmt, "%%%.*sd", (int) sizeof(fmt) - 3, start); + HTSprintf0(bufp, fmt, entry); + } else { + sprintf(fmt, "%d", entry); + StrAllocCopy(*bufp, fmt); + } + return *bufp; +} + +static void LYListFmtParse(const char *fmtstr, + DIRED * data, + char *file, + HTStructured * target, + char *tail) +{ + char c; + char *s; + char *end; + char *start; + char *str = NULL; + char *buf = NULL; + char tmp[LY_MAXPATH]; + char type; + +#ifndef NOUSERS + const char *name; +#endif + time_t now; + char *datestr; + +#ifdef S_IFLNK + int len; +#endif +#define SEC_PER_YEAR (60 * 60 * 24 * 365) + +#ifdef _WINDOWS /* 1998/01/06 (Tue) 21:20:53 */ + static const char *pbits[] = + { + "---", "--x", "-w-", "-wx", + "r--", "r-x", "rw-", "rwx", + 0}; + +#define PBIT(a, n, s) pbits[((a) >> (n)) & 0x7] + +#else + static const char *pbits[] = + {"---", "--x", "-w-", "-wx", + "r--", "r-x", "rw-", "rwx", 0}; + static const char *psbits[] = + {"--S", "--s", "-wS", "-ws", + "r-S", "r-s", "rwS", "rws", 0}; + +#define PBIT(a, n, s) (s) ? psbits[((a) >> (n)) & 0x7] : \ + pbits[((a) >> (n)) & 0x7] +#endif +#if defined(S_ISVTX) && !defined(_WINDOWS) + static const char *ptbits[] = + {"--T", "--t", "-wT", "-wt", + "r-T", "r-t", "rwT", "rwt", 0}; + +#define PTBIT(a, s) (s) ? ptbits[(a) & 0x7] : pbits[(a) & 0x7] +#else +#define PTBIT(a, s) PBIT(a, 0, 0) +#endif + + if (data->file_info.st_mode == 0) + fmtstr = " %a"; /* can't stat so just do anchor */ + + StrAllocCopy(str, fmtstr); + s = str; + end = str + strlen(str); + while (*s) { + start = s; + while (*s) { + if (*s == '%') { + if (*(s + 1) == '%') /* literal % */ + s++; + else + break; + } + s++; + } + /* s is positioned either at a % or at \0 */ + *s = '\0'; + if (s > start) { /* some literal chars. */ + PUTS(start); + } + if (s == end) + break; + start = ++s; + while (isdigit(UCH(*s)) || *s == '.' || *s == '-' || *s == ' ' || + *s == '#' || *s == '+' || *s == '\'') + s++; + c = *s; /* the format char. or \0 */ + *s = '\0'; + + switch (c) { + case '\0': + PUTS(start); + continue; + + case 'A': + case 'a': /* anchor */ + HTDirEntry(target, tail, data->file_name); + FormatStr(&buf, start, data->file_name); + PUTS(buf); + END(HTML_A); + *buf = '\0'; +#ifdef S_IFLNK + if (c != 'A' && S_ISLNK(data->file_info.st_mode) && + (len = (int) readlink(file, tmp, sizeof(tmp) - 1)) >= 0) { + PUTS(" -> "); + tmp[len] = '\0'; + PUTS(tmp); + } +#endif + break; + + case 'T': /* MIME type */ + case 't': /* MIME type description */ + if (S_ISDIR(data->file_info.st_mode)) { + if (c != 'T') { + FormatStr(&buf, start, ENTRY_IS_DIRECTORY); + } else { + FormatStr(&buf, start, ""); + } + } else { + const char *cp2; + HTFormat format; + + format = HTFileFormat(file, NULL, &cp2); + + if (c != 'T') { + if (cp2 == NULL) { + if (!StrNCmp(HTAtom_name(format), + "application", 11)) { + cp2 = HTAtom_name(format) + 12; + if (!StrNCmp(cp2, "x-", 2)) + cp2 += 2; + } else { + cp2 = HTAtom_name(format); + } + } + FormatStr(&buf, start, cp2); + } else { + FormatStr(&buf, start, HTAtom_name(format)); + } + } + break; + + case 'd': /* date */ + now = time(0); + datestr = ctime(&data->file_info.st_mtime); + if ((now - data->file_info.st_mtime) < SEC_PER_YEAR / 2) + /* + * MMM DD HH:MM + */ + sprintf(tmp, "%.12s", datestr + 4); + else + /* + * MMM DD YYYY + */ + sprintf(tmp, "%.7s %.4s ", datestr + 4, + datestr + 20); + FormatStr(&buf, start, tmp); + break; + + case 's': /* size in bytes */ + FormatSize(&buf, start, data->file_info.st_size); + break; + + case 'K': /* size in Kilobytes but not for directories */ + if (S_ISDIR(data->file_info.st_mode)) { + FormatStr(&buf, start, ""); + StrAllocCat(buf, " "); + break; + } + /* FALL THROUGH */ + case 'k': /* size in Kilobytes */ + FormatSize(&buf, start, ((data->file_info.st_size + 1023) / 1024)); + StrAllocCat(buf, "K"); + break; + + case 'p': /* unix-style permission bits */ + switch (data->file_info.st_mode & S_IFMT) { +#if defined(_MSC_VER) && defined(_S_IFIFO) + case _S_IFIFO: + type = 'p'; + break; +#else + case S_IFIFO: + type = 'p'; + break; +#endif + case S_IFCHR: + type = 'c'; + break; + case S_IFDIR: + type = 'd'; + break; + case S_IFREG: + type = '-'; + break; +#ifdef S_IFBLK + case S_IFBLK: + type = 'b'; + break; +#endif +#ifdef S_IFLNK + case S_IFLNK: + type = 'l'; + break; +#endif +#ifdef S_IFSOCK +# ifdef S_IFIFO /* some older machines (e.g., apollo) have a conflict */ +# if S_IFIFO != S_IFSOCK + case S_IFSOCK: + type = 's'; + break; +# endif +# else + case S_IFSOCK: + type = 's'; + break; +# endif +#endif /* S_IFSOCK */ + default: + type = '?'; + break; + } +#ifdef _WINDOWS + sprintf(tmp, "%c%s", type, + PBIT(data->file_info.st_mode, 6, data->file_info.st_mode & S_IRWXU)); +#else + sprintf(tmp, "%c%s%s%s", type, + PBIT(data->file_info.st_mode, 6, data->file_info.st_mode & S_ISUID), + PBIT(data->file_info.st_mode, 3, data->file_info.st_mode & S_ISGID), + PTBIT(data->file_info.st_mode, data->file_info.st_mode & S_ISVTX)); +#endif + FormatStr(&buf, start, tmp); + break; + + case 'o': /* owner */ +#ifndef NOUSERS + name = HTAA_UidToName((int) data->file_info.st_uid); + if (*name) { + FormatStr(&buf, start, name); + } else { + FormatNum(&buf, start, (int) data->file_info.st_uid); + } +#endif + break; + + case 'g': /* group */ +#ifndef NOUSERS + name = HTAA_GidToName((int) data->file_info.st_gid); + if (*name) { + FormatStr(&buf, start, name); + } else { + FormatNum(&buf, start, (int) data->file_info.st_gid); + } +#endif + break; + + case 'l': /* link count */ + FormatNum(&buf, start, (int) data->file_info.st_nlink); + break; + + case '%': /* literal % with flags/width */ + FormatStr(&buf, start, "%"); + break; + + default: + fprintf(stderr, + "Unknown format character `%c' in list format\n", c); + break; + } + if (buf) + PUTS(buf); + + s++; + } + FREE(buf); + PUTC('\n'); + FREE(str); +} +#endif /* LONG_LIST */ + +/* Define the representation associated with a file suffix. + * -------------------------------------------------------- + * + * Calling this with suffix set to "*" will set the default + * representation. + * Calling this with suffix set to "*.*" will set the default + * representation for unknown suffix files which contain a ".". + * + * The encoding parameter can give a trivial (8bit, 7bit, binary) + * or real (gzip, compress) encoding. + * + * If filename suffix is already defined with the same encoding + * its previous definition is overridden. + */ +void HTSetSuffix5(const char *suffix, + const char *representation, + const char *encoding, + const char *desc, + double value) +{ + HTSuffix *suff; + BOOL trivial_enc = (BOOL) IsUnityEncStr(encoding); + + if (strcmp(suffix, s_no_suffix) == 0) + suff = &no_suffix; + else if (strcmp(suffix, s_unknown_suffix) == 0) + suff = &unknown_suffix; + else { + HTList *cur = HTSuffixes; + + while (NULL != (suff = (HTSuffix *) HTList_nextObject(cur))) { + if (suff->suffix && 0 == strcmp(suff->suffix, suffix) && + ((trivial_enc && IsUnityEnc(suff->encoding)) || + (!trivial_enc && !IsUnityEnc(suff->encoding) && + strcmp(encoding, HTAtom_name(suff->encoding)) == 0))) + break; + } + if (!suff) { /* Not found -- create a new node */ + suff = typecalloc(HTSuffix); + if (suff == NULL) + outofmem(__FILE__, "HTSetSuffix"); + + if (!HTSuffixes) { + HTSuffixes = HTList_new(); +#ifdef LY_FIND_LEAKS + atexit(free_suffixes); +#endif + } + + HTList_addObject(HTSuffixes, suff); + + StrAllocCopy(suff->suffix, suffix); + } + } + + if (representation) + suff->rep = HTAtom_for(representation); + + /* + * Memory leak fixed. + * 05-28-94 Lynx 2-3-1 Garrett Arch Blythe + * Invariant code removed. + */ + suff->encoding = HTAtom_for(encoding); + + StrAllocCopy(suff->desc, desc); + + suff->quality = (float) value; +} + +#ifdef LY_FIND_LEAKS +/* + * Purpose: Free all added suffixes. + * Arguments: void + * Return Value: void + * Remarks/Portability/Dependencies/Restrictions: + * To be used at program exit. + * Revision History: + * 05-28-94 created Lynx 2-3-1 Garrett Arch Blythe + */ +static void free_suffixes(void) +{ + HTSuffix *suff = NULL; + + /* + * Loop through all suffixes. + */ + while (!HTList_isEmpty(HTSuffixes)) { + /* + * Free off each item and its members if need be. + */ + suff = (HTSuffix *) HTList_removeLastObject(HTSuffixes); + FREE(suff->suffix); + FREE(suff->desc); + FREE(suff); + } + /* + * Free off the list itself. + */ + HTList_delete(HTSuffixes); + HTSuffixes = NULL; +} +#endif /* LY_FIND_LEAKS */ + +/* Make the cache file name for a W3 document. + * ------------------------------------------- + * Make up a suitable name for saving the node in + * + * E.g. /tmp/WWW_Cache_news/1234@cernvax.cern.ch + * /tmp/WWW_Cache_http/crnvmc/FIND/xx.xxx.xx + * + * On exit: + * Returns a malloc'ed string which must be freed by the caller. + */ +char *HTCacheFileName(const char *name) +{ + char *acc_method = HTParse(name, "", PARSE_ACCESS); + char *host = HTParse(name, "", PARSE_HOST); + char *path = HTParse(name, "", PARSE_PATH + PARSE_PUNCTUATION); + char *result = NULL; + + HTSprintf0(&result, "%s/WWW/%s/%s%s", HTCacheRoot, acc_method, host, path); + + FREE(path); + FREE(acc_method); + FREE(host); + return result; +} + +/* Open a file for write, creating the path. + * ----------------------------------------- + */ +#ifdef NOT_IMPLEMENTED +static int HTCreatePath(const char *path) +{ + return -1; +} +#endif /* NOT_IMPLEMENTED */ + +/* Convert filename from URL-path syntax to local path format + * ---------------------------------------------------------- + * Input name is assumed to be the URL-path of a local file + * URL, i.e. what comes after the "file://localhost". + * '#'-fragments to be treated as such must already be stripped. + * If expand_all is FALSE, unescape only escaped '/'. - kw + * + * On exit: + * Returns a malloc'ed string which must be freed by the caller. + */ +char *HTURLPath_toFile(const char *name, + int expand_all, + int is_remote GCC_UNUSED) +{ + char *path = NULL; + char *result = NULL; + + StrAllocCopy(path, name); + if (expand_all) + HTUnEscape(path); /* Interpret all % signs */ + else + HTUnEscapeSome(path, "/"); /* Interpret % signs for path delims */ + + CTRACE((tfp, "URLPath `%s' means path `%s'\n", name, path)); +#if defined(USE_DOS_DRIVES) + StrAllocCopy(result, is_remote ? path : HTDOS_name(path)); +#else + StrAllocCopy(result, path); +#endif + + FREE(path); + + return result; +} +/* Convert filenames between local and WWW formats. + * ------------------------------------------------ + * Make up a suitable name for saving the node in + * + * E.g. $(HOME)/WWW/news/1234@cernvax.cern.ch + * $(HOME)/WWW/http/crnvmc/FIND/xx.xxx.xx + * + * On exit: + * Returns a malloc'ed string which must be freed by the caller. + */ +/* NOTE: Don't use this function if you know that the input is a URL path + rather than a full URL, use HTURLPath_toFile instead. Otherwise + this function will return the wrong thing for some unusual + paths (like ones containing "//", possibly escaped). - kw +*/ +char *HTnameOfFile_WWW(const char *name, + int WWW_prefix, + int expand_all) +{ + char *acc_method = HTParse(name, "", PARSE_ACCESS); + char *host = HTParse(name, "", PARSE_HOST); + char *path = HTParse(name, "", PARSE_PATH + PARSE_PUNCTUATION); + const char *home; + char *result = NULL; + + if (expand_all) { + HTUnEscape(path); /* Interpret all % signs */ + } else + HTUnEscapeSome(path, "/"); /* Interpret % signs for path delims */ + + if (0 == strcmp(acc_method, "file") /* local file */ + ||!*acc_method) { /* implicitly local? */ + if ((0 == strcasecomp(host, HTHostName())) || + (0 == strcasecomp(host, "localhost")) || !*host) { + CTRACE((tfp, "Node `%s' means path `%s'\n", name, path)); + StrAllocCopy(result, HTSYS_name(path)); + } else if (WWW_prefix) { + HTSprintf0(&result, "%s%s%s", "/Net/", host, path); + CTRACE((tfp, "Node `%s' means file `%s'\n", name, result)); + } else { + StrAllocCopy(result, path); + } + } else if (WWW_prefix) { /* other access */ +#ifdef VMS + if ((home = LYGetEnv("HOME")) == NULL) + home = HTCacheRoot; + else + home = HTVMS_wwwName(home); +#else +#if defined(_WINDOWS) /* 1997/10/16 (Thu) 20:42:51 */ + home = Home_Dir(); +#else + home = LYGetEnv("HOME"); +#endif + if (home == NULL) + home = "/tmp"; +#endif /* VMS */ + HTSprintf0(&result, "%s/WWW/%s/%s%s", home, acc_method, host, path); + } else { + StrAllocCopy(result, path); + } + + FREE(host); + FREE(path); + FREE(acc_method); + + CTRACE((tfp, "HTnameOfFile_WWW(%s,%d,%d) = %s\n", + name, WWW_prefix, expand_all, result)); + + return result; +} + +/* Make a WWW name from a full local path name. + * -------------------------------------------- + * + * Bugs: + * At present, only the names of two network root nodes are hand-coded + * in and valid for the NeXT only. This should be configurable in + * the general case. + */ +char *WWW_nameOfFile(const char *name) +{ + char *result = NULL; + +#ifdef NeXT + if (0 == StrNCmp("/private/Net/", name, 13)) { + HTSprintf0(&result, "%s//%s", STR_FILE_URL, name + 13); + } else +#endif /* NeXT */ + if (0 == StrNCmp(HTMountRoot, name, 5)) { + HTSprintf0(&result, "%s//%s", STR_FILE_URL, name + 5); + } else { + HTSprintf0(&result, "%s//%s%s", STR_FILE_URL, HTHostName(), name); + } + CTRACE((tfp, "File `%s'\n\tmeans node `%s'\n", name, result)); + return result; +} + +/* Determine a suitable suffix, given the representation. + * ------------------------------------------------------ + * + * On entry, + * rep is the atomized MIME style representation + * enc is an encoding, trivial (8bit, binary, etc.) or gzip etc. + * + * On exit: + * Returns a pointer to a suitable suffix string if one has been + * found, else "". + */ +const char *HTFileSuffix(HTAtom *rep, + const char *enc) +{ + HTSuffix *suff; + +#ifdef FNAMES_8_3 + HTSuffix *first_found = NULL; +#endif + BOOL trivial_enc; + int n; + int i; + +#define NO_INIT /* don't init anymore since I do it in Lynx at startup */ +#ifndef NO_INIT + if (!HTSuffixes) + HTFileInit(); +#endif /* !NO_INIT */ + + trivial_enc = (BOOL) IsUnityEncStr(enc); + n = HTList_count(HTSuffixes); + for (i = 0; i < n; i++) { + suff = (HTSuffix *) HTList_objectAt(HTSuffixes, i); + if (suff->rep == rep && +#if defined(VMS) || defined(FNAMES_8_3) + /* Don't return a suffix whose first char is a dot, and which + has more dots or asterisks after that, for + these systems - kw */ + (!suff->suffix || !suff->suffix[0] || suff->suffix[0] != '.' || + (StrChr(suff->suffix + 1, '.') == NULL && + StrChr(suff->suffix + 1, '*') == NULL)) && +#endif + ((trivial_enc && IsUnityEnc(suff->encoding)) || + (!trivial_enc && !IsUnityEnc(suff->encoding) && + strcmp(enc, HTAtom_name(suff->encoding)) == 0))) { +#ifdef FNAMES_8_3 + if (suff->suffix && (strlen(suff->suffix) <= 4)) { + /* + * If length of suffix (including dot) is 4 or smaller, return + * this one even if we found a longer one earlier - kw + */ + return suff->suffix; + } else if (!first_found) { + first_found = suff; /* remember this one */ + } +#else + return suff->suffix; /* OK -- found */ +#endif + } + } +#ifdef FNAMES_8_3 + if (first_found) + return first_found->suffix; +#endif + return ""; /* Dunno */ +} + +/* + * Trim version from VMS filenames to avoid confusing comparisons. + */ +#ifdef VMS +static const char *VMS_trim_version(const char *filename) +{ + const char *result = filename; + const char *version = StrChr(filename, ';'); + + if (version != 0) { + static char *stripped; + + StrAllocCopy(stripped, filename); + stripped[version - filename] = '\0'; + result = (const char *) stripped; + } + return result; +} +#define VMS_DEL_VERSION(name) name = VMS_trim_version(name) +#else +#define VMS_DEL_VERSION(name) /* nothing */ +#endif + +/* Determine file format from file name. + * ------------------------------------- + * + * This version will return the representation and also set + * a variable for the encoding. + * + * Encoding may be a unity encoding (binary, 8bit, etc.) or + * a content-coding like gzip, compress. + * + * It will handle for example x.txt, x.txt,Z, x.Z + */ +HTFormat HTFileFormat(const char *filename, + HTAtom **pencoding, + const char **pdesc) +{ + HTSuffix *suff; + int n; + int i; + int lf; + char *search; + + VMS_DEL_VERSION(filename); + + if ((search = FindSearch(filename)) != 0) { + char *newname = NULL; + HTFormat result; + + StrAllocCopy(newname, filename); + newname[((const char *) search) - filename] = '\0'; + result = HTFileFormat(newname, pencoding, pdesc); + free(newname); + return result; + } + + if (pencoding) + *pencoding = NULL; + if (pdesc) + *pdesc = NULL; + if (LYforce_HTML_mode) { + if (pencoding) + *pencoding = WWW_ENC_8BIT; + return WWW_HTML; + } +#ifndef NO_INIT + if (!HTSuffixes) + HTFileInit(); +#endif /* !NO_INIT */ + lf = (int) strlen(filename); + n = HTList_count(HTSuffixes); + for (i = 0; i < n; i++) { + int ls; + + suff = (HTSuffix *) HTList_objectAt(HTSuffixes, i); + ls = (int) strlen(suff->suffix); + if ((ls <= lf) && 0 == strcasecomp(suff->suffix, filename + lf - ls)) { + int j; + + if (pencoding) + *pencoding = suff->encoding; + if (pdesc) + *pdesc = suff->desc; + if (suff->rep) { + return suff->rep; /* OK -- found */ + } + for (j = 0; j < n; j++) { /* Got encoding, need representation */ + int ls2; + + suff = (HTSuffix *) HTList_objectAt(HTSuffixes, j); + ls2 = (int) strlen(suff->suffix); + if ((ls + ls2 <= lf) && + !strncasecomp(suff->suffix, + filename + lf - ls - ls2, ls2)) { + if (suff->rep) { + if (pdesc && !(*pdesc)) + *pdesc = suff->desc; + if (pencoding && IsUnityEnc(*pencoding) && + *pencoding != WWW_ENC_7BIT && + !IsUnityEnc(suff->encoding)) + *pencoding = suff->encoding; + return suff->rep; + } + } + } + + } + } + + /* defaults tree */ + + suff = (StrChr(filename, '.') + ? (unknown_suffix.rep + ? &unknown_suffix + : &no_suffix) + : &no_suffix); + + /* + * Set default encoding unless found with suffix already. + */ + if (pencoding && !*pencoding) { + *pencoding = (suff->encoding + ? suff->encoding + : HTAtom_for("binary")); + } + return suff->rep ? suff->rep : WWW_BINARY; +} + +/* Revise the file format in relation to the Lynx charset. - FM + * ------------------------------------------------------- + * + * This checks the format associated with an anchor for + * an extended MIME Content-Type, and if a charset is + * indicated, sets Lynx up for proper handling in relation + * to the currently selected character set. - FM + */ +HTFormat HTCharsetFormat(HTFormat format, + HTParentAnchor *anchor, + int default_LYhndl) +{ + char *cp = NULL, *cp1, *cp2, *cp3 = NULL, *cp4; + BOOL chartrans_ok = FALSE; + int chndl = -1; + const char *format_name = format->name; + + FREE(anchor->charset); + if (format_name == 0) + format_name = ""; + StrAllocCopy(cp, format_name); + LYLowerCase(cp); + if (((cp1 = StrChr(cp, ';')) != NULL) && + (cp2 = strstr(cp1, "charset")) != NULL) { + CTRACE((tfp, "HTCharsetFormat: Extended MIME Content-Type is %s\n", + format_name)); + cp2 += 7; + while (*cp2 == ' ' || *cp2 == '=') + cp2++; + StrAllocCopy(cp3, cp2); /* copy to mutilate more */ + for (cp4 = cp3; (*cp4 != '\0' && *cp4 != '"' && + *cp4 != ';' && *cp4 != ':' && + !WHITE(*cp4)); cp4++) { + ; /* do nothing */ + } + *cp4 = '\0'; + cp4 = cp3; + chndl = UCGetLYhndl_byMIME(cp3); + if (UCCanTranslateFromTo(chndl, current_char_set)) { + chartrans_ok = YES; + *cp1 = '\0'; + format = HTAtom_for(cp); + StrAllocCopy(anchor->charset, cp4); + HTAnchor_setUCInfoStage(anchor, chndl, + UCT_STAGE_MIME, + UCT_SETBY_MIME); + } else if (chndl < 0) { + /* + * Got something but we don't recognize it. + */ + chndl = UCLYhndl_for_unrec; + if (chndl < 0) + /* + * UCLYhndl_for_unrec not defined :-( fallback to + * UCLYhndl_for_unspec which always valid. + */ + chndl = UCLYhndl_for_unspec; /* always >= 0 */ + if (UCCanTranslateFromTo(chndl, current_char_set)) { + chartrans_ok = YES; + HTAnchor_setUCInfoStage(anchor, chndl, + UCT_STAGE_MIME, + UCT_SETBY_DEFAULT); + } + } + if (chartrans_ok) { + LYUCcharset *p_in = HTAnchor_getUCInfoStage(anchor, + UCT_STAGE_MIME); + LYUCcharset *p_out = HTAnchor_setUCInfoStage(anchor, + current_char_set, + UCT_STAGE_HTEXT, + UCT_SETBY_DEFAULT); + + if (!p_out) { + /* + * Try again. + */ + p_out = HTAnchor_getUCInfoStage(anchor, UCT_STAGE_HTEXT); + } + if (!strcmp(p_in->MIMEname, "x-transparent")) { + HTPassEightBitRaw = TRUE; + HTAnchor_setUCInfoStage(anchor, + HTAnchor_getUCLYhndl(anchor, + UCT_STAGE_HTEXT), + UCT_STAGE_MIME, + UCT_SETBY_DEFAULT); + } + if (!strcmp(p_out->MIMEname, "x-transparent")) { + HTPassEightBitRaw = TRUE; + HTAnchor_setUCInfoStage(anchor, + HTAnchor_getUCLYhndl(anchor, + UCT_STAGE_MIME), + UCT_STAGE_HTEXT, + UCT_SETBY_DEFAULT); + } + if (p_in->enc != UCT_ENC_CJK) { + HTCJK = NOCJK; + if (!(p_in->codepoints & + UCT_CP_SUBSETOF_LAT1) && + chndl == current_char_set) { + HTPassEightBitRaw = TRUE; + } + } else if (p_out->enc == UCT_ENC_CJK) { + Set_HTCJK(p_in->MIMEname, p_out->MIMEname); + } + } else { + /* + * Cannot translate. If according to some heuristic the given + * charset and the current display character both are likely to be + * like ISO-8859 in structure, pretend we have some kind of match. + */ + BOOL given_is_8859 = (BOOL) (!StrNCmp(cp4, "iso-8859-", 9) && + isdigit(UCH(cp4[9]))); + BOOL given_is_8859like = (BOOL) (given_is_8859 || + !StrNCmp(cp4, "windows-", 8) || + !StrNCmp(cp4, "cp12", 4) || + !StrNCmp(cp4, "cp-12", 5)); + BOOL given_and_display_8859like = (BOOL) (given_is_8859like && + (strstr(LYchar_set_names[current_char_set], + "ISO-8859") || + strstr(LYchar_set_names[current_char_set], + "windows-"))); + + if (given_and_display_8859like) { + *cp1 = '\0'; + format = HTAtom_for(cp); + } + if (given_is_8859) { + cp1 = &cp4[10]; + while (*cp1 && + isdigit(UCH(*cp1))) + cp1++; + *cp1 = '\0'; + } + if (given_and_display_8859like) { + StrAllocCopy(anchor->charset, cp4); + HTPassEightBitRaw = TRUE; + } + HTAlert(*cp4 ? cp4 : anchor->charset); + } + FREE(cp3); + } else if (cp1 != NULL) { + /* + * No charset parameter is present. Ignore all other parameters, as we + * do when charset is present. - FM + */ + *cp1 = '\0'; + format = HTAtom_for(cp); + } + FREE(cp); + + /* + * Set up defaults, if needed. - FM + */ + if (!chartrans_ok && !anchor->charset && default_LYhndl >= 0) { + HTAnchor_setUCInfoStage(anchor, default_LYhndl, + UCT_STAGE_MIME, + UCT_SETBY_DEFAULT); + } + HTAnchor_copyUCInfoStage(anchor, + UCT_STAGE_PARSER, + UCT_STAGE_MIME, + -1); + + return format; +} + +/* Get various pieces of meta info from file name. + * ----------------------------------------------- + * + * LYGetFileInfo fills in information that can be determined without + * an actual (new) access to the filesystem, based on current suffix + * and character set configuration. If the file has been loaded and + * parsed before (with the same URL generated here!) and the anchor + * is still around, some results may be influenced by that (in + * particular, charset info from a META tag - this is not actually + * tested!). + * The caller should not keep pointers to the returned objects around + * for too long, the valid lifetimes vary. In particular, the returned + * charset string should be copied if necessary. If return of the + * file_anchor is requested, that one can be used to retrieve + * additional bits of info that are stored in the anchor object and + * are not covered here; as usual, don't keep pointers to the + * file_anchor longer than necessary since the object may disappear + * through HTuncache_current_document or at the next document load. + * - kw + */ +void LYGetFileInfo(const char *filename, + HTParentAnchor **pfile_anchor, + HTFormat *pformat, + HTAtom **pencoding, + const char **pdesc, + const char **pcharset, + int *pfile_cs) +{ + char *Afn; + char *Aname = NULL; + HTFormat format; + HTAtom *myEnc = NULL; + HTParentAnchor *file_anchor; + const char *file_csname; + int file_cs; + + /* + * Convert filename to URL. Note that it is always supposed to be a + * filename, not maybe-filename-maybe-URL, so we don't use + * LYFillLocalFileURL and LYEnsureAbsoluteURL. - kw + */ + Afn = HTEscape(filename, URL_PATH); + LYLocalFileToURL(&Aname, Afn); + file_anchor = HTAnchor_findSimpleAddress(Aname); + + format = HTFileFormat(filename, &myEnc, pdesc); + format = HTCharsetFormat(format, file_anchor, UCLYhndl_HTFile_for_unspec); + file_cs = HTAnchor_getUCLYhndl(file_anchor, UCT_STAGE_MIME); + file_csname = file_anchor->charset; + if (!file_csname) { + if (file_cs >= 0) + file_csname = LYCharSet_UC[file_cs].MIMEname; + else + file_csname = "display character set"; + } + CTRACE((tfp, "GetFileInfo: '%s' is a%s %s %s file, charset=%s (%d).\n", + filename, + ((myEnc && *HTAtom_name(myEnc) == '8') ? "n" : myEnc ? "" : + *HTAtom_name(format) == 'a' ? "n" : ""), + myEnc ? HTAtom_name(myEnc) : "", + HTAtom_name(format), + file_csname, + file_cs)); + FREE(Afn); + FREE(Aname); + if (pfile_anchor) + *pfile_anchor = file_anchor; + if (pformat) + *pformat = format; + if (pencoding) + *pencoding = myEnc; + if (pcharset) + *pcharset = file_csname; + if (pfile_cs) + *pfile_cs = file_cs; +} + +/* Determine value from file name. + * ------------------------------- + * + */ +float HTFileValue(const char *filename) +{ + HTSuffix *suff; + int n; + int i; + int lf = (int) strlen(filename); + +#ifndef NO_INIT + if (!HTSuffixes) + HTFileInit(); +#endif /* !NO_INIT */ + n = HTList_count(HTSuffixes); + for (i = 0; i < n; i++) { + int ls; + + suff = (HTSuffix *) HTList_objectAt(HTSuffixes, i); + ls = (int) strlen(suff->suffix); + if ((ls <= lf) && 0 == strcmp(suff->suffix, filename + lf - ls)) { + CTRACE((tfp, "File: Value of %s is %.3f\n", + filename, suff->quality)); + return suff->quality; /* OK -- found */ + } + } + return (float) 0.3; /* Dunno! */ +} + +/* + * Determine compression type from file name, by looking at its suffix. + * Sets as side-effect a pointer to the "dot" that begins the suffix. + */ +CompressFileType HTCompressFileType(const char *filename, + const char *dots, + int *rootlen) +{ + CompressFileType result = cftNone; + char *search; + + if ((search = FindSearch(filename)) != 0) { + char *newname = NULL; + + StrAllocCopy(newname, filename); + newname[((const char *) search) - filename] = '\0'; + result = HTCompressFileType(newname, dots, rootlen); + free(newname); + } else { + size_t len; + const char *ftype; + + VMS_DEL_VERSION(filename); + len = strlen(filename); + ftype = filename + len; + + if ((len > 3) + && !strcasecomp((ftype - 2), "br") + && StrChr(dots, ftype[-3]) != 0) { + result = cftBrotli; + ftype -= 3; + } else if ((len > 4) + && !strcasecomp((ftype - 3), "bz2") + && StrChr(dots, ftype[-4]) != 0) { + result = cftBzip2; + ftype -= 4; + } else if ((len > 3) + && !strcasecomp((ftype - 2), "gz") + && StrChr(dots, ftype[-3]) != 0) { + result = cftGzip; + ftype -= 3; + } else if ((len > 3) + && !strcasecomp((ftype - 2), "zz") + && StrChr(dots, ftype[-3]) != 0) { + result = cftDeflate; + ftype -= 3; + } else if ((len > 2) + && !strcmp((ftype - 1), "Z") + && StrChr(dots, ftype[-2]) != 0) { + result = cftCompress; + ftype -= 2; + } + + *rootlen = (int) (ftype - filename); + + CTRACE((tfp, "HTCompressFileType(%s) returns %d:%s\n", + filename, (int) result, filename + *rootlen)); + } + return result; +} + +/* + * Determine expected file-suffix from the compression method. + */ +const char *HTCompressTypeToSuffix(CompressFileType method) +{ + const char *result = ""; + + switch (method) { + default: + case cftNone: + result = ""; + break; + case cftGzip: + result = ".gz"; + break; + case cftCompress: + result = ".Z"; + break; + case cftBzip2: + result = ".bz2"; + break; + case cftDeflate: + result = ".zz"; + break; + case cftBrotli: + result = ".br"; + break; + } + return result; +} + +/* + * Determine compression encoding from the compression method. + */ +const char *HTCompressTypeToEncoding(CompressFileType method) +{ + const char *result = NULL; + + switch (method) { + default: + case cftNone: + result = NULL; + break; + case cftGzip: + result = "gzip"; + break; + case cftCompress: + result = "compress"; + break; + case cftBzip2: + result = "bzip2"; + break; + case cftDeflate: + result = "deflate"; + break; + case cftBrotli: + result = "brotli"; + break; + } + return result; +} + +/* + * Check if the token from "Content-Encoding" corresponds to a compression + * type. RFC 2068 (and cut/paste into RFC 2616) lists these: + * gzip + * compress + * deflate + * as well as "identity" (but that does nothing). + */ +CompressFileType HTEncodingToCompressType(const char *coding) +{ + CompressFileType result = cftNone; + + if (coding == NULL) { + result = cftNone; + } else if (!strcasecomp(coding, "gzip") || + !strcasecomp(coding, "x-gzip")) { + result = cftGzip; + } else if (!strcasecomp(coding, "compress") || + !strcasecomp(coding, "x-compress")) { + result = cftCompress; + } else if (!strcasecomp(coding, "bzip2") || + !strcasecomp(coding, "x-bzip2")) { + result = cftBzip2; + } else if (!strcasecomp(coding, "br") || /* actual */ + !strcasecomp(coding, "brotli") || /* expected */ + !strcasecomp(coding, "x-brotli")) { + result = cftBrotli; + } else if (!strcasecomp(coding, "deflate") || + !strcasecomp(coding, "x-deflate")) { + result = cftDeflate; + } + return result; +} + +CompressFileType HTContentTypeToCompressType(const char *ct) +{ + CompressFileType method = cftNone; + + if (ct == NULL) { + method = cftNone; + } else if (!strncasecomp(ct, "application/gzip", 16) || + !strncasecomp(ct, "application/x-gzip", 18)) { + method = cftGzip; + } else if (!strncasecomp(ct, "application/compress", 20) || + !strncasecomp(ct, "application/x-compress", 22)) { + method = cftCompress; + } else if (!strncasecomp(ct, "application/bzip2", 17) || + !strncasecomp(ct, "application/x-bzip2", 19)) { + method = cftBzip2; + } else if (!strncasecomp(ct, "application/br", 14) || + !strncasecomp(ct, "application/brotli", 18) || + !strncasecomp(ct, "application/x-brotli", 20)) { + method = cftBrotli; + } + return method; +} + +/* + * Check the anchor's content_type and content_encoding elements for a gzip or + * Unix compressed file -FM, TD + */ +CompressFileType HTContentToCompressType(HTParentAnchor *anchor) +{ + CompressFileType method = cftNone; + const char *ct = HTAnchor_content_type(anchor); + const char *ce = HTAnchor_content_encoding(anchor); + + if (ct != 0) { + method = HTContentTypeToCompressType(ct); + } else if (ce != 0) { + method = HTEncodingToCompressType(ce); + } + return method; +} + +/* Determine write access to a file. + * --------------------------------- + * + * On exit: + * Returns YES if file can be accessed and can be written to. + * + * Bugs: + * 1. No code for non-unix systems. + * 2. Isn't there a quicker way? + */ +BOOL HTEditable(const char *filename GCC_UNUSED) +{ +#ifndef NO_GROUPS + GETGROUPS_T groups[NGROUPS]; + uid_t myUid; + int ngroups; /* The number of groups */ + struct stat fileStatus; + int i; + + if (stat(filename, &fileStatus)) /* Get details of filename */ + return NO; /* Can't even access file! */ + + ngroups = getgroups(NGROUPS, groups); /* Groups to which I belong */ + myUid = geteuid(); /* Get my user identifier */ + + if (TRACE) { + int i2; + + fprintf(tfp, + "File mode is 0%o, uid=%d, gid=%d. My uid=%d, %d groups (", + (unsigned int) fileStatus.st_mode, + (int) fileStatus.st_uid, + (int) fileStatus.st_gid, + (int) myUid, + (int) ngroups); + for (i2 = 0; i2 < ngroups; i2++) + fprintf(tfp, " %d", (int) groups[i2]); + fprintf(tfp, ")\n"); + } + + if (fileStatus.st_mode & 0002) /* I can write anyway? */ + return YES; + + if ((fileStatus.st_mode & 0200) /* I can write my own file? */ + &&(fileStatus.st_uid == myUid)) + return YES; + + if (fileStatus.st_mode & 0020) /* Group I am in can write? */ + { + for (i = 0; i < ngroups; i++) { + if (groups[i] == fileStatus.st_gid) + return YES; + } + } + CTRACE((tfp, "\tFile is not editable.\n")); +#endif /* NO_GROUPS */ + return NO; /* If no excuse, can't do */ +} + +/* Make a save stream. + * ------------------- + * + * The stream must be used for writing back the file. + * @@@ no backup done + */ +HTStream *HTFileSaveStream(HTParentAnchor *anchor) +{ + const char *addr = anchor->address; + char *localname = HTLocalName(addr); + FILE *fp = fopen(localname, BIN_W); + + FREE(localname); + if (!fp) + return NULL; + + return HTFWriter_new(fp); +} + +/* Output one directory entry. + * --------------------------- + */ +void HTDirEntry(HTStructured * target, const char *tail, const char *entry) +{ + char *relative = NULL; + char *stripped = NULL; + char *escaped = NULL; + int len; + + if (entry == NULL) + entry = ""; + StrAllocCopy(escaped, entry); + LYTrimPathSep(escaped); + if (strcmp(escaped, "..") != 0) { + stripped = escaped; + escaped = HTEscape(stripped, URL_XPALPHAS); + if (((len = (int) strlen(escaped)) > 2) && + escaped[(len - 3)] == '%' && + escaped[(len - 2)] == '2' && + TOUPPER(escaped[(len - 1)]) == 'F') { + escaped[(len - 3)] = '\0'; + } + } + + if (isEmpty(tail)) { + /* + * Handle extra slash at end of path. + */ + HTStartAnchor(target, NULL, (escaped[0] != '\0' ? escaped : "/")); + } else { + /* + * If empty tail, gives absolute ref below. + */ + relative = 0; + HTSprintf0(&relative, "%s%s%s", + tail, + (*escaped != '\0' ? "/" : ""), + escaped); + HTStartAnchor(target, NULL, relative); + FREE(relative); + } + FREE(stripped); + FREE(escaped); +} + +static BOOL view_structured(HTFormat format_out) +{ + BOOL result = FALSE; + +#ifdef USE_PRETTYSRC + if (psrc_view + || (format_out == WWW_DUMP)) + result = TRUE; +#else + if (format_out == WWW_SOURCE) + result = TRUE; +#endif + return result; +} + +/* + * Write a DOCTYPE to the given stream if we happen to want to see the + * source view, or are dumping source. This is not needed when the source + * is not visible, since the document is rendered from a HTStructured object. + */ +void HTStructured_doctype(HTStructured * target, HTFormat format_out) +{ + if (view_structured(format_out)) + PUTS(LYNX_DOCTYPE "\n"); +} + +void HTStructured_meta(HTStructured * target, HTFormat format_out) +{ + if (view_structured(format_out)) + PUTS("\n"); +} +/* Output parent directory entry. + * ------------------------------ + * + * This gives the TITLE and H1 header, and also a link + * to the parent directory if appropriate. + * + * On exit: + * Returns TRUE if an "Up to " link was not created + * for a readable local directory because LONG_LIST is defined + * and NO_PARENT_DIR_REFERENCE is not defined, so that the + * calling function should use LYListFmtParse() to create a link + * to the parent directory. Otherwise, it returns FALSE. - FM + */ +BOOL HTDirTitles(HTStructured * target, HTParentAnchor *anchor, + HTFormat format_out, + int tildeIsTop) +{ + const char *logical = anchor->address; + char *path = HTParse(logical, "", PARSE_PATH + PARSE_PUNCTUATION); + char *current; + char *cp = NULL; + BOOL need_parent_link = FALSE; + int i; + +#if defined(USE_DOS_DRIVES) + BOOL local_link = (strlen(logical) > 18 + && !strncasecomp(logical, "file://localhost/", 17) + && LYIsDosDrive(logical + 17)); + BOOL is_remote = !local_link; + +#else +#define is_remote TRUE +#endif + + /* + * Check tildeIsTop for treating home directory as Welcome (assume the + * tilde is not followed by a username). - FM + */ + if (tildeIsTop && !StrNCmp(path, "/~", 2)) { + if (path[2] == '\0') { + path[1] = '\0'; + } else { + for (i = 0; path[(i + 2)]; i++) { + path[i] = path[(i + 2)]; + } + path[i] = '\0'; + } + } + + /* + * Trim out the ;type= parameter, if present. - FM + */ + if ((cp = strrchr(path, ';')) != NULL) { + if (!strncasecomp((cp + 1), "type=", 5)) { + if (TOUPPER(*(cp + 6)) == 'D' || + TOUPPER(*(cp + 6)) == 'A' || + TOUPPER(*(cp + 6)) == 'I') + *cp = '\0'; + } + cp = NULL; + } + current = LYPathLeaf(path); /* last part or "" */ + + { + char *printable = NULL; + +#ifdef DIRED_SUPPORT + printable = HTURLPath_toFile(((!strncasecomp(path, "/%2F", 4)) /* "//" ? */ + ? (path + 1) + : path), + TRUE, + is_remote); + if (0 == strncasecomp(printable, "/vmsysu:", 8) || + 0 == strncasecomp(printable, "/anonymou.", 10)) { + StrAllocCopy(cp, (printable + 1)); + StrAllocCopy(printable, cp); + FREE(cp); + } +#else + StrAllocCopy(printable, current); + HTUnEscape(printable); +#endif /* DIRED_SUPPORT */ + + HTStructured_doctype(target, format_out); + + START(HTML_HEAD); + PUTC('\n'); + START(HTML_TITLE); + PUTS(*printable ? printable : WELCOME_MSG); + PUTS(SEGMENT_DIRECTORY); + END(HTML_TITLE); + PUTC('\n'); + HTStructured_meta(target, format_out); + END(HTML_HEAD); + PUTC('\n'); + + START(HTML_BODY); + PUTC('\n'); + +#ifdef DIRED_SUPPORT + START(HTML_H2); + PUTS(*printable ? SEGMENT_CURRENT_DIR : ""); + PUTS(*printable ? printable : WELCOME_MSG); + END(HTML_H2); + PUTC('\n'); +#else + START(HTML_H1); + PUTS(*printable ? printable : WELCOME_MSG); + END(HTML_H1); + PUTC('\n'); +#endif /* DIRED_SUPPORT */ + if (((0 == strncasecomp(printable, "vmsysu:", 7)) && + (cp = StrChr(printable, '.')) != NULL && + StrChr(cp, '/') == NULL) || + (0 == strncasecomp(printable, "anonymou.", 9) && + StrChr(printable, '/') == NULL)) { + FREE(printable); + FREE(path); + return (need_parent_link); + } + FREE(printable); + } + +#ifndef NO_PARENT_DIR_REFERENCE + /* + * Make link back to parent directory. + */ + if (current - path > 0 + && LYIsPathSep(current[-1]) + && current[0] != '\0') { /* was a slash AND something else too */ + char *parent = NULL; + char *relative = NULL; + + current[-1] = '\0'; + parent = strrchr(path, '/'); /* penultimate slash */ + + if ((parent && + (!strcmp(parent, "/..") || + !strncasecomp(parent, "/%2F", 4))) || + !strncasecomp(current, "%2F", 3)) { + FREE(path); + return (need_parent_link); + } + + relative = 0; + HTSprintf0(&relative, "%s/..", current); + +#if defined(DOSPATH) || defined(__EMX__) + if (local_link) { + if (parent != 0 && strlen(parent) == 3) { + StrAllocCat(relative, "/."); + } + } else +#endif + +#if !defined (VMS) + { + /* + * On Unix, if it's not ftp and the directory cannot be read, don't + * put out a link. + * + * On VMS, this problem is dealt with internally by + * HTVMSBrowseDir(). + */ + DIR *dp = NULL; + + if (LYisLocalFile(logical)) { + /* + * We need an absolute file path for the opendir. We also need + * to unescape for this test. Don't worry about %2F now, they + * presumably have been dealt with above, and shouldn't appear + * for local files anyway... Assume OS / filesystem will just + * ignore superfluous slashes. - KW + */ + char *fullparentpath = NULL; + + /* + * Path has been shortened above. + */ + StrAllocCopy(fullparentpath, *path ? path : "/"); + + /* + * Guard against weirdness. + */ + if (0 == strcmp(current, "..")) { + StrAllocCat(fullparentpath, "/../.."); + } else if (0 == strcmp(current, ".")) { + StrAllocCat(fullparentpath, "/.."); + } + + HTUnEscape(fullparentpath); + if ((dp = opendir(fullparentpath)) == NULL) { + FREE(fullparentpath); + FREE(relative); + FREE(path); + return (need_parent_link); + } + closedir(dp); + FREE(fullparentpath); +#ifdef LONG_LIST + need_parent_link = TRUE; + FREE(path); + FREE(relative); + return (need_parent_link); +#endif /* LONG_LIST */ + } + } +#endif /* !VMS */ + HTStartAnchor(target, "", relative); + FREE(relative); + + PUTS(SEGMENT_UP_TO); + if (parent) { + if ((0 == strcmp(current, ".")) || + (0 == strcmp(current, ".."))) { + /* + * Should not happen, but if it does, at least avoid giving + * misleading info. - KW + */ + PUTS(".."); + } else { + char *printable = NULL; + + StrAllocCopy(printable, parent + 1); + HTUnEscape(printable); + PUTS(printable); + FREE(printable); + } + } else { + PUTC('/'); + } + END(HTML_A); + PUTC('\n'); + } +#endif /* !NO_PARENT_DIR_REFERENCE */ + + FREE(path); + return (need_parent_link); +} + +#if defined HAVE_READDIR +/* Send README file. + * ----------------- + * + * If a README file exists, then it is inserted into the document here. + */ +static void do_readme(HTStructured * target, const char *localname) +{ + FILE *fp; + char *readme_file_name = NULL; + int ch; + + HTSprintf0(&readme_file_name, "%s/%s", localname, HT_DIR_README_FILE); + + fp = fopen(readme_file_name, "r"); + + if (fp) { + START(HTML_PRE); + while ((ch = fgetc(fp)) != EOF) { + PUTC((char) ch); + } + END(HTML_PRE); + HTDisplayPartial(); + fclose(fp); + } + FREE(readme_file_name); +} + +#define DIRED_BLOK(obj) (((DIRED *)(obj))->sort_tags) +#define DIRED_NAME(obj) (((DIRED *)(obj))->file_name) + +#define NM_cmp(a,b) ((a) < (b) ? -1 : ((a) > (b) ? 1 : 0)) + +#if defined(LONG_LIST) && defined(DIRED_SUPPORT) +static const char *file_type(const char *path) +{ + const char *type; + + while (*path == '.') + ++path; + type = StrChr(path, '.'); + if (type == NULL) + type = ""; + return type; +} +#endif /* LONG_LIST && DIRED_SUPPORT */ + +static int dired_cmp(void *a, void *b) +{ + DIRED *p = (DIRED *) a; + DIRED *q = (DIRED *) b; + int code = p->sort_tags - q->sort_tags; + +#if defined(LONG_LIST) && defined(DIRED_SUPPORT) + if (code == 0) { + switch (dir_list_order) { + case ORDER_BY_SIZE: + code = -NM_cmp(p->file_info.st_size, q->file_info.st_size); + break; + case ORDER_BY_DATE: + code = -NM_cmp(p->file_info.st_mtime, q->file_info.st_mtime); + break; + case ORDER_BY_MODE: + code = NM_cmp(p->file_info.st_mode, q->file_info.st_mode); + break; + case ORDER_BY_USER: + code = NM_cmp(p->file_info.st_uid, q->file_info.st_uid); + break; + case ORDER_BY_GROUP: + code = NM_cmp(p->file_info.st_gid, q->file_info.st_gid); + break; + case ORDER_BY_TYPE: + code = AS_cmp(file_type(p->file_name), file_type(q->file_name)); + break; + default: + code = 0; + break; + } + } +#endif /* LONG_LIST && DIRED_SUPPORT */ + if (code == 0) + code = AS_cmp(p->file_name, q->file_name); +#if 0 + CTRACE((tfp, "dired_cmp(%d) ->%d\n\t%c:%s (%s)\n\t%c:%s (%s)\n", + dir_list_order, + code, + p->sort_tags, p->file_name, file_type(p->file_name), + q->sort_tags, q->file_name, file_type(q->file_name))); +#endif + return code; +} + +static int print_local_dir(DIR *dp, char *localname, + HTParentAnchor *anchor, + HTFormat format_out, + HTStream *sink) +{ + HTStructured *target; /* HTML object */ + HTBTree *bt; + HTStructuredClass targetClass; + STRUCT_DIRENT *dirbuf; + char *pathname = NULL; + char *tail = NULL; + const char *p; + char *tmpfilename = NULL; + BOOL need_parent_link = FALSE; + BOOL preformatted = FALSE; + int status; + struct stat *actual_info; + +#ifdef DISP_PARTIAL + int num_of_entries = 0; /* lines counter */ +#endif + +#ifdef S_IFLNK + struct stat link_info; +#endif + + CTRACE((tfp, "print_local_dir() started\n")); + + pathname = HTParse(anchor->address, "", + PARSE_PATH + PARSE_PUNCTUATION); + + if ((p = strrchr(pathname, '/')) == NULL) + p = "/"; + StrAllocCopy(tail, (p + 1)); + FREE(pathname); + + if (UCLYhndl_HTFile_for_unspec >= 0) { + HTAnchor_setUCInfoStage(anchor, + UCLYhndl_HTFile_for_unspec, + UCT_STAGE_PARSER, + UCT_SETBY_DEFAULT); + } + + target = HTML_new(anchor, format_out, sink); + targetClass = *target->isa; /* Copy routine entry points */ + + /* + * The need_parent_link flag will be set if an "Up to " link was + * not created for a readable parent in HTDirTitles() because LONG_LIST is + * defined and NO_PARENT_DIR_REFERENCE is not defined so that need we to + * create the link via an LYListFmtParse() call. - FM + */ + need_parent_link = HTDirTitles(target, anchor, format_out, FALSE); + +#ifdef DIRED_SUPPORT + if (!isLYNXCGI(anchor->address)) { + HTAnchor_setFormat(anchor, WWW_DIRED); + lynx_edit_mode = TRUE; + } +#endif /* DIRED_SUPPORT */ + if (HTDirReadme == HT_DIR_README_TOP) + do_readme(target, localname); + + bt = HTBTree_new(dired_cmp); + + _HTProgress(READING_DIRECTORY); + status = HT_LOADED; /* assume we don't get interrupted */ + while ((dirbuf = readdir(dp)) != NULL) { + /* + * While there are directory entries to be read... + */ + DIRED *data = NULL; + +#ifdef STRUCT_DIRENT__D_INO + if (dirbuf->d_ino == 0) + /* + * If the entry is not being used, skip it. + */ + continue; +#endif + /* + * Skip self, parent if handled in HTDirTitles() or if + * NO_PARENT_DIR_REFERENCE is not defined, and any dot files if + * no_dotfiles is set or show_dotfiles is not set. - FM + */ + if (!strcmp(dirbuf->d_name, ".") /* self */ || + (!strcmp(dirbuf->d_name, "..") /* parent */ && + need_parent_link == FALSE) || + ((strcmp(dirbuf->d_name, "..")) && + (dirbuf->d_name[0] == '.' && + (no_dotfiles || !show_dotfiles)))) + continue; + + StrAllocCopy(tmpfilename, localname); + /* + * If filename is not root directory, add trailing separator. + */ + LYAddPathSep(&tmpfilename); + + StrAllocCat(tmpfilename, dirbuf->d_name); + data = (DIRED *) malloc(sizeof(DIRED) + strlen(dirbuf->d_name) + 4); + if (data == NULL) { + status = HT_PARTIAL_CONTENT; + break; + } + LYTrimPathSep(tmpfilename); + + actual_info = &(data->file_info); +#ifdef S_IFLNK + if (lstat(tmpfilename, actual_info) < 0) { + actual_info->st_mode = 0; + } else { + if (S_ISLNK(actual_info->st_mode)) { + actual_info = &link_info; + if (stat(tmpfilename, actual_info) < 0) + actual_info->st_mode = 0; + } + } +#else + if (stat(tmpfilename, actual_info) < 0) + actual_info->st_mode = 0; +#endif + + strcpy(data->file_name, dirbuf->d_name); +#ifndef DIRED_SUPPORT + if (S_ISDIR(actual_info->st_mode)) { + data->sort_tags = 'D'; + } else { + data->sort_tags = 'F'; + /* D & F to have first directories, then files */ + } +#else + if (S_ISDIR(actual_info->st_mode)) { + if (dir_list_style == MIXED_STYLE) { + data->sort_tags = ' '; + LYAddPathSep0(data->file_name); + } else if (!strcmp(dirbuf->d_name, "..")) { + data->sort_tags = 'A'; + } else { + data->sort_tags = 'D'; + } + } else if (dir_list_style == MIXED_STYLE) { + data->sort_tags = ' '; + } else if (dir_list_style == FILES_FIRST) { + data->sort_tags = 'C'; + /* C & D to have first files, then directories */ + } else { + data->sort_tags = 'F'; + } +#endif /* !DIRED_SUPPORT */ + /* + * Sort dirname in the tree bt. + */ + HTBTree_add(bt, data); + +#ifdef DISP_PARTIAL + /* optimize for expensive operation: */ + if (num_of_entries % (partial_threshold > 0 ? + partial_threshold : display_lines) == 0) { + if (HTCheckForInterrupt()) { + status = HT_PARTIAL_CONTENT; + break; + } + } + num_of_entries++; +#endif /* DISP_PARTIAL */ + + } /* end while directory entries left to read */ + + if (status != HT_PARTIAL_CONTENT) + _HTProgress(OPERATION_OK); + else + CTRACE((tfp, "Reading the directory interrupted by user\n")); + + /* + * Run through tree printing out in order. + */ + { + HTBTElement *next_element = HTBTree_next(bt, NULL); + + /* pick up the first element of the list */ + int num_of_entries_output = 0; /* lines counter */ + + char state; + + /* I for initial (.. file), + D for directory file, + F for file */ + +#ifdef DIRED_SUPPORT + char test; +#endif /* DIRED_SUPPORT */ + state = 'I'; + + while (next_element != NULL) { + DIRED *entry; + +#ifndef DISP_PARTIAL + if (num_of_entries_output % HTMAX(display_lines, 10) == 0) { + if (HTCheckForInterrupt()) { + _HTProgress(TRANSFER_INTERRUPTED); + status = HT_PARTIAL_CONTENT; + break; + } + } +#endif + StrAllocCopy(tmpfilename, localname); + /* + * If filename is not root directory. + */ + LYAddPathSep(&tmpfilename); + + entry = (DIRED *) (HTBTree_object(next_element)); + /* + * Append the current entry's filename to the path. + */ + StrAllocCat(tmpfilename, entry->file_name); + HTSimplify(tmpfilename, LYIsPathSep(*tmpfilename)); + /* + * Output the directory entry. + */ + if (strcmp(DIRED_NAME(HTBTree_object(next_element)), "..")) { +#ifdef DIRED_SUPPORT + test = + (char) (DIRED_BLOK(HTBTree_object(next_element)) + == 'D' ? 'D' : 'F'); + if (state != test) { +#ifndef LONG_LIST + if (dir_list_style == FILES_FIRST) { + if (state == 'F') { + END(HTML_DIR); + PUTC('\n'); + } + } else if (dir_list_style != MIXED_STYLE) + if (state == 'D') { + END(HTML_DIR); + PUTC('\n'); + } +#endif /* !LONG_LIST */ + state = + (char) (DIRED_BLOK(HTBTree_object(next_element)) + == 'D' ? 'D' : 'F'); + if (preformatted) { + END(HTML_PRE); + PUTC('\n'); + preformatted = FALSE; + } + START(HTML_H2); + if (dir_list_style != MIXED_STYLE) { + START(HTML_EM); + PUTS(state == 'D' + ? LABEL_SUBDIRECTORIES + : LABEL_FILES); + END(HTML_EM); + } + END(HTML_H2); + PUTC('\n'); +#ifndef LONG_LIST + START(HTML_DIR); + PUTC('\n'); +#endif /* !LONG_LIST */ + } +#else + if (state != DIRED_BLOK(HTBTree_object(next_element))) { +#ifndef LONG_LIST + if (state == 'D') { + END(HTML_DIR); + PUTC('\n'); + } +#endif /* !LONG_LIST */ + state = + (char) (DIRED_BLOK(HTBTree_object(next_element)) + == 'D' ? 'D' : 'F'); + if (preformatted) { + END(HTML_PRE); + PUTC('\n'); + preformatted = FALSE; + } + START(HTML_H2); + START(HTML_EM); + PUTS(state == 'D' + ? LABEL_SUBDIRECTORIES + : LABEL_FILES); + END(HTML_EM); + END(HTML_H2); + PUTC('\n'); +#ifndef LONG_LIST + START(HTML_DIR); + PUTC('\n'); +#endif /* !LONG_LIST */ + } +#endif /* DIRED_SUPPORT */ +#ifndef LONG_LIST + START(HTML_LI); +#endif /* !LONG_LIST */ + } + if (!preformatted) { + START(HTML_PRE); + PUTC('\n'); + preformatted = TRUE; + } +#ifdef LONG_LIST + LYListFmtParse(list_format, entry, tmpfilename, target, tail); +#else + HTDirEntry(target, tail, entry->file_name); + PUTS(entry->file_name); + END(HTML_A); + MAYBE_END(HTML_LI); + PUTC('\n'); +#endif /* LONG_LIST */ + + next_element = HTBTree_next(bt, next_element); + /* pick up the next element of the list; + if none, return NULL */ + + /* optimize for expensive operation: */ +#ifdef DISP_PARTIAL + if (num_of_entries_output % + ((partial_threshold > 0) + ? partial_threshold + : display_lines) == 0) { + /* num_of_entries, num_of_entries_output... */ + HTDisplayPartial(); + + if (HTCheckForInterrupt()) { + _HTProgress(TRANSFER_INTERRUPTED); + status = HT_PARTIAL_CONTENT; + break; + } + } + num_of_entries_output++; +#endif /* DISP_PARTIAL */ + + } /* end while next_element */ + + if (status == HT_LOADED) { + if (state == 'I') { + START(HTML_P); + PUTS("Empty Directory"); + } +#ifndef LONG_LIST + else + END(HTML_DIR); +#endif /* !LONG_LIST */ + } + } /* end printing out the tree in order */ + if (preformatted) { + END(HTML_PRE); + PUTC('\n'); + } + END(HTML_BODY); + PUTC('\n'); + + FREE(tmpfilename); + FREE(tail); + HTBTreeAndObject_free(bt); + + if (status == HT_LOADED) { + if (HTDirReadme == HT_DIR_README_BOTTOM) + do_readme(target, localname); + FREE_TARGET; + } else { + ABORT_TARGET; + } + HTFinishDisplayPartial(); + return status; /* document loaded, maybe partial */ +} +#endif /* HAVE_READDIR */ + +#ifndef VMS +int HTStat(const char *filename, + struct stat *data) +{ + int result = -1; + size_t len = strlen(filename); + + if (len != 0 && LYIsPathSep(filename[len - 1])) { + char *temp_name = NULL; + + HTSprintf0(&temp_name, "%s.", filename); + result = HTStat(temp_name, data); + FREE(temp_name); + } else { + result = stat(filename, data); +#ifdef _WINDOWS + /* + * Someone claims that stat() doesn't give the proper result for a + * directory on Windows. + */ + if (result == -1 + && access(filename, 0) == 0) { + data->st_mode = S_IFDIR; + result = 0; + } +#endif + } + return result; +} +#endif + +#if defined(USE_ZLIB) || defined(USE_BZLIB) +static BOOL sniffStream(FILE *fp, char *buffer, size_t needed) +{ + long offset = ftell(fp); + BOOL result = FALSE; + + if (offset >= 0) { + if (fread(buffer, sizeof(char), needed, fp) == needed) { + result = TRUE; + } + if (fseek(fp, offset, SEEK_SET) < 0) { + CTRACE((tfp, "error seeking in stream\n")); + result = FALSE; + } + } + return result; +} +#endif + +#ifdef USE_ZLIB +static BOOL isGzipStream(FILE *fp) +{ + char buffer[3]; + BOOL result; + + if (sniffStream(fp, buffer, sizeof(buffer)) + && !MemCmp(buffer, "\037\213", sizeof(buffer) - 1)) { + result = TRUE; + } else { + CTRACE((tfp, "not a gzip-stream\n")); + result = FALSE; + } + return result; +} + +/* + * Strictly speaking, DEFLATE has no header bytes. But decode what we can, + * (to eliminate the one "reserved" pattern) and provide a trace. See RFC-1951 + * discussion of BFINAL and BTYPE. + */ +static BOOL isDeflateStream(FILE *fp) +{ + char buffer[3]; + BOOL result = FALSE; + + if (sniffStream(fp, buffer, sizeof(buffer))) { + int bit1 = ((buffer[0] >> 0) & 1); + int bit2 = ((buffer[0] >> 1) & 1); + int bit3 = ((buffer[0] >> 2) & 1); + int btype = ((bit3 << 1) + bit2); + + if (!MemCmp(buffer, "\170\234", sizeof(buffer) - 1)) { + result = TRUE; + CTRACE((tfp, "isDeflate: assume zlib-wrapped deflate\n")); + } else if (btype == 3) { + CTRACE((tfp, "isDeflate: not a deflate-stream\n")); + } else { + CTRACE((tfp, "isDeflate: %send block, %s compression\n", + (bit1 ? "" : "non-"), + (btype == 0 + ? "no" + : (btype == 1 + ? "static Huffman" + : "dynamic Huffman")))); + result = TRUE; + } + } + return result; +} +#endif + +#ifdef USE_BZLIB +static BOOL isBzip2Stream(FILE *fp) +{ + char buffer[6]; + BOOL result; + + if (sniffStream(fp, buffer, sizeof(buffer)) + && !MemCmp(buffer, "BZh", 3) + && isdigit(UCH(buffer[3])) + && isdigit(UCH(buffer[4]))) { + result = TRUE; + } else { + CTRACE((tfp, "not a bzip2-stream\n")); + result = FALSE; + } + return result; +} +#endif + +#ifdef VMS +#define FOPEN_MODE(bin) "r", "shr=put", "shr=upd" +#define DOT_STRING "._-" /* FIXME: should we check if suffix is after ']' or ':' ? */ +#else +#define FOPEN_MODE(bin) (bin ? BIN_R : "r") +#define DOT_STRING "." +#endif + +#ifdef USE_BROTLI +static FILE *brotli_open(const char *localname, const char *mode) +{ + CTRACE((tfp, "brotli_open file=%s, mode=%s\n", localname, mode)); + return fopen(localname, mode); +} +#endif + +static int decompressAndParse(HTParentAnchor *anchor, + HTFormat format_out, + HTStream *sink, + char *nodename GCC_UNUSED, + char *filename, + HTAtom *myEncoding, + HTFormat format, + int *statusp) +{ + HTAtom *encoding = 0; + +#ifdef USE_ZLIB + FILE *zzfp = 0; + gzFile gzfp = 0; +#endif /* USE_ZLIB */ +#ifdef USE_BZLIB + BZFILE *bzfp = 0; +#endif /* USE_BZLIB */ +#ifdef USE_BROTLI + FILE *brfp = 0; +#endif /* USE_BROTLI */ +#if defined(USE_ZLIB) || defined(USE_BZLIB) + CompressFileType internal_decompress = cftNone; + BOOL failed_decompress = NO; +#endif + int rootlen = 0; + char *localname = filename; + int bin; + FILE *fp; + int result = FALSE; + +#ifdef VMS + /* + * Assume that the file is in Unix-style syntax if it contains a '/' after + * the leading one. @@ + */ + localname = (StrChr(localname + 1, '/') + ? HTVMS_name(nodename, localname) + : localname + 1); +#endif /* VMS */ + + bin = HTCompressFileType(filename, ".", &rootlen) != cftNone; + fp = fopen(localname, FOPEN_MODE(bin)); + +#ifdef VMS + /* + * If the file wasn't VMS syntax, then perhaps it is Ultrix. + */ + if (!fp) { + char *ultrixname = 0; + + CTRACE((tfp, "HTLoadFile: Can't open as %s\n", localname)); + HTSprintf0(&ultrixname, "%s::\"%s\"", nodename, filename); + fp = fopen(ultrixname, FOPEN_MODE(bin)); + if (!fp) { + CTRACE((tfp, "HTLoadFile: Can't open as %s\n", ultrixname)); + } + FREE(ultrixname); + } +#endif /* VMS */ + CTRACE((tfp, "HTLoadFile: Opening `%s' gives %p\n", localname, (void *) fp)); + if (fp) { /* Good! */ + if (HTEditable(localname)) { + HTAtom *put = HTAtom_for("PUT"); + HTList *methods = HTAnchor_methods(anchor); + + if (HTList_indexOf(methods, put) == (-1)) { + HTList_addObject(methods, put); + } + } + /* + * Fake a Content-Encoding for compressed files. - FM + */ + if (!IsUnityEnc(myEncoding)) { + /* + * We already know from the call to HTFileFormat that + * this is a compressed file, no need to look at the filename + * again. - kw + */ +#if defined(USE_ZLIB) || defined(USE_BZLIB) + CompressFileType method = HTEncodingToCompressType(HTAtom_name(myEncoding)); +#endif + +#define isDOWNLOAD(m) (strcmp(format_out->name, STR_DOWNLOAD) && (method == m)) +#ifdef USE_ZLIB + if (isDOWNLOAD(cftGzip)) { + if (isGzipStream(fp)) { + fclose(fp); + fp = 0; + gzfp = gzopen(localname, BIN_R); + + CTRACE((tfp, "HTLoadFile: gzopen of `%s' gives %p\n", + localname, (void *) gzfp)); + } + internal_decompress = cftGzip; + } else if (isDOWNLOAD(cftDeflate)) { + if (isDeflateStream(fp)) { + zzfp = fp; + fp = 0; + + CTRACE((tfp, "HTLoadFile: zzopen of `%s' gives %p\n", + localname, (void *) zzfp)); + } + internal_decompress = cftDeflate; + } else +#endif /* USE_ZLIB */ +#ifdef USE_BZLIB + if (isDOWNLOAD(cftBzip2)) { + if (isBzip2Stream(fp)) { + fclose(fp); + fp = 0; + bzfp = BZ2_bzopen(localname, BIN_R); + + CTRACE((tfp, "HTLoadFile: bzopen of `%s' gives %p\n", + localname, bzfp)); + } + internal_decompress = cftBzip2; + } else +#endif /* USE_BZLIB */ +#ifdef USE_BROTLI + if (isDOWNLOAD(cftBrotli)) { + fclose(fp); + fp = 0; + brfp = brotli_open(localname, BIN_R); + + CTRACE((tfp, "HTLoadFile: brotli_open of `%s' gives %p\n", + localname, (void *) brfp)); + internal_decompress = cftBrotli; + } else +#endif /* USE_BROTLI */ + { + StrAllocCopy(anchor->content_type, format->name); + StrAllocCopy(anchor->content_encoding, HTAtom_name(myEncoding)); + format = HTAtom_for("www/compressed"); + } + } else { + CompressFileType cft = HTCompressFileType(localname, DOT_STRING, &rootlen); + + if (cft != cftNone) { + char *cp = NULL; + + StrAllocCopy(cp, localname); + cp[rootlen] = '\0'; + format = HTFileFormat(cp, &encoding, NULL); + FREE(cp); + format = HTCharsetFormat(format, anchor, + UCLYhndl_HTFile_for_unspec); + StrAllocCopy(anchor->content_type, format->name); + } + + switch (cft) { + case cftCompress: + StrAllocCopy(anchor->content_encoding, "x-compress"); + format = HTAtom_for("www/compressed"); + break; + case cftDeflate: + StrAllocCopy(anchor->content_encoding, "x-deflate"); +#ifdef USE_ZLIB + if (strcmp(format_out->name, STR_DOWNLOAD) != 0) { + if (isDeflateStream(fp)) { + zzfp = fp; + fp = 0; + + CTRACE((tfp, "HTLoadFile: zzopen of `%s' gives %p\n", + localname, (void *) zzfp)); + } + internal_decompress = cftDeflate; + } +#else /* USE_ZLIB */ + format = HTAtom_for("www/compressed"); +#endif /* USE_ZLIB */ + break; + case cftGzip: + StrAllocCopy(anchor->content_encoding, "x-gzip"); +#ifdef USE_ZLIB + if (strcmp(format_out->name, STR_DOWNLOAD) != 0) { + if (isGzipStream(fp)) { + fclose(fp); + fp = 0; + gzfp = gzopen(localname, BIN_R); + + CTRACE((tfp, "HTLoadFile: gzopen of `%s' gives %p\n", + localname, (void *) gzfp)); + } + internal_decompress = cftGzip; + } +#else /* USE_ZLIB */ + format = HTAtom_for("www/compressed"); +#endif /* USE_ZLIB */ + break; + case cftBzip2: + StrAllocCopy(anchor->content_encoding, "x-bzip2"); +#ifdef USE_BZLIB + if (strcmp(format_out->name, STR_DOWNLOAD) != 0) { + if (isBzip2Stream(fp)) { + fclose(fp); + fp = 0; + bzfp = BZ2_bzopen(localname, BIN_R); + + CTRACE((tfp, "HTLoadFile: bzopen of `%s' gives %p\n", + localname, bzfp)); + } + internal_decompress = cftBzip2; + } +#else /* USE_BZLIB */ + format = HTAtom_for("www/compressed"); +#endif /* USE_BZLIB */ + break; + case cftBrotli: + StrAllocCopy(anchor->content_encoding, "x-brotli"); +#ifdef USE_BROTLI + if (strcmp(format_out->name, STR_DOWNLOAD) != 0) { + fclose(fp); + fp = 0; + brfp = brotli_open(localname, BIN_R); + + CTRACE((tfp, "HTLoadFile: brotli_open of `%s' gives %p\n", + localname, (void *) brfp)); + internal_decompress = cftBrotli; + } +#else /* USE_BROTLI */ + format = HTAtom_for("www/compressed"); +#endif /* USE_BROTLI */ + break; + case cftNone: + break; + } + } +#if defined(USE_ZLIB) || defined(USE_BZLIB) + if (internal_decompress != cftNone) { + switch (internal_decompress) { +#ifdef USE_ZLIB + case cftDeflate: + failed_decompress = (BOOLEAN) (zzfp == NULL); + break; + case cftCompress: + case cftGzip: + failed_decompress = (BOOLEAN) (gzfp == NULL); + break; +#endif +#ifdef USE_BZLIB + case cftBzip2: + failed_decompress = (BOOLEAN) (bzfp == NULL); + break; +#endif +#ifdef USE_BROTLI + case cftBrotli: + failed_decompress = (BOOLEAN) (brfp == NULL); + break; +#endif + default: + failed_decompress = YES; + break; + } + if (failed_decompress) { + *statusp = HTLoadError(NULL, + -(HT_ERROR), + FAILED_OPEN_COMPRESSED_FILE); + } else { + char *sugfname = NULL; + + if (anchor->SugFname) { + StrAllocCopy(sugfname, anchor->SugFname); + } else { + char *anchor_path = HTParse(anchor->address, "", + PARSE_PATH + PARSE_PUNCTUATION); + char *lastslash; + + HTUnEscape(anchor_path); + lastslash = strrchr(anchor_path, '/'); + if (lastslash) + StrAllocCopy(sugfname, lastslash + 1); + FREE(anchor_path); + } + FREE(anchor->content_encoding); + if (sugfname && *sugfname) + HTCheckFnameForCompression(&sugfname, anchor, + TRUE); + if (sugfname && *sugfname) + StrAllocCopy(anchor->SugFname, sugfname); + FREE(sugfname); +#ifdef USE_BROTLI + if (brfp) + *statusp = HTParseBrFile(format, format_out, + anchor, + brfp, sink); +#endif +#ifdef USE_BZLIB + if (bzfp) + *statusp = HTParseBzFile(format, format_out, + anchor, + bzfp, sink); +#endif +#ifdef USE_ZLIB + if (gzfp) + *statusp = HTParseGzFile(format, format_out, + anchor, + gzfp, sink); + else if (zzfp) + *statusp = HTParseZzFile(format, format_out, + anchor, + zzfp, sink); +#endif + } + } else +#endif /* USE_ZLIB || USE_BZLIB */ + { + *statusp = HTParseFile(format, format_out, anchor, fp, sink); + } + if (fp != 0) { + fclose(fp); + fp = 0; + } + result = TRUE; + } /* If successful open */ + return result; +} + +/* Load a document. + * ---------------- + * + * On entry: + * addr must point to the fully qualified hypertext reference. + * This is the physical address of the file + * + * On exit: + * returns <0 Error has occurred. + * HTLOADED OK + * + */ +int HTLoadFile(const char *addr, + HTParentAnchor *anchor, + HTFormat format_out, + HTStream *sink) +{ + char *filename = NULL; + char *acc_method = NULL; + HTFormat format; + char *nodename = NULL; + char *newname = NULL; /* Simplified name of file */ + HTAtom *myEncoding = NULL; /* enc of this file, may be gzip etc. */ + int status = -1; + +#ifndef DISABLE_FTP + char *ftp_newhost; +#endif + +#ifdef VMS + struct stat stat_info; +#endif /* VMS */ + + /* + * Reduce the filename to a basic form (hopefully unique!). + */ + StrAllocCopy(newname, addr); + filename = HTParse(newname, "", PARSE_PATH | PARSE_PUNCTUATION); + nodename = HTParse(newname, "", PARSE_HOST); + + /* + * If access is ftp, or file is on another host, invoke ftp now. + */ + acc_method = HTParse(newname, "", PARSE_ACCESS); + if (strcmp("ftp", acc_method) == 0 || + (!LYSameHostname("localhost", nodename) && + !LYSameHostname(nodename, HTHostName()))) { + status = -1; + FREE(newname); + FREE(filename); + FREE(nodename); + FREE(acc_method); +#ifndef DISABLE_FTP + ftp_newhost = HTParse(addr, "", PARSE_HOST); + if (strcmp(ftp_lasthost, ftp_newhost)) + ftp_local_passive = ftp_passive; + + status = HTFTPLoad(addr, anchor, format_out, sink); + + if (ftp_passive == ftp_local_passive) { + if ((status >= 400) || (status < 0)) { + ftp_local_passive = (BOOLEAN) !ftp_passive; + status = HTFTPLoad(addr, anchor, format_out, sink); + } + } + + free(ftp_lasthost); + ftp_lasthost = ftp_newhost; +#endif /* DISABLE_FTP */ + return status; + } else { + FREE(newname); + FREE(acc_method); + } +#if defined(VMS) || defined(USE_DOS_DRIVES) + HTUnEscape(filename); +#endif /* VMS */ + + /* + * Determine the format and encoding mapped to any suffix. + */ + if (anchor->content_type && anchor->content_encoding) { + /* + * If content_type and content_encoding are BOTH already set in the + * anchor object, we believe it and don't try to derive format and + * encoding from the filename. - kw + */ + format = HTAtom_for(anchor->content_type); + myEncoding = HTAtom_for(anchor->content_encoding); + } else { + int default_UCLYhndl = UCLYhndl_HTFile_for_unspec; + + if (force_old_UCLYhndl_on_reload) { + force_old_UCLYhndl_on_reload = FALSE; + default_UCLYhndl = forced_UCLYhdnl; + } + + format = HTFileFormat(filename, &myEncoding, NULL); + + /* + * Check the format for an extended MIME charset value, and act on it + * if present. Otherwise, assume what is indicated by the last + * parameter (fallback will effectively be UCLYhndl_for_unspec, by + * default ISO-8859-1). - kw + */ + format = HTCharsetFormat(format, anchor, default_UCLYhndl); + } + +#ifdef VMS + /* + * Check to see if the 'filename' is in fact a directory. If it is create + * a new hypertext object containing a list of files and subdirectories + * contained in the directory. All of these are links to the directories + * or files listed. + */ + if (HTStat(filename, &stat_info) == -1) { + CTRACE((tfp, "HTLoadFile: Can't stat %s\n", filename)); + } else { + if (S_ISDIR(stat_info.st_mode)) { + if (HTDirAccess == HT_DIR_FORBID) { + FREE(filename); + FREE(nodename); + return HTLoadError(sink, 403, DISALLOWED_DIR_SCAN); + } + + if (HTDirAccess == HT_DIR_SELECTIVE) { + char *enable_file_name = NULL; + + HTSprintf0(&enable_file_name, "%s/%s", filename, HT_DIR_ENABLE_FILE); + if (HTStat(enable_file_name, &stat_info) == -1) { + FREE(filename); + FREE(nodename); + FREE(enable_file_name); + return HTLoadError(sink, 403, DISALLOWED_SELECTIVE_ACCESS); + } + } + + FREE(filename); + FREE(nodename); + return HTVMSBrowseDir(addr, anchor, format_out, sink); + } + } + + if (decompressAndParse(anchor, + format_out, + sink, + nodename, + filename, + myEncoding, + format, + &status)) { + FREE(nodename); + FREE(filename); + return status; + } + FREE(filename); + +#else /* not VMS: */ + + FREE(filename); + + /* + * For unix, we try to translate the name into the name of a transparently + * mounted file. + * + * Not allowed in secure (HTClientHost) situations. TBL 921019 + */ +#ifndef NO_UNIX_IO + /* Need protection here for telnet server but not httpd server. */ + + if (!HTSecure) { /* try local file system */ + char *localname = HTLocalName(addr); + struct stat dir_info; + +#ifdef HAVE_READDIR + /* + * Multiformat handling. + * + * If needed, scan directory to find a good file. Bug: We don't stat + * the file to find the length. + */ + if ((strlen(localname) > strlen(MULTI_SUFFIX)) && + (0 == strcmp(localname + strlen(localname) - strlen(MULTI_SUFFIX), + MULTI_SUFFIX))) { + DIR *dp = 0; + BOOL forget_multi = NO; + + STRUCT_DIRENT *dirbuf; + float best = (float) NO_VALUE_FOUND; /* So far best is bad */ + HTFormat best_rep = NULL; /* Set when rep found */ + HTAtom *best_enc = NULL; + char *best_name = NULL; /* Best dir entry so far */ + + char *base = strrchr(localname, '/'); + size_t baselen = 0; + + if (!base || base == localname) { + forget_multi = YES; + } else { + *base++ = '\0'; /* Just got directory name */ + baselen = strlen(base) - strlen(MULTI_SUFFIX); + base[baselen] = '\0'; /* Chop off suffix */ + + dp = opendir(localname); + } + if (forget_multi || !dp) { + FREE(localname); + FREE(nodename); + return HTLoadError(sink, 500, FAILED_DIR_SCAN); + } + + while ((dirbuf = readdir(dp)) != NULL) { + /* + * While there are directory entries to be read... + */ +#ifdef STRUCT_DIRENT__D_INO + if (dirbuf->d_ino == 0) + continue; /* if the entry is not being used, skip it */ +#endif + if (strlen(dirbuf->d_name) > baselen && /* Match? */ + !StrNCmp(dirbuf->d_name, base, baselen)) { + HTAtom *enc; + HTFormat rep = HTFileFormat(dirbuf->d_name, &enc, NULL); + float filevalue = HTFileValue(dirbuf->d_name); + float value = HTStackValue(rep, format_out, + filevalue, + 0L /* @@@@@@ */ ); + + if (value <= 0.0) { + int rootlen = 0; + const char *atomname = NULL; + CompressFileType cft = + HTCompressFileType(dirbuf->d_name, ".", &rootlen); + char *cp = NULL; + + enc = NULL; + if (cft != cftNone) { + StrAllocCopy(cp, dirbuf->d_name); + cp[rootlen] = '\0'; + format = HTFileFormat(cp, NULL, NULL); + FREE(cp); + value = HTStackValue(format, format_out, + filevalue, 0L); + } + switch (cft) { + case cftCompress: + atomname = "application/x-compressed"; + break; + case cftGzip: + atomname = "application/x-gzip"; + break; + case cftDeflate: + atomname = "application/x-deflate"; + break; + case cftBzip2: + atomname = "application/x-bzip2"; + break; + case cftBrotli: + atomname = "application/x-brotli"; + break; + case cftNone: + break; + } + + if (atomname != NULL) { + value = HTStackValue(format, format_out, + filevalue, 0L); + if (value <= 0.0) { + format = HTAtom_for(atomname); + value = HTStackValue(format, format_out, + filevalue, 0L); + } + if (value <= 0.0) { + format = HTAtom_for("www/compressed"); + value = HTStackValue(format, format_out, + filevalue, 0L); + } + } + } + if (value < NO_VALUE_FOUND) { + CTRACE((tfp, + "HTLoadFile: value of presenting %s is %f\n", + HTAtom_name(rep), value)); + if (value > best) { + best_rep = rep; + best_enc = enc; + best = value; + StrAllocCopy(best_name, dirbuf->d_name); + } + } /* if best so far */ + } + /* if match */ + } /* end while directory entries left to read */ + closedir(dp); + + if (best_rep) { + format = best_rep; + myEncoding = best_enc; + base[-1] = '/'; /* Restore directory name */ + base[0] = '\0'; + StrAllocCat(localname, best_name); + FREE(best_name); + } else { /* If not found suitable file */ + FREE(localname); + FREE(nodename); + return HTLoadError(sink, 403, FAILED_NO_REPRESENTATION); + } + /*NOTREACHED */ + } + /* if multi suffix */ + /* + * Check to see if the 'localname' is in fact a directory. If it is + * create a new hypertext object containing a list of files and + * subdirectories contained in the directory. All of these are links + * to the directories or files listed. NB This assumes the existence + * of a type 'STRUCT_DIRENT', which will hold the directory entry, and + * a type 'DIR' which is used to point to the current directory being + * read. + */ +#if defined(USE_DOS_DRIVES) + if (strlen(localname) == 2 && LYIsDosDrive(localname)) + LYAddPathSep(&localname); +#endif + if (HTStat(localname, &dir_info) == -1) /* get file information */ + { + /* if can't read file information */ + CTRACE((tfp, "HTLoadFile: can't stat %s\n", localname)); + + } else { /* Stat was OK */ + + if (S_ISDIR(dir_info.st_mode)) { + /* + * If localname is a directory. + */ + DIR *dp; + struct stat file_info; + + CTRACE((tfp, "%s is a directory\n", localname)); + + /* + * Check directory access. Selective access means only those + * directories containing a marker file can be browsed. + */ + if (HTDirAccess == HT_DIR_FORBID) { + FREE(localname); + FREE(nodename); + return HTLoadError(sink, 403, DISALLOWED_DIR_SCAN); + } + + if (HTDirAccess == HT_DIR_SELECTIVE) { + char *enable_file_name = NULL; + + HTSprintf0(&enable_file_name, "%s/%s", localname, HT_DIR_ENABLE_FILE); + if (stat(enable_file_name, &file_info) != 0) { + FREE(localname); + FREE(nodename); + FREE(enable_file_name); + return HTLoadError(sink, 403, DISALLOWED_SELECTIVE_ACCESS); + } + } + + CTRACE((tfp, "Opening directory %s\n", localname)); + dp = opendir(localname); + if (!dp) { + FREE(localname); + FREE(nodename); + return HTLoadError(sink, 403, FAILED_DIR_UNREADABLE); + } + + /* + * Directory access is allowed and possible. + */ + + status = print_local_dir(dp, localname, + anchor, format_out, sink); + closedir(dp); + FREE(localname); + FREE(nodename); + return status; /* document loaded, maybe partial */ + + } + /* end if localname is a directory */ + if (S_ISREG(dir_info.st_mode)) { +#ifdef LONG_MAX + if (dir_info.st_size <= LONG_MAX) +#endif + anchor->content_length = (long) dir_info.st_size; + } + + } /* end if file stat worked */ + +/* End of directory reading section +*/ +#endif /* HAVE_READDIR */ + if (decompressAndParse(anchor, + format_out, + sink, + nodename, + localname, + myEncoding, + format, + &status)) { + FREE(nodename); + FREE(localname); + return status; + } + FREE(localname); + } /* local unix file system */ +#endif /* !NO_UNIX_IO */ +#endif /* VMS */ + +#ifndef DECNET + /* + * Now, as transparently mounted access has failed, we try FTP. + */ + { + /* + * Deal with case-sensitivity differences on VMS versus Unix. + */ +#ifdef VMS + if (strcasecomp(nodename, HTHostName()) != 0) +#else + if (strcmp(nodename, HTHostName()) != 0) +#endif /* VMS */ + { + status = -1; + FREE(nodename); + if (StrNCmp(addr, "file://localhost", 16)) { + /* never go to ftp site when URL + * is file://localhost + */ +#ifndef DISABLE_FTP + status = HTFTPLoad(addr, anchor, format_out, sink); +#endif /* DISABLE_FTP */ + } + return status; + } + FREE(nodename); + } +#endif /* !DECNET */ + + /* + * All attempts have failed. + */ + { + CTRACE((tfp, "Can't open `%s', errno=%d\n", addr, SOCKET_ERRNO)); + + return HTLoadError(sink, 403, FAILED_FILE_UNREADABLE); + } +} + +static const char *program_paths[pp_Last]; + +/* + * Given a program number, return its path + */ +const char *HTGetProgramPath(ProgramPaths code) +{ + const char *result = NULL; + + if (code > ppUnknown && code < pp_Last) + result = program_paths[code]; + return result; +} + +/* + * Store a program's path. The caller must allocate the string used for 'path', + * since HTInitProgramPaths() may free it. + */ +void HTSetProgramPath(ProgramPaths code, const char *path) +{ + if (code > ppUnknown && code < pp_Last) { + program_paths[code] = isEmpty(path) ? 0 : path; + } +} + +/* + * Reset the list of known program paths to the ones that are compiled-in + */ +void HTInitProgramPaths(BOOL init) +{ + ProgramPaths code; + int n; + const char *path; + const char *test; + + for (n = (int) ppUnknown + 1; n < (int) pp_Last; ++n) { + switch (code = (ProgramPaths) n) { +#ifdef BROTLI_PATH + case ppBROTLI: + path = BROTLI_PATH; + break; +#endif +#ifdef BZIP2_PATH + case ppBZIP2: + path = BZIP2_PATH; + break; +#endif +#ifdef CHMOD_PATH + case ppCHMOD: + path = CHMOD_PATH; + break; +#endif +#ifdef COMPRESS_PATH + case ppCOMPRESS: + path = COMPRESS_PATH; + break; +#endif +#ifdef COPY_PATH + case ppCOPY: + path = COPY_PATH; + break; +#endif +#ifdef CSWING_PATH + case ppCSWING: + path = CSWING_PATH; + break; +#endif +#ifdef GZIP_PATH + case ppGZIP: + path = GZIP_PATH; + break; +#endif +#ifdef INFLATE_PATH + case ppINFLATE: + path = INFLATE_PATH; + break; +#endif +#ifdef INSTALL_PATH + case ppINSTALL: + path = INSTALL_PATH; + break; +#endif +#ifdef MKDIR_PATH + case ppMKDIR: + path = MKDIR_PATH; + break; +#endif +#ifdef MV_PATH + case ppMV: + path = MV_PATH; + break; +#endif +#ifdef RLOGIN_PATH + case ppRLOGIN: + path = RLOGIN_PATH; + break; +#endif +#ifdef RM_PATH + case ppRM: + path = RM_PATH; + break; +#endif +#ifdef RMDIR_PATH + case ppRMDIR: + path = RMDIR_PATH; + break; +#endif +#ifdef SETFONT_PATH + case ppSETFONT: + path = SETFONT_PATH; + break; +#endif +#ifdef TAR_PATH + case ppTAR: + path = TAR_PATH; + break; +#endif +#ifdef TELNET_PATH + case ppTELNET: + path = TELNET_PATH; + break; +#endif +#ifdef TN3270_PATH + case ppTN3270: + path = TN3270_PATH; + break; +#endif +#ifdef TOUCH_PATH + case ppTOUCH: + path = TOUCH_PATH; + break; +#endif +#ifdef UNCOMPRESS_PATH + case ppUNCOMPRESS: + path = UNCOMPRESS_PATH; + break; +#endif +#ifdef UNZIP_PATH + case ppUNZIP: + path = UNZIP_PATH; + break; +#endif +#ifdef UUDECODE_PATH + case ppUUDECODE: + path = UUDECODE_PATH; + break; +#endif +#ifdef ZCAT_PATH + case ppZCAT: + path = ZCAT_PATH; + break; +#endif +#ifdef ZIP_PATH + case ppZIP: + path = ZIP_PATH; + break; +#endif + default: + path = NULL; + break; + } + test = HTGetProgramPath(code); + if (test != NULL && test != path) { + free(DeConst(test)); + } + if (init) { + HTSetProgramPath(code, path); + } + } +} + +/* + * Protocol descriptors + */ +#ifdef GLOBALDEF_IS_MACRO +#define _HTFILE_C_1_INIT { "ftp", HTLoadFile, 0 } +GLOBALDEF(HTProtocol, HTFTP, _HTFILE_C_1_INIT); +#define _HTFILE_C_2_INIT { "file", HTLoadFile, HTFileSaveStream } +GLOBALDEF(HTProtocol, HTFile, _HTFILE_C_2_INIT); +#else +GLOBALDEF HTProtocol HTFTP = +{"ftp", HTLoadFile, 0}; +GLOBALDEF HTProtocol HTFile = +{"file", HTLoadFile, HTFileSaveStream}; +#endif /* GLOBALDEF_IS_MACRO */ diff --git a/WWW/Library/Implementation/HTFile.h b/WWW/Library/Implementation/HTFile.h new file mode 100644 index 0000000..1209090 --- /dev/null +++ b/WWW/Library/Implementation/HTFile.h @@ -0,0 +1,368 @@ +/* + * $LynxId: HTFile.h,v 1.36 2023/10/24 08:07:39 tom Exp $ + * File access in libwww + * FILE ACCESS + * + * These are routines for local file access used by WWW browsers and servers. + * Implemented by HTFile.c. + * + * If the file is not a local file, then we pass it on to HTFTP in case it + * can be reached by FTP. + */ +#ifndef HTFILE_H +#define HTFILE_H + +#include +#include + +#ifndef HTML_H +#include /* SCW */ +#endif /* HTML_H */ + +#ifdef __cplusplus +extern "C" { +#endif +/* + * Controlling globals + * + * These flags control how directories and files are represented as + * hypertext, and are typically set by the application from command + * line options, etc. + */ extern int HTDirAccess; + /* Directory access level */ + +#define HT_DIR_FORBID 0 /* Altogether forbidden */ +#define HT_DIR_SELECTIVE 1 /* If HT_DIR_ENABLE_FILE exists */ +#define HT_DIR_OK 2 /* Any accessible directory */ + +#define HT_DIR_ENABLE_FILE ".www_browsable" /* If exists, can browse */ + + extern int HTDirReadme; /* Include readme files in listing? */ + + /* Values: */ +#define HT_DIR_README_NONE 0 /* No */ +#define HT_DIR_README_TOP 1 /* Yes, first */ +#define HT_DIR_README_BOTTOM 2 /* Yes, at the end */ + +#define HT_DIR_README_FILE "README" + +/* + * Convert filenames between local and WWW formats + */ + extern char *HTURLPath_toFile(const char *name, int expand_all, int is_remote); + extern char *HTnameOfFile_WWW(const char *name, int WWW_prefix, int expand_all); + +#define HTLocalName(name) HTnameOfFile_WWW(name,TRUE,TRUE) +#define HTfullURL_toFile(name) HTnameOfFile_WWW(name,FALSE,TRUE) +#define HTpartURL_toFile(name) HTnameOfFile_WWW(name,FALSE,FALSE) + +/* + * Make a WWW name from a full local path name + */ + extern char *WWW_nameOfFile(const char *name); + +/* + * Generate the name of a cache file + */ + extern char *HTCacheFileName(const char *name); + +/* + * Generate fragments of HTML for source-view: + */ + extern void HTStructured_doctype(HTStructured * target, HTFormat format_out); + + extern void HTStructured_meta(HTStructured * target, HTFormat format_out); +/* + * Output directory titles + * + * This is (like the next one) used by HTFTP. It is common code to generate + * the title and heading 1 and the parent directory link for any anchor. + * + * changed to return TRUE if parent directory link was generated, + * FALSE otherwise - KW + */ + extern BOOL HTDirTitles(HTStructured * target, HTParentAnchor *anchor, + HTFormat format_out, + int tildeIsTop); + +/* + * Check existence. + */ + extern int HTStat(const char *filename, + struct stat *data); + +/* Load a document. + * ---------------- + */ + extern int HTLoadFile(const char *addr, + HTParentAnchor *anchor, + HTFormat format_out, + HTStream *sink); + +/* + * Output a directory entry + * + * This is used by HTFTP.c for example -- it is a common routine for + * generating a linked directory entry. + */ + extern void HTDirEntry(HTStructured * target, /* in which to put the linked text */ const char *tail, /* last part of directory name */ + const char *entry); /* name of this entry */ + +/* + * HTSetSuffix: Define the representation for a file suffix + * + * This defines a mapping between local file suffixes and file content + * types and encodings. + * + * ON ENTRY, + * + * suffix includes the "." if that is important (normally, yes!) + * + * representation is MIME-style content-type + * + * encoding is MIME-style content-transfer-encoding + * (8bit, 7bit, etc) or HTTP-style content-encoding + * (gzip, compress etc.) + * + * quality an a priori judgement of the quality of such files + * (0.0..1.0) + * + * HTSetSuffix5 has one more parameter for a short description of the type + * which is otherwise derived from the representation: + * + * desc is a short textual description, or NULL + * + * Examples: HTSetSuffix(".ps", "application/postscript", "8bit", 1.0); + * Examples: HTSetSuffix(".psz", "application/postscript", "gzip", 1.0); + * A MIME type could also indicate a non-trivial encoding on its own + * ("application/x-compressed-tar"), but in that case don't use encoding + * to also indicate it but use "binary" etc. + */ + extern void HTSetSuffix5(const char *suffix, + const char *representation, + const char *encoding, + const char *desc, + double quality); + +#define HTSetSuffix(suff,rep,enc,q) HTSetSuffix5(suff, rep, enc, NULL, q) + +/* + * HTFileFormat: Get Representation and Encoding from file name. + * + * ON EXIT, + * + * return The representation it imagines the file is in. + * + * *pEncoding The encoding (binary, 7bit, etc). See HTSetSuffix. + */ + extern HTFormat HTFileFormat(const char *filename, + HTAtom **pEncoding, + const char **pDesc); + +/* + * HTCharsetFormat: Revise the file format in relation to the Lynx charset. + * + * This checks the format associated with an anchor for + * for an extended MIME Content-Type, and if a charset is + * indicated, sets Lynx up for proper handling in relation + * to the currently selected character set. - FM + */ + extern HTFormat HTCharsetFormat(HTFormat format, + HTParentAnchor *anchor, + int default_LYhndl); + +/* Get various pieces of meta info from file name. + * ----------------------------------------------- + * + * LYGetFileInfo fills in information that can be determined without + * an actual (new) access to the filesystem, based on current suffix + * and character set configuration. If the file has been loaded and + * parsed before (with the same URL generated here!) and the anchor + * is still around, some results may be influenced by that (in + * particular, charset info from a META tag - this is not actually + * tested!). + * The caller should not keep pointers to the returned objects around + * for too long, the valid lifetimes vary. In particular, the returned + * charset string should be copied if necessary. If return of the + * file_anchor is requested, that one can be used to retrieve + * additional bits of info that are stored in the anchor object and + * are not covered here; as usual, don't keep pointers to the + * file_anchor longer than necessary since the object may disappear + * through HTuncache_current_document or at the next document load. + * - kw + */ + extern void LYGetFileInfo(const char *filename, + HTParentAnchor **pfile_anchor, + HTFormat *pformat, + HTAtom **pencoding, + const char **pdesc, + const char **pcharset, + int *pfile_cs); + +/* + * Determine file value from file name. + */ + extern float HTFileValue(const char *filename); + +/* + * Known compression types. + */ + typedef enum { + cftNone + ,cftCompress + ,cftGzip + ,cftBzip2 + ,cftDeflate + ,cftBrotli + } CompressFileType; + +/* + * Determine compression type from file name, by looking at its suffix. + */ + extern CompressFileType HTCompressFileType(const char *filename, + const char *dots, + int *rootlen); + +/* + * Determine compression type from the content-encoding. + */ + extern CompressFileType HTEncodingToCompressType(const char *encoding); +/* + * Determine compression type from the content-encoding. + */ + extern CompressFileType HTContentTypeToCompressType(const char *ct); +/* + * Determine compression type from the content-type and/or content-encoding. + */ + extern CompressFileType HTContentToCompressType(HTParentAnchor *anchor); +/* + * Determine compression encoding from the compression method. + */ + extern const char *HTCompressTypeToEncoding(CompressFileType method); +/* + * Determine expected file-suffix from the compression method. + */ + extern const char *HTCompressTypeToSuffix(CompressFileType method); +/* + * Determine write access to a file. + * + * ON EXIT, + * + * return value YES if file can be accessed and can be written to. + * + * BUGS + * + * Isn't there a quicker way? + */ + +#if defined(HAVE_CONFIG_H) + +#ifndef HAVE_GETGROUPS +#define NO_GROUPS +#endif + +#else + +#ifdef VMS +#define NO_GROUPS +#endif /* VMS */ +#ifdef NO_UNIX_IO +#define NO_GROUPS +#endif /* NO_UNIX_IO */ +#ifdef PCNFS +#define NO_GROUPS +#endif /* PCNFS */ +#ifdef NOUSERS +#define NO_GROUPS +#endif /* PCNFS */ + +#endif /* HAVE_CONFIG_H */ + + extern BOOL HTEditable(const char *filename); + +/* Make a save stream. + * ------------------- + */ + extern HTStream *HTFileSaveStream(HTParentAnchor *anchor); + +/* + * Determine a suitable suffix, given the representation. + * + * ON ENTRY, + * + * rep is the atomized MIME style representation + * enc is an encoding (8bit, binary, gzip, compress,..) + * + * ON EXIT, + * + * returns a pointer to a suitable suffix string if one has + * been found, else NULL. + */ + extern const char *HTFileSuffix(HTAtom *rep, + const char *enc); + +/* + * Enumerate external programs that lynx may assume exists. Unlike those + * given in download scripts, etc., lynx would really like to know their + * absolute paths, for better security. + */ + typedef enum { + ppUnknown = 0 + ,ppBROTLI + ,ppBZIP2 + ,ppCHMOD + ,ppCOMPRESS + ,ppCOPY + ,ppCSWING + ,ppGZIP + ,ppINFLATE + ,ppINSTALL + ,ppMKDIR + ,ppMV + ,ppRLOGIN + ,ppRM + ,ppRMDIR + ,ppSETFONT + ,ppTAR + ,ppTELNET + ,ppTN3270 + ,ppTOUCH + ,ppUNCOMPRESS + ,ppUNZIP + ,ppUUDECODE + ,ppZCAT + ,ppZIP + ,pp_Last + } ProgramPaths; + +/* + * Given a program number, return its path + */ + extern const char *HTGetProgramPath(ProgramPaths code); + +/* + * Store a program's path + */ + extern void HTSetProgramPath(ProgramPaths code, + const char *path); + +/* + * Reset the list of known program paths to the ones that are compiled-in + */ + extern void HTInitProgramPaths(BOOL init); + +/* + * The Protocols + */ +#ifdef GLOBALREF_IS_MACRO + extern GLOBALREF (HTProtocol, HTFTP); + extern GLOBALREF (HTProtocol, HTFile); + +#else + GLOBALREF HTProtocol HTFTP, HTFile; +#endif /* GLOBALREF_IS_MACRO */ + +#ifdef __cplusplus +} +#endif +#endif /* HTFILE_H */ diff --git a/WWW/Library/Implementation/HTFinger.c b/WWW/Library/Implementation/HTFinger.c new file mode 100644 index 0000000..566761a --- /dev/null +++ b/WWW/Library/Implementation/HTFinger.c @@ -0,0 +1,418 @@ +/* + * $LynxId: HTFinger.c,v 1.31 2013/11/28 11:27:50 tom Exp $ + * + * FINGER ACCESS HTFinger.c + * ============= + * Authors: + * ARB Andrew Brooks + * + * History: + * 21 Apr 94 First version (ARB, from HTNews.c by TBL) + * 12 Mar 96 Made the URL and command buffering secure from + * stack modifications, beautified the HTLoadFinger() + * and response() functions, and added support for the + * following URL formats for sending a "", "/w", + * "username[@host]", or "/w username[@host]" command + * to the server: + * finger://host + * finger://host/ + * finger://host/%2fw + * finger://host/%2fw%20username[@host] + * finger://host/w/username[@host] + * finger://host/username[@host] + * finger://host/username[@host]/w + * finger://username@host + * finger://username@host/ + * finger://username@host/w + * 15 Mar 96 Added support for port 79 gtype 0 gopher URLs + * relayed from HTLoadGopher. - FM + */ + +#include + +#ifndef DISABLE_FINGER + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define FINGER_PORT 79 /* See rfc742 */ +#define BIG 1024 /* Bug */ + +#define PUTC(c) (*targetClass.put_character)(target, c) +#define PUTS(s) (*targetClass.put_string)(target, s) +#define START(e) (*targetClass.start_element)(target, e, 0, 0, -1, 0) +#define END(e) (*targetClass.end_element)(target, e, 0) +#define FREE_TARGET (*targetClass._free)(target) +#define NEXT_CHAR HTGetCharacter() + +/* Module-wide variables +*/ +static int finger_fd; /* Socket for FingerHost */ + +struct _HTStructured { + const HTStructuredClass *isa; /* For gopher streams */ + /* ... */ +}; + +static HTStructured *target; /* The output sink */ +static HTStructuredClass targetClass; /* Copy of fn addresses */ + +/* Initialisation for this module + * ------------------------------ + */ +static BOOL initialized = NO; +static BOOL initialize(void) +{ + finger_fd = -1; /* Disconnected */ + return YES; +} + +/* Start anchor element + * -------------------- + */ +static void start_anchor(const char *href) +{ + BOOL present[HTML_A_ATTRIBUTES]; + const char *value[HTML_A_ATTRIBUTES]; + + { + int i; + + for (i = 0; i < HTML_A_ATTRIBUTES; i++) + present[i] = (BOOL) (i == HTML_A_HREF); + } + ((const char **) value)[HTML_A_HREF] = href; + (*targetClass.start_element) (target, HTML_A, present, + (const char **) value, -1, 0); + +} + +/* Send Finger Command line to remote host & Check Response + * -------------------------------------------------------- + * + * On entry, + * command points to the command to be sent, including CRLF, or is null + * pointer if no command to be sent. + * On exit, + * Negative status indicates transmission error, socket closed. + * Positive status is a Finger status. + */ + +static int response(char *command, + char *sitename, + HTParentAnchor *anAnchor, + HTFormat format_out, + HTStream *sink) +{ + int status; + int length = (int) strlen(command); + int ch, i; + char line[BIG], *l, *cmd = NULL; + char *p = line, *href = NULL; + + if (length == 0) + return (-1); + + /* Set up buffering. + */ + HTInitInput(finger_fd); + + /* Send the command. + */ + CTRACE((tfp, "HTFinger command to be sent: %s", command)); + status = (int) NETWRITE(finger_fd, (char *) command, (unsigned) length); + if (status < 0) { + CTRACE((tfp, "HTFinger: Unable to send command. Disconnecting.\n")); + NETCLOSE(finger_fd); + finger_fd = -1; + return status; + } + /* if bad status */ + /* Make a hypertext object with an anchor list. + */ + target = HTML_new(anAnchor, format_out, sink); + targetClass = *target->isa; /* Copy routine entry points */ + + /* Create the results report. + */ + CTRACE((tfp, "HTFinger: Reading finger information\n")); + START(HTML_HTML); + PUTC('\n'); + START(HTML_HEAD); + PUTC('\n'); + START(HTML_TITLE); + PUTS("Finger server on "); + PUTS(sitename); + END(HTML_TITLE); + PUTC('\n'); + END(HTML_HEAD); + PUTC('\n'); + START(HTML_BODY); + PUTC('\n'); + START(HTML_H1); + PUTS("Finger server on "); + START(HTML_EM); + PUTS(sitename); + END(HTML_EM); + PUTS(": "); + StrAllocCopy(cmd, command); + for (i = ((int) strlen(cmd) - 1); i >= 0; i--) { + if (cmd[i] == LF || cmd[i] == CR) { + cmd[i] = '\0'; + } else { + break; + } + } + PUTS(cmd); + FREE(cmd); + END(HTML_H1); + PUTC('\n'); + START(HTML_PRE); + + while ((ch = NEXT_CHAR) != EOF) { + + if (interrupted_in_htgetcharacter) { + CTRACE((tfp, + "HTFinger: Interrupted in HTGetCharacter, apparently.\n")); + _HTProgress(CONNECTION_INTERRUPTED); + goto end_html; + } + + if (ch != LF) { + *p = (char) ch; /* Put character in line */ + if (p < &line[BIG - 1]) { + p++; + } + } else { + *p = '\0'; /* Terminate line */ + /* + * OK we now have a line. + * Load it as 'l' and parse it. + */ + p = l = line; + while (*l) { + if (StrNCmp(l, STR_NEWS_URL, LEN_NEWS_URL) && + StrNCmp(l, "snews://", 8) && + StrNCmp(l, "nntp://", 7) && + StrNCmp(l, "snewspost:", 10) && + StrNCmp(l, "snewsreply:", 11) && + StrNCmp(l, "newspost:", 9) && + StrNCmp(l, "newsreply:", 10) && + StrNCmp(l, "ftp://", 6) && + StrNCmp(l, "file:/", 6) && + StrNCmp(l, "finger://", 9) && + StrNCmp(l, "http://", 7) && + StrNCmp(l, "https://", 8) && + StrNCmp(l, "wais://", 7) && + StrNCmp(l, STR_MAILTO_URL, LEN_MAILTO_URL) && + StrNCmp(l, "cso://", 6) && + StrNCmp(l, "gopher://", 9)) + PUTC(*l++); + else { + StrAllocCopy(href, l); + start_anchor(strtok(href, " \r\n\t,>)\"")); + while (*l && !StrChr(" \r\n\t,>)\"", *l)) + PUTC(*l++); + END(HTML_A); + FREE(href); + } + } + PUTC('\n'); + } + } + NETCLOSE(finger_fd); + finger_fd = -1; + + end_html: + END(HTML_PRE); + PUTC('\n'); + END(HTML_BODY); + PUTC('\n'); + END(HTML_HTML); + PUTC('\n'); + FREE_TARGET; + return (0); +} + +/* Load by name HTLoadFinger + * ============ + */ +int HTLoadFinger(const char *arg, + HTParentAnchor *anAnchor, + HTFormat format_out, + HTStream *stream) +{ + static char empty[1]; + + char *username, *sitename; /* Fields extracted from URL */ + char *slash, *at_sign; /* Fields extracted from URL */ + char *command, *str, *param; /* Buffers */ + int port; /* Port number from URL */ + int status; /* tcp return */ + int result = HT_LOADED; + BOOL IsGopherURL = FALSE; + const char *p1 = arg; + + CTRACE((tfp, "HTFinger: Looking for %s\n", (arg ? arg : "NULL"))); + + if (!(arg && *arg)) { + HTAlert(COULD_NOT_LOAD_DATA); + return HT_NOT_LOADED; /* Ignore if no name */ + } + + if (!initialized) + initialized = initialize(); + if (!initialized) { + HTAlert(gettext("Could not set up finger connection.")); + return HT_NOT_LOADED; /* FAIL */ + } + + /* Set up the host and command fields. + */ + if (!strncasecomp(arg, "finger://", 9)) { + p1 = arg + 9; /* Skip "finger://" prefix */ + } else if (!strncasecomp(arg, "gopher://", 9)) { + p1 = arg + 9; /* Skip "gopher://" prefix */ + IsGopherURL = TRUE; + } + + param = 0; + sitename = StrAllocCopy(param, p1); + if (param == 0) { + HTAlert(COULD_NOT_LOAD_DATA); + return HT_NOT_LOADED; + } else if ((slash = StrChr(sitename, '/')) != NULL) { + *slash++ = '\0'; + HTUnEscape(slash); + if (IsGopherURL) { + if (*slash != '0') { + HTAlert(COULD_NOT_LOAD_DATA); + return HT_NOT_LOADED; /* FAIL */ + } + *slash++ = '\0'; + } + } + + if ((at_sign = StrChr(sitename, '@')) != NULL) { + if (IsGopherURL) { + HTAlert(COULD_NOT_LOAD_DATA); + return HT_NOT_LOADED; /* FAIL */ + } else { + *at_sign++ = '\0'; + username = sitename; + sitename = at_sign; + HTUnEscape(username); + } + } else if (slash) { + username = slash; + } else { + username = empty; + } + + if (*sitename == '\0') { + HTAlert(gettext("Could not load data (no sitename in finger URL)")); + result = HT_NOT_LOADED; /* Ignore if no name */ + } else if (HTParsePort(sitename, &port) != NULL) { + if (port != 79) { + HTAlert(gettext("Invalid port number - will only use port 79!")); + result = HT_NOT_LOADED; /* Ignore if wrong port */ + } + } + + if (result == HT_LOADED) { + /* Load the string for making a connection/ + */ + str = 0; + HTSprintf0(&str, "lose://%s/", sitename); + + /* Load the command for the finger server. + */ + command = 0; + if (at_sign && slash) { + if (*slash == 'w' || *slash == 'W') { + HTSprintf0(&command, "/w %s%c%c", username, CR, LF); + } else { + HTSprintf0(&command, "%s%c%c", username, CR, LF); + } + } else if (at_sign) { + HTSprintf0(&command, "%s%c%c", username, CR, LF); + } else if (*username == '/') { + if ((slash = StrChr((username + 1), '/')) != NULL) { + *slash = ' '; + } + HTSprintf0(&command, "%s%c%c", username, CR, LF); + } else if ((*username == 'w' || *username == 'W') && + *(username + 1) == '/') { + if (*username + 2 != '\0') { + *(username + 1) = ' '; + } else { + *(username + 1) = '\0'; + } + HTSprintf0(&command, "/%s%c%c", username, CR, LF); + } else if ((*username == 'w' || *username == 'W') && + *(username + 1) == '\0') { + HTSprintf0(&command, "/%s%c%c", username, CR, LF); + } else if ((slash = StrChr(username, '/')) != NULL) { + *slash++ = '\0'; + if (*slash == 'w' || *slash == 'W') { + HTSprintf0(&command, "/w %s%c%c", username, CR, LF); + } else { + HTSprintf0(&command, "%s%c%c", username, CR, LF); + } + } else { + HTSprintf0(&command, "%s%c%c", username, CR, LF); + } + + /* Now, let's get a stream setup up from the FingerHost: + * CONNECTING to finger host + */ + CTRACE((tfp, "HTFinger: doing HTDoConnect on '%s'\n", str)); + status = HTDoConnect(str, "finger", FINGER_PORT, &finger_fd); + CTRACE((tfp, "HTFinger: Done DoConnect; status %d\n", status)); + + if (status == HT_INTERRUPTED) { + /* Interrupt cleanly */ + CTRACE((tfp, + "HTFinger: Interrupted on connect; recovering cleanly.\n")); + HTProgress(CONNECTION_INTERRUPTED); + result = HT_NOT_LOADED; + } else if (status < 0) { + NETCLOSE(finger_fd); + finger_fd = -1; + CTRACE((tfp, "HTFinger: Unable to connect to finger host.\n")); + HTAlert(gettext("Could not access finger host.")); + result = HT_NOT_LOADED; /* FAIL */ + } else { + CTRACE((tfp, "HTFinger: Connected to finger host '%s'.\n", str)); + + /* Send the command, and process response if successful. + */ + if (response(command, sitename, anAnchor, format_out, stream) != 0) { + HTAlert(gettext("No response from finger server.")); + result = HT_NOT_LOADED; + } + } + FREE(str); + FREE(command); + } + FREE(param); + return result; +} + +#ifdef GLOBALDEF_IS_MACRO +#define _HTFINGER_C_1_INIT { "finger", HTLoadFinger, NULL } +GLOBALDEF(HTProtocol, HTFinger, _HTFINGER_C_1_INIT); +#else +GLOBALDEF HTProtocol HTFinger = +{"finger", HTLoadFinger, NULL}; +#endif /* GLOBALDEF_IS_MACRO */ + +#endif /* not DISABLE_FINGER */ diff --git a/WWW/Library/Implementation/HTFinger.h b/WWW/Library/Implementation/HTFinger.h new file mode 100644 index 0000000..071d43b --- /dev/null +++ b/WWW/Library/Implementation/HTFinger.h @@ -0,0 +1,30 @@ +/* Finger protocol module for the WWW library */ +/* History: + * 21 Apr 94 Andrew Brooks + */ + +#ifndef HTFINGER_H +#define HTFINGER_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif +#ifdef GLOBALREF_IS_MACRO + extern GLOBALREF (HTProtocol, HTFinger); + +#else + GLOBALREF HTProtocol HTFinger; +#endif /* GLOBALREF_IS_MACRO */ + + extern int HTLoadFinger(const char *arg, + HTParentAnchor *anAnchor, + HTFormat format_out, + HTStream *stream); + +#ifdef __cplusplus +} +#endif +#endif /* HTFINGER_H */ diff --git a/WWW/Library/Implementation/HTFormat.c b/WWW/Library/Implementation/HTFormat.c new file mode 100644 index 0000000..a830387 --- /dev/null +++ b/WWW/Library/Implementation/HTFormat.c @@ -0,0 +1,2181 @@ +/* + * $LynxId: HTFormat.c,v 1.96 2022/03/31 23:39:38 tom Exp $ + * + * Manage different file formats HTFormat.c + * ============================= + * + * Bugs: + * Not reentrant. + * + * Assumes the incoming stream is ASCII, rather than a local file + * format, and so ALWAYS converts from ASCII on non-ASCII machines. + * Therefore, non-ASCII machines can't read local files. + * + */ + +#define HTSTREAM_INTERNAL 1 + +#include + +/* Implements: +*/ +#include + +static float HTMaxSecs = 1e10; /* No effective limit */ + +#ifdef UNIX +#ifdef NeXT +#define PRESENT_POSTSCRIPT "open %s; /bin/rm -f %s\n" +#else +#define PRESENT_POSTSCRIPT "(ghostview %s ; /bin/rm -f %s)&\n" + /* Full pathname would be better! */ +#endif /* NeXT */ +#endif /* UNIX */ + +#include +#include +#include +#include +#include +#include +#include +#include +/* Streams and structured streams which we use: +*/ +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifdef DISP_PARTIAL +#include +#endif + +#ifdef USE_BROTLI +#include +#endif + +BOOL HTOutputSource = NO; /* Flag: shortcut parser to stdout */ + +/* this version used by the NetToText stream */ +struct _HTStream { + const HTStreamClass *isa; + BOOL had_cr; + HTStream *sink; +}; + +/* Presentation methods + * -------------------- + */ +HTList *HTPresentations = NULL; +HTPresentation *default_presentation = NULL; + +/* + * To free off the presentation list. + */ +#ifdef LY_FIND_LEAKS +static void HTFreePresentations(void); +#endif + +/* Define a presentation system command for a content-type + * ------------------------------------------------------- + */ +void HTSetPresentation(const char *representation, + const char *command, + const char *testcommand, + double quality, + double secs, + double secs_per_byte, + long int maxbytes, + AcceptMedia media) +{ + HTPresentation *pres = typecalloc(HTPresentation); + + if (pres == NULL) + outofmem(__FILE__, "HTSetPresentation"); + + assert(representation != NULL); + + CTRACE2(TRACE_CFG, + (tfp, + "HTSetPresentation rep=%s, command=%s, test=%s, qual=%f\n", + NonNull(representation), + NonNull(command), + NonNull(testcommand), + quality)); + + pres->rep = HTAtom_for(representation); + pres->rep_out = WWW_PRESENT; /* Fixed for now ... :-) */ + pres->converter = HTSaveAndExecute; /* Fixed for now ... */ + pres->quality = (float) quality; + pres->secs = (float) secs; + pres->secs_per_byte = (float) secs_per_byte; + pres->maxbytes = maxbytes; + pres->get_accept = 0; + pres->accept_opt = media; + + pres->command = NULL; + StrAllocCopy(pres->command, command); + + pres->testcommand = NULL; + StrAllocCopy(pres->testcommand, testcommand); + + /* + * Memory leak fixed. + * 05-28-94 Lynx 2-3-1 Garrett Arch Blythe + */ + if (!HTPresentations) { + HTPresentations = HTList_new(); +#ifdef LY_FIND_LEAKS + atexit(HTFreePresentations); +#endif + } + + if (strcmp(representation, "*") == 0) { + FREE(default_presentation); + default_presentation = pres; + } else { + HTList_addObject(HTPresentations, pres); + } +} + +/* Define a built-in function for a content-type + * --------------------------------------------- + */ +void HTSetConversion(const char *representation_in, + const char *representation_out, + HTConverter *converter, + double quality, + double secs, + double secs_per_byte, + long int maxbytes, + AcceptMedia media) +{ + HTPresentation *pres = typecalloc(HTPresentation); + + if (pres == NULL) + outofmem(__FILE__, "HTSetConversion"); + + CTRACE2(TRACE_CFG, + (tfp, + "HTSetConversion rep_in=%s, rep_out=%s, qual=%f\n", + NonNull(representation_in), + NonNull(representation_out), + quality)); + + pres->rep = HTAtom_for(representation_in); + pres->rep_out = HTAtom_for(representation_out); + pres->converter = converter; + pres->command = NULL; + pres->testcommand = NULL; + pres->quality = (float) quality; + pres->secs = (float) secs; + pres->secs_per_byte = (float) secs_per_byte; + pres->maxbytes = maxbytes; + pres->get_accept = TRUE; + pres->accept_opt = media; + + /* + * Memory Leak fixed. + * 05-28-94 Lynx 2-3-1 Garrett Arch Blythe + */ + if (!HTPresentations) { + HTPresentations = HTList_new(); +#ifdef LY_FIND_LEAKS + atexit(HTFreePresentations); +#endif + } + + HTList_addObject(HTPresentations, pres); +} + +#ifdef LY_FIND_LEAKS +/* + * Purpose: Free the presentation list. + * Arguments: void + * Return Value: void + * Remarks/Portability/Dependencies/Restrictions: + * Made to clean up Lynx's bad leakage. + * Revision History: + * 05-28-94 created Lynx 2-3-1 Garrett Arch Blythe + */ +static void HTFreePresentations(void) +{ + HTPresentation *pres = NULL; + + /* + * Loop through the list. + */ + while (!HTList_isEmpty(HTPresentations)) { + /* + * Free off each item. May also need to free off it's items, but not + * sure as of yet. + */ + pres = (HTPresentation *) HTList_removeLastObject(HTPresentations); + FREE(pres->command); + FREE(pres->testcommand); + FREE(pres); + } + /* + * Free the list itself. + */ + HTList_delete(HTPresentations); + HTPresentations = NULL; +} +#endif /* LY_FIND_LEAKS */ + +/* File buffering + * -------------- + * + * The input file is read using the macro which can read from + * a socket or a file. + * The input buffer size, if large will give greater efficiency and + * release the server faster, and if small will save space on PCs etc. + */ +#define INPUT_BUFFER_SIZE 4096 /* Tradeoff */ +static char input_buffer[INPUT_BUFFER_SIZE]; +static char *input_pointer; +static char *input_limit; +static int input_file_number; + +/* Set up the buffering + * + * These routines are public because they are in fact needed by + * many parsers, and on PCs and Macs we should not duplicate + * the static buffer area. + */ +void HTInitInput(int file_number) +{ + input_file_number = file_number; + input_pointer = input_limit = input_buffer; +} + +int interrupted_in_htgetcharacter = 0; +int HTGetCharacter(void) +{ + char ch; + + interrupted_in_htgetcharacter = 0; + do { + if (input_pointer >= input_limit) { + int status = NETREAD(input_file_number, + input_buffer, INPUT_BUFFER_SIZE); + + if (status <= 0) { + if (status == 0) + return EOF; + if (status == HT_INTERRUPTED) { + CTRACE((tfp, "HTFormat: Interrupted in HTGetCharacter\n")); + interrupted_in_htgetcharacter = 1; + return EOF; + } + CTRACE((tfp, "HTFormat: File read error %d\n", status)); + return EOF; /* -1 is returned by UCX + at end of HTTP link */ + } + input_pointer = input_buffer; + input_limit = input_buffer + status; + } + ch = *input_pointer++; + } while (ch == (char) 13); /* Ignore ASCII carriage return */ + + return FROMASCII(UCH(ch)); +} + +#ifdef USE_SSL +int HTGetSSLCharacter(void *handle) +{ + char ch; + + interrupted_in_htgetcharacter = 0; + if (!handle) + return (char) EOF; + do { + if (input_pointer >= input_limit) { + int status = SSL_read((SSL *) handle, + input_buffer, INPUT_BUFFER_SIZE); + + if (status <= 0) { + if (status == 0) + return (char) EOF; + if (status == HT_INTERRUPTED) { + CTRACE((tfp, + "HTFormat: Interrupted in HTGetSSLCharacter\n")); + interrupted_in_htgetcharacter = 1; + return (char) EOF; + } + CTRACE((tfp, "HTFormat: SSL_read error %d\n", status)); + return (char) EOF; /* -1 is returned by UCX + at end of HTTP link */ + } + input_pointer = input_buffer; + input_limit = input_buffer + status; + } + ch = *input_pointer++; + } while (ch == (char) 13); /* Ignore ASCII carriage return */ + + return FROMASCII(ch); +} +#endif /* USE_SSL */ + +/* Match maintype to any MIME type starting with maintype, for example: + * image/gif should match image + */ +static int half_match(char *trial_type, char *target) +{ + char *cp = StrChr(trial_type, '/'); + + /* if no '/' or no '*' */ + if (!cp || *(cp + 1) != '*') + return 0; + + CTRACE((tfp, "HTFormat: comparing %s and %s for half match\n", + trial_type, target)); + + /* main type matches */ + if (!StrNCmp(trial_type, target, ((cp - trial_type) - 1))) + return 1; + + return 0; +} + +/* + * Evaluate a deferred mailcap test command, i.e.,. one that substitutes the + * document's charset or other values in %{name} format. + */ +static BOOL failsMailcap(HTPresentation *pres, HTParentAnchor *anchor) +{ + if (pres->testcommand != NULL && + anchor != NULL && + anchor->content_type_params != NULL) { + if (LYTestMailcapCommand(pres->testcommand, + anchor->content_type_params) != 0) + return TRUE; + } + return FALSE; +} + +#define WWW_WILDCARD_REP_OUT HTAtom_for("*") + +/* Look up a presentation + * ---------------------- + * + * If fill_in is NULL, only look for an exact match. + * If a wildcard match is made, *fill_in is used to store + * a possibly modified presentation, and a pointer to it is + * returned. For an exact match, a pointer to the presentation + * in the HTPresentations list is returned. Returns NULL if + * nothing found. - kw + * + */ +static HTPresentation *HTFindPresentation(HTFormat rep_in, + HTFormat rep_out, + HTPresentation *fill_in, + HTParentAnchor *anchor) +{ +#undef THIS_FUNC +#define THIS_FUNC "HTFindPresentation" + HTAtom *wildcard = NULL; /* = HTAtom_for("*"); lookup when needed - kw */ + int n; + int i; + HTPresentation *pres; + HTPresentation *match; + HTPresentation *strong_wildcard_match = 0; + HTPresentation *weak_wildcard_match = 0; + HTPresentation *last_default_match = 0; + HTPresentation *strong_subtype_wildcard_match = 0; + + CTRACE((tfp, THIS_FUNC ": Looking up presentation for %s to %s\n", + HTAtom_name(rep_in), HTAtom_name(rep_out))); + + n = HTList_count(HTPresentations); + for (i = 0; i < n; i++) { + pres = (HTPresentation *) HTList_objectAt(HTPresentations, i); + if (pres->rep == rep_in) { + if (pres->rep_out == rep_out) { + if (failsMailcap(pres, anchor)) + continue; + CTRACE((tfp, THIS_FUNC ": found exact match: %s -> %s\n", + HTAtom_name(pres->rep), + HTAtom_name(pres->rep_out))); + return pres; + + } else if (!fill_in) { + continue; + } else { + if (!wildcard) + wildcard = WWW_WILDCARD_REP_OUT; + if (pres->rep_out == wildcard) { + if (failsMailcap(pres, anchor)) + continue; + if (!strong_wildcard_match) + strong_wildcard_match = pres; + /* otherwise use the first one */ + CTRACE((tfp, THIS_FUNC + ": found strong wildcard match: %s -> %s\n", + HTAtom_name(pres->rep), + HTAtom_name(pres->rep_out))); + } + } + + } else if (!fill_in) { + continue; + + } else if (half_match(HTAtom_name(pres->rep), + HTAtom_name(rep_in))) { + if (pres->rep_out == rep_out) { + if (failsMailcap(pres, anchor)) + continue; + if (!strong_subtype_wildcard_match) + strong_subtype_wildcard_match = pres; + /* otherwise use the first one */ + CTRACE((tfp, THIS_FUNC + ": found strong subtype wildcard match: %s -> %s\n", + HTAtom_name(pres->rep), + HTAtom_name(pres->rep_out))); + } + } + + if (pres->rep == WWW_SOURCE) { + if (pres->rep_out == rep_out) { + if (failsMailcap(pres, anchor)) + continue; + if (!weak_wildcard_match) + weak_wildcard_match = pres; + /* otherwise use the first one */ + CTRACE((tfp, + THIS_FUNC ": found weak wildcard match: %s\n", + HTAtom_name(pres->rep_out))); + + } else if (!last_default_match) { + if (!wildcard) + wildcard = WWW_WILDCARD_REP_OUT; + if (pres->rep_out == wildcard) { + if (failsMailcap(pres, anchor)) + continue; + last_default_match = pres; + /* otherwise use the first one */ + } + } + } + } + + match = (strong_subtype_wildcard_match + ? strong_subtype_wildcard_match + : (strong_wildcard_match + ? strong_wildcard_match + : (weak_wildcard_match + ? weak_wildcard_match + : last_default_match))); + + if (match) { + *fill_in = *match; /* Specific instance */ + fill_in->rep = rep_in; /* yuk */ + fill_in->rep_out = rep_out; /* yuk */ + return fill_in; + } + + return NULL; +#undef THIS_FUNC +} + +/* Create a filter stack + * --------------------- + * + * If a wildcard match is made, a temporary HTPresentation + * structure is made to hold the destination format while the + * new stack is generated. This is just to pass the out format to + * MIME so far. Storing the format of a stream in the stream might + * be a lot neater. + * + */ +HTStream *HTStreamStack(HTFormat rep_in, + HTFormat rep_out, + HTStream *sink, + HTParentAnchor *anchor) +{ +#undef THIS_FUNC +#define THIS_FUNC "HTStreamStack" + HTPresentation temp; + HTPresentation *match; + HTStream *result; + + CTRACE((tfp, THIS_FUNC ": Constructing stream stack for %s to %s (%s)\n", + HTAtom_name(rep_in), + HTAtom_name(rep_out), + NONNULL(anchor->content_type_params))); + + if (rep_out == rep_in) { + result = sink; + + } else if ((match = HTFindPresentation(rep_in, rep_out, &temp, anchor))) { + if (match == &temp) { + CTRACE((tfp, THIS_FUNC ": Using %s\n", HTAtom_name(temp.rep_out))); + } else { + CTRACE((tfp, THIS_FUNC ": found exact match: %s -> %s\n", + HTAtom_name(match->rep), + HTAtom_name(match->rep_out))); + } + result = (*match->converter) (match, anchor, sink); + } else { + result = NULL; + } + if (TRACE) { + if (result && result->isa && result->isa->name) { + CTRACE((tfp, THIS_FUNC ": Returning \"%s\"\n", result->isa->name)); + } else if (result) { + CTRACE((tfp, THIS_FUNC ": Returning *unknown* stream!\n")); + } else { + CTRACE((tfp, THIS_FUNC ": Returning NULL!\n")); + CTRACE_FLUSH(tfp); /* a crash may be imminent... - kw */ + } + } + return result; +#undef THIS_FUNC +} + +/* Put a presentation near start of list + * ------------------------------------- + * + * Look up a presentation (exact match only) and, if found, reorder + * it to the start of the HTPresentations list. - kw + */ +void HTReorderPresentation(HTFormat rep_in, + HTFormat rep_out) +{ + HTPresentation *match; + + if ((match = HTFindPresentation(rep_in, rep_out, NULL, NULL))) { + HTList_removeObject(HTPresentations, match); + HTList_addObject(HTPresentations, match); + } +} + +/* + * Setup 'get_accept' flag to denote presentations that are not redundant, + * and will be listed in "Accept:" header. + */ +void HTFilterPresentations(void) +{ + int i, j; + int n = HTList_count(HTPresentations); + HTPresentation *p, *q; + BOOL matched; + char *s, *t; + + CTRACE((tfp, "HTFilterPresentations (AcceptMedia %#x)\n", LYAcceptMedia)); + for (i = 0; i < n; i++) { + p = (HTPresentation *) HTList_objectAt(HTPresentations, i); + s = HTAtom_name(p->rep); + + p->get_accept = FALSE; + if ((LYAcceptMedia & p->accept_opt) != 0 + && p->rep_out == WWW_PRESENT + && p->rep != WWW_SOURCE + && strcasecomp(s, "www/mime") + && strcasecomp(s, "www/compressed") + && p->quality <= 1.0 && p->quality >= 0.0) { + matched = TRUE; + for (j = 0; j < i; j++) { + q = (HTPresentation *) HTList_objectAt(HTPresentations, j); + t = HTAtom_name(q->rep); + + if (!strcasecomp(s, t)) { + matched = FALSE; + CTRACE((tfp, " match %s %s\n", s, t)); + break; + } + } + p->get_accept = matched; + } + } +} + +/* Find the cost of a filter stack + * ------------------------------- + * + * Must return the cost of the same stack which HTStreamStack would set up. + * + * On entry, + * length The size of the data to be converted + */ +float HTStackValue(HTFormat rep_in, + HTFormat rep_out, + double initial_value, + long int length) +{ + HTAtom *wildcard = WWW_WILDCARD_REP_OUT; + + CTRACE((tfp, "HTFormat: Evaluating stream stack for %s worth %.3f to %s\n", + HTAtom_name(rep_in), initial_value, HTAtom_name(rep_out))); + + if (rep_out == WWW_SOURCE || rep_out == rep_in) + return 0.0; + + { + int n = HTList_count(HTPresentations); + int i; + HTPresentation *pres; + + for (i = 0; i < n; i++) { + pres = (HTPresentation *) HTList_objectAt(HTPresentations, i); + if (pres->rep == rep_in && + (pres->rep_out == rep_out || pres->rep_out == wildcard)) { + float value = (float) (initial_value * pres->quality); + + if (HTMaxSecs > 0.0) + value = (value + - ((float) length * pres->secs_per_byte + + pres->secs) + / HTMaxSecs); + return value; + } + } + } + + return (float) -1e30; /* Really bad */ + +} + +/* Display the page while transfer in progress + * ------------------------------------------- + * + * Repaint the page only when necessary. + * This is a traverse call for HText_pageDisplay() - it works!. + * + */ +void HTDisplayPartial(void) +{ +#ifdef DISP_PARTIAL + if (display_partial) { + /* + * HText_getNumOfLines() = "current" number of complete lines received + * NumOfLines_partial = number of lines at the moment of last repaint. + * (we update NumOfLines_partial only when we repaint the display.) + * + * display_partial could only be enabled in HText_new() so a new + * HTMainText object available - all HText_ functions use it, lines + * counter HText_getNumOfLines() in particular. + * + * Otherwise HTMainText holds info from the previous document and we + * may repaint it instead of the new one: prev doc scrolled to the + * first line (=Newline_partial) is not good looking :-) 23 Aug 1998 + * Leonid Pauzner + * + * So repaint the page only when necessary: + */ + int Newline_partial = LYGetNewline(); + + if (((Newline_partial + display_lines) - 1 > NumOfLines_partial) + /* current page not complete... */ + && (partial_threshold > 0 ? + ((Newline_partial + partial_threshold) - 1 <= + HText_getNumOfLines()) : + ((Newline_partial + display_lines) - 1 <= HText_getNumOfLines())) + /* + * Originally we rendered by increments of 2 lines, + * but that got annoying on slow network connections. + * Then we switched to full-pages. Now it's configurable. + * If partial_threshold <= 0, then it's a full page + */ + ) { + if (LYMainLoop_pageDisplay(Newline_partial)) + NumOfLines_partial = HText_getNumOfLines(); + } + } +#else /* nothing */ +#endif /* DISP_PARTIAL */ +} + +/* Put this as early as possible, OK just after HTDisplayPartial() */ +void HTFinishDisplayPartial(void) +{ +#ifdef DISP_PARTIAL + /* + * End of incremental rendering stage here. + */ + display_partial = FALSE; +#endif /* DISP_PARTIAL */ +} + +/* Push data from a socket down a stream + * ------------------------------------- + * + * This routine is responsible for creating and PRESENTING any + * graphic (or other) objects described by the file. + * + * The file number given is assumed to be a TELNET stream, i.e., containing + * CRLF at the end of lines which need to be stripped to LF for unix + * when the format is textual. + * + * State of socket and target stream on entry: + * socket (file_number) assumed open, + * target (sink) assumed valid. + * + * Return values: + * HT_INTERRUPTED Interruption or error after some data received. + * -2 Unexpected disconnect before any data received. + * -1 Interruption or error before any data received, or + * (UNIX) other read error before any data received, or + * download cancelled. + * HT_LOADED Normal close of socket (end of file indication + * received), or + * unexpected disconnect after some data received, or + * other read error after some data received, or + * (not UNIX) other read error before any data received. + * + * State of socket and target stream on return depends on return value: + * HT_INTERRUPTED socket still open, target aborted. + * -2 socket still open, target stream still valid. + * -1 socket still open, target aborted. + * otherwise socket closed, target stream still valid. + */ +int HTCopy(HTParentAnchor *anchor, + int file_number, + void *handle GCC_UNUSED, + HTStream *sink) +{ + HTStreamClass targetClass; + BOOL suppress_readprogress = NO; + off_t limit = anchor ? anchor->content_length : 0; + off_t bytes = 0; + off_t header_length = 0; + int rv = 0; + + /* Push the data down the stream + */ + targetClass = *(sink->isa); /* Copy pointers to procedures */ + + /* + * Push binary from socket down sink + * + * This operation could be put into a main event loop + */ + HTReadProgress(bytes, (off_t) 0); + for (;;) { + int status; + + if (LYCancelDownload) { + LYCancelDownload = FALSE; + (*targetClass._abort) (sink, NULL); + rv = -1; + goto finished; + } + + if (HTCheckForInterrupt()) { + _HTProgress(TRANSFER_INTERRUPTED); + (*targetClass._abort) (sink, NULL); + if (bytes) + rv = HT_INTERRUPTED; + else + rv = -1; + goto finished; + } +#ifdef USE_SSL + if (handle) + status = SSL_read((SSL *) handle, input_buffer, INPUT_BUFFER_SIZE); + else + status = NETREAD(file_number, input_buffer, INPUT_BUFFER_SIZE); +#else + status = NETREAD(file_number, input_buffer, INPUT_BUFFER_SIZE); +#endif /* USE_SSL */ + if (status <= 0) { + if (status == 0) { + break; + } else if (status == HT_INTERRUPTED) { + _HTProgress(TRANSFER_INTERRUPTED); + (*targetClass._abort) (sink, NULL); + if (bytes) + rv = HT_INTERRUPTED; + else + rv = -1; + goto finished; + } else if (SOCKET_ERRNO == ENOTCONN || +#ifdef _WINDOWS /* 1997/11/10 (Mon) 16:57:18 */ + SOCKET_ERRNO == ETIMEDOUT || +#endif + SOCKET_ERRNO == ECONNRESET || + SOCKET_ERRNO == EPIPE) { + /* + * Arrrrgh, HTTP 0/1 compatibility problem, maybe. + */ + if (bytes <= 0) { + /* + * Don't have any data, so let the calling function decide + * what to do about it. - FM + */ + rv = -2; + goto finished; + } else { +#ifdef UNIX + /* + * Treat what we've received already as the complete + * transmission, but not without giving the user an alert. + * I don't know about all the different TCP stacks for VMS + * etc., so this is currently only for UNIX. - kw + */ + HTInetStatus("NETREAD"); + HTAlert("Unexpected server disconnect."); + CTRACE((tfp, + "HTCopy: Unexpected server disconnect. Treating as completed.\n")); +#else /* !UNIX */ + /* + * Treat what we've gotten already as the complete + * transmission. - FM + */ + CTRACE((tfp, + "HTCopy: Unexpected server disconnect. Treating as completed.\n")); + status = 0; +#endif /* UNIX */ + } +#ifdef UNIX + } else { /* status < 0 and other errno */ + /* + * Treat what we've received already as the complete + * transmission, but not without giving the user an alert. I + * don't know about all the different TCP stacks for VMS etc., + * so this is currently only for UNIX. - kw + */ + HTInetStatus("NETREAD"); + HTAlert("Unexpected read error."); + if (bytes) { + (void) NETCLOSE(file_number); + rv = HT_LOADED; + } else { + (*targetClass._abort) (sink, NULL); + rv = -1; + } + goto finished; +#endif + } + break; + } + + /* + * Suppress ReadProgress messages when collecting a redirection + * message, at least initially (unless/until anchor->content_type gets + * changed, probably by the MIME message parser). That way messages + * put up by the HTTP module or elsewhere can linger in the statusline + * for a while. - kw + */ + suppress_readprogress = (BOOL) (anchor && anchor->content_type && + !strcmp(anchor->content_type, + "message/x-http-redirection")); +#ifdef NOT_ASCII + { + char *p; + + for (p = input_buffer; p < input_buffer + status; p++) { + *p = FROMASCII(*p); + } + } +#endif /* NOT_ASCII */ + + header_length = anchor != 0 ? anchor->header_length : 0; + + (*targetClass.put_block) (sink, input_buffer, status); + if (anchor != 0 && anchor->inHEAD) { + if (!suppress_readprogress) { + statusline(gettext("Reading headers...")); + } + CTRACE((tfp, "HTCopy read %" PRI_off_t " header bytes\n", + CAST_off_t (anchor->header_length))); + } else { + /* + * If header-length is increased at this point, that is due to + * HTMIME, which detects the end of the server headers. There + * may be additional (non-header) data in that block. + */ + if (anchor != 0 && (anchor->header_length > header_length)) { + int header = (int) (anchor->header_length - header_length); + + CTRACE((tfp, "HTCopy read %" PRI_off_t " header bytes " + "(%d extra vs %d total)\n", + CAST_off_t (anchor->header_length), + header, status)); + if (status > header) { + bytes += (status - header); + } + } else { + bytes += status; + } + if (!suppress_readprogress) { + HTReadProgress(bytes, limit); + } + HTDisplayPartial(); + } + + /* a few buggy implementations do not close the connection properly + * and will hang if we try to read past the declared content-length. + */ + if (limit > 0 && bytes >= limit) + break; + } /* next bufferload */ + if (anchor != 0) { + CTRACE((tfp, "HTCopy copied %" + PRI_off_t " actual, %" + PRI_off_t " limit\n", CAST_off_t (bytes), CAST_off_t (limit))); + anchor->actual_length = bytes; + } + + _HTProgress(TRANSFER_COMPLETE); + (void) NETCLOSE(file_number); + rv = HT_LOADED; + + finished: + HTFinishDisplayPartial(); + return (rv); +} + +/* Push data from a file pointer down a stream + * ------------------------------------- + * + * This routine is responsible for creating and PRESENTING any + * graphic (or other) objects described by the file. + * + * + * State of file and target stream on entry: + * FILE* (fp) assumed open, + * target (sink) assumed valid. + * + * Return values: + * HT_INTERRUPTED Interruption after some data read. + * HT_PARTIAL_CONTENT Error after some data read. + * -1 Error before any data read. + * HT_LOADED Normal end of file indication on reading. + * + * State of file and target stream on return: + * always fp still open, target stream still valid. + */ +int HTFileCopy(FILE *fp, HTStream *sink) +{ + HTStreamClass targetClass; + int status; + off_t bytes; + int rv = HT_OK; + + /* Push the data down the stream + */ + targetClass = *(sink->isa); /* Copy pointers to procedures */ + + /* Push binary from socket down sink + */ + HTReadProgress(bytes = 0, (off_t) 0); + for (;;) { + status = (int) fread(input_buffer, + (size_t) 1, + (size_t) INPUT_BUFFER_SIZE, fp); + if (status == 0) { /* EOF or error */ + if (ferror(fp) == 0) { + rv = HT_LOADED; + break; + } + CTRACE((tfp, "HTFormat: Read error, read returns %d\n", + ferror(fp))); + if (bytes) { + rv = HT_PARTIAL_CONTENT; + } else { + rv = -1; + } + break; + } + + (*targetClass.put_block) (sink, input_buffer, status); + bytes += status; + HTReadProgress(bytes, (off_t) 0); + /* Suppress last screen update in partial mode - a regular update under + * control of mainloop() should follow anyway. - kw + */ +#ifdef DISP_PARTIAL + if (display_partial && bytes != HTMainAnchor->content_length) + HTDisplayPartial(); +#endif + + if (HTCheckForInterrupt()) { + _HTProgress(TRANSFER_INTERRUPTED); + if (bytes) { + rv = HT_INTERRUPTED; + } else { + rv = -1; + } + break; + } + } /* next bufferload */ + + HTFinishDisplayPartial(); + return rv; +} + +#ifdef USE_SOURCE_CACHE +/* Push data from an HTChunk down a stream + * --------------------------------------- + * + * This routine is responsible for creating and PRESENTING any + * graphic (or other) objects described by the file. + * + * State of memory and target stream on entry: + * HTChunk* (chunk) and target (sink) assumed valid. + * + * Return values: + * HT_LOADED All data sent. + * HT_INTERRUPTED Interruption after some data read. + * + * State of memory and target stream on return: + * always chunk unchanged, target stream still valid. + */ +int HTMemCopy(HTChunk *chunk, HTStream *sink) +{ + HTStreamClass targetClass; + off_t bytes; + int rv = HT_OK; + + targetClass = *(sink->isa); + HTReadProgress(bytes = 0, (off_t) 0); + for (; chunk != NULL; chunk = chunk->next) { + + /* Push the data down the stream a piece at a time, in case we're + * running a large document on a slow machine. + */ + (*targetClass.put_block) (sink, chunk->data, chunk->size); + bytes += chunk->size; + + HTReadProgress(bytes, (off_t) 0); + HTDisplayPartial(); + + if (HTCheckForInterrupt()) { + _HTProgress(TRANSFER_INTERRUPTED); + if (bytes) { + rv = HT_INTERRUPTED; + } else { + rv = -1; + } + break; + } + } + + HTFinishDisplayPartial(); + return rv; +} +#endif + +#ifdef USE_ZLIB +/* Push data from a gzip file pointer down a stream + * ------------------------------------- + * + * This routine is responsible for creating and PRESENTING any + * graphic (or other) objects described by the file. + * + * + * State of file and target stream on entry: + * gzFile (gzfp) assumed open (should have gzipped content), + * target (sink) assumed valid. + * + * Return values: + * HT_INTERRUPTED Interruption after some data read. + * HT_PARTIAL_CONTENT Error after some data read. + * -1 Error before any data read. + * HT_LOADED Normal end of file indication on reading. + * + * State of file and target stream on return: + * always gzfp still open, target stream still valid. + */ +static int HTGzFileCopy(gzFile gzfp, HTStream *sink) +{ + HTStreamClass targetClass; + int status; + off_t bytes; + int gzerrnum; + int rv = HT_OK; + + /* Push the data down the stream + */ + targetClass = *(sink->isa); /* Copy pointers to procedures */ + + /* read and inflate gzip'd file, and push binary down sink + */ + HTReadProgress(bytes = 0, (off_t) 0); + for (;;) { + status = gzread(gzfp, input_buffer, INPUT_BUFFER_SIZE); + if (status <= 0) { /* EOF or error */ + if (status == 0) { + rv = HT_LOADED; + break; + } + CTRACE((tfp, "HTGzFileCopy: Read error, gzread returns %d\n", + status)); + CTRACE((tfp, "gzerror : %s\n", + gzerror(gzfp, &gzerrnum))); + if (TRACE) { + if (gzerrnum == Z_ERRNO) + perror("gzerror "); + } + if (bytes) { + rv = HT_PARTIAL_CONTENT; + } else { + rv = -1; + } + break; + } + + (*targetClass.put_block) (sink, input_buffer, status); + bytes += status; + HTReadProgress(bytes, (off_t) -1); + HTDisplayPartial(); + + if (HTCheckForInterrupt()) { + _HTProgress(TRANSFER_INTERRUPTED); + rv = HT_INTERRUPTED; + break; + } + } /* next bufferload */ + + HTFinishDisplayPartial(); + return rv; +} + +#ifndef HAVE_ZERROR +#define zError(s) LynxZError(s) +static const char *zError(int status) +{ + static char result[80]; + + sprintf(result, "zlib error %d", status); + return result; +} +#endif + +/* Push data from a deflate file pointer down a stream + * ------------------------------------- + * + * This routine is responsible for creating and PRESENTING any + * graphic (or other) objects described by the file. The code is + * loosely based on the inflate.c file from w3m. + * + * + * State of file and target stream on entry: + * FILE (zzfp) assumed open (should have deflated content), + * target (sink) assumed valid. + * + * Return values: + * HT_INTERRUPTED Interruption after some data read. + * HT_PARTIAL_CONTENT Error after some data read. + * -1 Error before any data read. + * HT_LOADED Normal end of file indication on reading. + * + * State of file and target stream on return: + * always zzfp still open, target stream still valid. + */ +static int HTZzFileCopy(FILE *zzfp, HTStream *sink) +{ +#undef THIS_FUNC +#define THIS_FUNC "HTZzFileCopy" + static char dummy_head[1 + 1] = + { + 0x8 + 0x7 * 0x10, + (((0x8 + 0x7 * 0x10) * 0x100 + 30) / 31 * 31) & 0xFF, + }; + + z_stream s; + HTStreamClass targetClass; + off_t bytes; + int rv = HT_OK; + char output_buffer[INPUT_BUFFER_SIZE]; + int status; + int flush; + int retry = 0; + int len = 0; + + /* Push the data down the stream + */ + targetClass = *(sink->isa); /* Copy pointers to procedures */ + + memset(&s, 0, sizeof(s)); + status = inflateInit(&s); + if (status != Z_OK) { + CTRACE((tfp, THIS_FUNC " inflateInit() %s\n", zError(status))); + exit_immediately(EXIT_FAILURE); + } + s.avail_in = 0; + s.next_out = (Bytef *) output_buffer; + s.avail_out = sizeof(output_buffer); + flush = Z_NO_FLUSH; + + /* read and inflate deflate'd file, and push binary down sink + */ + HTReadProgress(bytes = 0, (off_t) 0); + for (;;) { + if (s.avail_in == 0) { + s.next_in = (Bytef *) input_buffer; + s.avail_in = (uInt) fread(input_buffer, + (size_t) 1, + (size_t) INPUT_BUFFER_SIZE, zzfp); + len = (int) s.avail_in; + } + status = inflate(&s, flush); + if (status == Z_STREAM_END || status == Z_BUF_ERROR) { + len = (int) sizeof(output_buffer) - (int) s.avail_out; + if (len > 0) { + (*targetClass.put_block) (sink, output_buffer, len); + bytes += len; + HTReadProgress(bytes, (off_t) -1); + HTDisplayPartial(); + } + rv = HT_LOADED; + break; + } else if (status == Z_DATA_ERROR && !retry++) { + status = inflateReset(&s); + if (status != Z_OK) { + CTRACE((tfp, THIS_FUNC " inflateReset() %s\n", zError(status))); + rv = -1; + break; + } + s.next_in = (Bytef *) dummy_head; + s.avail_in = sizeof(dummy_head); + (void) inflate(&s, flush); + s.next_in = (Bytef *) input_buffer; + s.avail_in = (unsigned) len; + continue; + } else if (status != Z_OK) { + CTRACE((tfp, THIS_FUNC " inflate() %s\n", zError(status))); + rv = bytes ? HT_PARTIAL_CONTENT : -1; + break; + } else if (s.avail_out == 0) { + len = sizeof(output_buffer); + s.next_out = (Bytef *) output_buffer; + s.avail_out = sizeof(output_buffer); + + (*targetClass.put_block) (sink, output_buffer, len); + bytes += len; + HTReadProgress(bytes, (off_t) -1); + HTDisplayPartial(); + + if (HTCheckForInterrupt()) { + _HTProgress(TRANSFER_INTERRUPTED); + rv = bytes ? HT_INTERRUPTED : -1; + break; + } + } + retry = 1; + } /* next bufferload */ + + inflateEnd(&s); + HTFinishDisplayPartial(); + return rv; +#undef THIS_FUNC +} +#endif /* USE_ZLIB */ + +#ifdef USE_BZLIB +/* Push data from a bzip file pointer down a stream + * ------------------------------------- + * + * This routine is responsible for creating and PRESENTING any + * graphic (or other) objects described by the file. + * + * + * State of file and target stream on entry: + * BZFILE (bzfp) assumed open (should have bzipped content), + * target (sink) assumed valid. + * + * Return values: + * HT_INTERRUPTED Interruption after some data read. + * HT_PARTIAL_CONTENT Error after some data read. + * -1 Error before any data read. + * HT_LOADED Normal end of file indication on reading. + * + * State of file and target stream on return: + * always bzfp still open, target stream still valid. + */ +static int HTBzFileCopy(BZFILE * bzfp, HTStream *sink) +{ + HTStreamClass targetClass; + int status; + off_t bytes; + int bzerrnum; + int rv = HT_OK; + + /* Push the data down the stream + */ + targetClass = *(sink->isa); /* Copy pointers to procedures */ + + /* read and inflate bzip'd file, and push binary down sink + */ + HTReadProgress(bytes = 0, (off_t) 0); + for (;;) { + status = BZ2_bzread(bzfp, input_buffer, INPUT_BUFFER_SIZE); + if (status <= 0) { /* EOF or error */ + if (status == 0) { + rv = HT_LOADED; + break; + } + CTRACE((tfp, "HTBzFileCopy: Read error, bzread returns %d\n", + status)); + CTRACE((tfp, "bzerror : %s\n", + BZ2_bzerror(bzfp, &bzerrnum))); + if (bytes) { + rv = HT_PARTIAL_CONTENT; + } else { + rv = -1; + } + break; + } + + (*targetClass.put_block) (sink, input_buffer, status); + bytes += status; + HTReadProgress(bytes, (off_t) -1); + HTDisplayPartial(); + + if (HTCheckForInterrupt()) { + _HTProgress(TRANSFER_INTERRUPTED); + rv = HT_INTERRUPTED; + break; + } + } /* next bufferload */ + + HTFinishDisplayPartial(); + return rv; +} +#endif /* USE_BZLIB */ + +#ifdef USE_BROTLI +/* Push data from a brotli file pointer down a stream + * ------------------------------------- + * + * This routine is responsible for creating and PRESENTING any + * graphic (or other) objects described by the file. + * + * + * State of file and target stream on entry: + * BZFILE (bzfp) assumed open (should have bzipped content), + * target (sink) assumed valid. + * + * Return values: + * HT_INTERRUPTED Interruption after some data read. + * HT_PARTIAL_CONTENT Error after some data read. + * -1 Error before any data read. + * HT_LOADED Normal end of file indication on reading. + * + * State of file and target stream on return: + * always bzfp still open, target stream still valid. + */ +static int HTBrFileCopy(FILE *brfp, HTStream *sink) +{ +#undef THIS_FUNC +#define THIS_FUNC "HTBrFileCopy" + HTStreamClass targetClass; + int status; + off_t bytes; + int rv = HT_OK; + BrotliDecoderResult status2 = BROTLI_DECODER_RESULT_ERROR; + + char *brotli_buffer = NULL; + char *normal_buffer = NULL; + size_t brotli_size; + size_t brotli_limit = 0; + size_t brotli_offset = brotli_limit; + size_t normal_size; + size_t normal_limit = 0; + + /* Push the data down the stream + */ + targetClass = *(sink->isa); /* Copy pointers to procedures */ + + /* read and inflate brotli'd file, and push binary down sink + */ + HTReadProgress(bytes = 0, (off_t) 0); + /* + * first, read all of the brotli'd file into memory, to work with the + * library's limitations. + */ + for (;;) { + size_t input_chunk = INPUT_BUFFER_SIZE; + + brotli_offset = brotli_limit; + brotli_limit += input_chunk; + brotli_buffer = realloc(brotli_buffer, brotli_limit); + if (brotli_buffer == NULL) + outofmem(__FILE__, THIS_FUNC); + status = (int) fread(brotli_buffer + brotli_offset, sizeof(char), + input_chunk, brfp); + + if (status <= 0) { /* EOF or error */ + if (status == 0) { + rv = HT_LOADED; + break; + } + CTRACE((tfp, THIS_FUNC ": Read error, fread returns %d\n", status)); + if (bytes) { + if (!feof(brfp)) + rv = HT_PARTIAL_CONTENT; + } else { + rv = -1; + } + break; + } + bytes += status; + } + + /* + * next, unless we encountered an error (and have no data), try + * decompressing with increasing output buffer sizes until the brotli + * library succeeds. + */ + if (bytes > 0) { + do { + if (normal_limit == 0) + normal_limit = (10 * brotli_limit) + INPUT_BUFFER_SIZE; + else + normal_limit *= 2; + normal_buffer = realloc(normal_buffer, normal_limit); + if (normal_buffer == NULL) + outofmem(__FILE__, THIS_FUNC); + brotli_size = (size_t) bytes; + normal_size = normal_limit; + status2 = BrotliDecoderDecompress(brotli_size, + (uint8_t *) brotli_buffer, + &normal_size, + (uint8_t *) normal_buffer); + /* + * brotli library should return + * BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT, + * but actually returns + * BROTLI_DECODER_RESULT_ERROR + * + * Accommodate possible improvements... + */ + } while (status2 != BROTLI_DECODER_RESULT_SUCCESS); + } + + /* + * finally, pump that data into the output stream. + */ + if (status2 == BROTLI_DECODER_RESULT_SUCCESS) { + CTRACE((tfp, THIS_FUNC ": decompressed %ld -> %ld (1:%.1f)\n", + brotli_size, normal_size, + (double) normal_size / (double) brotli_size)); + (*targetClass.put_block) (sink, normal_buffer, (int) normal_size); + bytes += status; + HTReadProgress(bytes, (off_t) -1); + HTDisplayPartial(); + + if (HTCheckForInterrupt()) { + _HTProgress(TRANSFER_INTERRUPTED); + rv = HT_INTERRUPTED; + } + } + free(brotli_buffer); + free(normal_buffer); + + /* next bufferload */ + HTFinishDisplayPartial(); + return rv; +#undef THIS_FUNC +} +#endif /* USE_BZLIB */ + +/* Push data from a socket down a stream STRIPPING CR + * -------------------------------------------------- + * + * This routine is responsible for creating and PRESENTING any + * graphic (or other) objects described by the socket. + * + * The file number given is assumed to be a TELNET stream ie containing + * CRLF at the end of lines which need to be stripped to LF for unix + * when the format is textual. + * + */ +void HTCopyNoCR(HTParentAnchor *anchor GCC_UNUSED, + int file_number, + HTStream *sink) +{ + HTStreamClass targetClass; + int character; + + /* Push the data, ignoring CRLF, down the stream + */ + targetClass = *(sink->isa); /* Copy pointers to procedures */ + + /* + * Push text from telnet socket down sink + * + * @@@@@ To push strings could be faster? (especially is we cheat and + * don't ignore CR! :-} + */ + HTInitInput(file_number); + for (;;) { + character = HTGetCharacter(); + if (character == EOF) + break; + (*targetClass.put_character) (sink, (char) character); + } +} + +/* Parse a socket given format and file number + * + * This routine is responsible for creating and PRESENTING any + * graphic (or other) objects described by the file. + * + * The file number given is assumed to be a TELNET stream ie containing + * CRLF at the end of lines which need to be stripped to LF for unix + * when the format is textual. + * + * State of socket and target stream on entry: + * socket (file_number) assumed open, + * target (sink) usually NULL (will call stream stack). + * + * Return values: + * HT_INTERRUPTED Interruption or error after some data received. + * -501 Stream stack failed (cannot present or convert). + * -2 Unexpected disconnect before any data received. + * -1 Stream stack failed (cannot present or convert), or + * Interruption or error before any data received, or + * (UNIX) other read error before any data received, or + * download cancelled. + * HT_LOADED Normal close of socket (end of file indication + * received), or + * unexpected disconnect after some data received, or + * other read error after some data received, or + * (not UNIX) other read error before any data received. + * + * State of socket and target stream on return depends on return value: + * HT_INTERRUPTED socket still open, target aborted. + * -501 socket still open, target stream NULL. + * -2 socket still open, target freed. + * -1 socket still open, target stream aborted or NULL. + * otherwise socket closed, target stream freed. + */ +int HTParseSocket(HTFormat rep_in, + HTFormat format_out, + HTParentAnchor *anchor, + int file_number, + HTStream *sink) +{ + HTStream *stream; + HTStreamClass targetClass; + int rv; + + stream = HTStreamStack(rep_in, format_out, sink, anchor); + + if (!stream) { + char *buffer = 0; + + if (LYCancelDownload) { + LYCancelDownload = FALSE; + return -1; + } + HTSprintf0(&buffer, CANNOT_CONVERT_I_TO_O, + HTAtom_name(rep_in), HTAtom_name(format_out)); + CTRACE((tfp, "HTFormat: %s\n", buffer)); + rv = HTLoadError(sink, 501, buffer); /* returns -501 */ + FREE(buffer); + } else { + /* + * Push the data, don't worry about CRLF we can strip them later. + */ + targetClass = *(stream->isa); /* Copy pointers to procedures */ + rv = HTCopy(anchor, file_number, NULL, stream); + if (rv != -1 && rv != HT_INTERRUPTED) + (*targetClass._free) (stream); + } + return rv; + /* Originally: full: HT_LOADED; partial: HT_INTERRUPTED; no bytes: -1 */ +} + +/* Parse a file given format and file pointer + * + * This routine is responsible for creating and PRESENTING any + * graphic (or other) objects described by the file. + * + * The file number given is assumed to be a TELNET stream ie containing + * CRLF at the end of lines which need to be stripped to \n for unix + * when the format is textual. + * + * State of file and target stream on entry: + * FILE* (fp) assumed open, + * target (sink) usually NULL (will call stream stack). + * + * Return values: + * -501 Stream stack failed (cannot present or convert). + * -1 Download cancelled. + * HT_NO_DATA Error before any data read. + * HT_PARTIAL_CONTENT Interruption or error after some data read. + * HT_LOADED Normal end of file indication on reading. + * + * State of file and target stream on return: + * always fp still open; target freed, aborted, or NULL. + */ +int HTParseFile(HTFormat rep_in, + HTFormat format_out, + HTParentAnchor *anchor, + FILE *fp, + HTStream *sink) +{ + HTStream *stream; + HTStreamClass targetClass; + int rv; + int result; + + if (fp == NULL) { + result = HT_LOADED; + } else { + stream = HTStreamStack(rep_in, format_out, sink, anchor); + + if (!stream || !stream->isa) { + char *buffer = 0; + + if (LYCancelDownload) { + LYCancelDownload = FALSE; + result = -1; + } else { + HTSprintf0(&buffer, CANNOT_CONVERT_I_TO_O, + HTAtom_name(rep_in), HTAtom_name(format_out)); + CTRACE((tfp, "HTFormat(in HTParseFile): %s\n", buffer)); + rv = HTLoadError(sink, 501, buffer); + FREE(buffer); + result = rv; + } + } else { + + /* + * Push the data down the stream + * + * @@ Bug: This decision ought to be made based on "encoding" + * rather than on content-type. @@@ When we handle encoding. The + * current method smells anyway. + */ + targetClass = *(stream->isa); /* Copy pointers to procedures */ + rv = HTFileCopy(fp, stream); + if (rv == -1 || rv == HT_INTERRUPTED) { + (*targetClass._abort) (stream, NULL); + } else { + (*targetClass._free) (stream); + } + + if (rv == -1) { + result = HT_NO_DATA; + } else if (rv == HT_INTERRUPTED || (rv > 0 && rv != HT_LOADED)) { + result = HT_PARTIAL_CONTENT; + } else { + result = HT_LOADED; + } + } + } + return result; +} + +#ifdef USE_SOURCE_CACHE +/* Parse a document in memory given format and memory block pointer + * + * This routine is responsible for creating and PRESENTING any + * graphic (or other) objects described by the file. + * + * State of memory and target stream on entry: + * HTChunk* (chunk) assumed valid, + * target (sink) usually NULL (will call stream stack). + * + * Return values: + * -501 Stream stack failed (cannot present or convert). + * HT_LOADED All data sent. + * + * State of memory and target stream on return: + * always chunk unchanged; target freed, aborted, or NULL. + */ +int HTParseMem(HTFormat rep_in, + HTFormat format_out, + HTParentAnchor *anchor, + HTChunk *chunk, + HTStream *sink) +{ + HTStream *stream; + HTStreamClass targetClass; + int rv; + int result; + + stream = HTStreamStack(rep_in, format_out, sink, anchor); + if (!stream || !stream->isa) { + char *buffer = 0; + + HTSprintf0(&buffer, CANNOT_CONVERT_I_TO_O, + HTAtom_name(rep_in), HTAtom_name(format_out)); + CTRACE((tfp, "HTFormat(in HTParseMem): %s\n", buffer)); + rv = HTLoadError(sink, 501, buffer); + FREE(buffer); + result = rv; + } else { + + /* Push the data down the stream + */ + targetClass = *(stream->isa); + (void) HTMemCopy(chunk, stream); + (*targetClass._free) (stream); + result = HT_LOADED; + } + return result; +} +#endif + +#ifdef USE_ZLIB +static int HTCloseGzFile(gzFile gzfp) +{ + int gzres; + + if (gzfp == NULL) + return 0; + gzres = gzclose(gzfp); + if (TRACE) { + if (gzres == Z_ERRNO) { + perror("gzclose "); + } else if (gzres != Z_OK) { + CTRACE((tfp, "gzclose : error number %d\n", gzres)); + } + } + return (gzres); +} + +/* HTParseGzFile + * + * State of file and target stream on entry: + * gzFile (gzfp) assumed open, + * target (sink) usually NULL (will call stream stack). + * + * Return values: + * -501 Stream stack failed (cannot present or convert). + * -1 Download cancelled. + * HT_NO_DATA Error before any data read. + * HT_PARTIAL_CONTENT Interruption or error after some data read. + * HT_LOADED Normal end of file indication on reading. + * + * State of file and target stream on return: + * always gzfp closed; target freed, aborted, or NULL. + */ +int HTParseGzFile(HTFormat rep_in, + HTFormat format_out, + HTParentAnchor *anchor, + gzFile gzfp, + HTStream *sink) +{ + HTStream *stream; + HTStreamClass targetClass; + int rv; + int result; + + stream = HTStreamStack(rep_in, format_out, sink, anchor); + + if (!stream || !stream->isa) { + char *buffer = 0; + + HTCloseGzFile(gzfp); + if (LYCancelDownload) { + LYCancelDownload = FALSE; + result = -1; + } else { + HTSprintf0(&buffer, CANNOT_CONVERT_I_TO_O, + HTAtom_name(rep_in), HTAtom_name(format_out)); + CTRACE((tfp, "HTFormat(in HTParseGzFile): %s\n", buffer)); + rv = HTLoadError(sink, 501, buffer); + FREE(buffer); + result = rv; + } + } else { + + /* + * Push the data down the stream + * + * @@ Bug: This decision ought to be made based on "encoding" rather than + * on content-type. @@@ When we handle encoding. The current method + * smells anyway. + */ + targetClass = *(stream->isa); /* Copy pointers to procedures */ + rv = HTGzFileCopy(gzfp, stream); + if (rv == -1 || rv == HT_INTERRUPTED) { + (*targetClass._abort) (stream, NULL); + } else { + (*targetClass._free) (stream); + } + + HTCloseGzFile(gzfp); + if (rv == -1) { + result = HT_NO_DATA; + } else if (rv == HT_INTERRUPTED || (rv > 0 && rv != HT_LOADED)) { + result = HT_PARTIAL_CONTENT; + } else { + result = HT_LOADED; + } + } + return result; +} + +/* HTParseZzFile + * + * State of file and target stream on entry: + * FILE (zzfp) assumed open, + * target (sink) usually NULL (will call stream stack). + * + * Return values: + * -501 Stream stack failed (cannot present or convert). + * -1 Download cancelled. + * HT_NO_DATA Error before any data read. + * HT_PARTIAL_CONTENT Interruption or error after some data read. + * HT_LOADED Normal end of file indication on reading. + * + * State of file and target stream on return: + * always zzfp closed; target freed, aborted, or NULL. + */ +int HTParseZzFile(HTFormat rep_in, + HTFormat format_out, + HTParentAnchor *anchor, + FILE *zzfp, + HTStream *sink) +{ + HTStream *stream; + HTStreamClass targetClass; + int rv; + int result; + + stream = HTStreamStack(rep_in, format_out, sink, anchor); + + if (!stream || !stream->isa) { + char *buffer = 0; + + fclose(zzfp); + if (LYCancelDownload) { + LYCancelDownload = FALSE; + result = -1; + } else { + HTSprintf0(&buffer, CANNOT_CONVERT_I_TO_O, + HTAtom_name(rep_in), HTAtom_name(format_out)); + CTRACE((tfp, "HTFormat(in HTParseGzFile): %s\n", buffer)); + rv = HTLoadError(sink, 501, buffer); + FREE(buffer); + result = rv; + } + } else { + + /* + * Push the data down the stream + * + * @@ Bug: This decision ought to be made based on "encoding" rather than + * on content-type. @@@ When we handle encoding. The current method + * smells anyway. + */ + targetClass = *(stream->isa); /* Copy pointers to procedures */ + rv = HTZzFileCopy(zzfp, stream); + if (rv == -1 || rv == HT_INTERRUPTED) { + (*targetClass._abort) (stream, NULL); + } else { + (*targetClass._free) (stream); + } + + fclose(zzfp); + if (rv == -1) { + result = HT_NO_DATA; + } else if (rv == HT_INTERRUPTED || (rv > 0 && rv != HT_LOADED)) { + result = HT_PARTIAL_CONTENT; + } else { + result = HT_LOADED; + } + } + return result; +} +#endif /* USE_ZLIB */ + +#ifdef USE_BZLIB +static void HTCloseBzFile(BZFILE * bzfp) +{ + if (bzfp) + BZ2_bzclose(bzfp); +} + +/* HTParseBzFile + * + * State of file and target stream on entry: + * bzFile (bzfp) assumed open, + * target (sink) usually NULL (will call stream stack). + * + * Return values: + * -501 Stream stack failed (cannot present or convert). + * -1 Download cancelled. + * HT_NO_DATA Error before any data read. + * HT_PARTIAL_CONTENT Interruption or error after some data read. + * HT_LOADED Normal end of file indication on reading. + * + * State of file and target stream on return: + * always bzfp closed; target freed, aborted, or NULL. + */ +int HTParseBzFile(HTFormat rep_in, + HTFormat format_out, + HTParentAnchor *anchor, + BZFILE * bzfp, + HTStream *sink) +{ + HTStream *stream; + HTStreamClass targetClass; + int rv; + int result; + + stream = HTStreamStack(rep_in, format_out, sink, anchor); + + if (!stream || !stream->isa) { + char *buffer = 0; + + HTCloseBzFile(bzfp); + if (LYCancelDownload) { + LYCancelDownload = FALSE; + result = -1; + } else { + HTSprintf0(&buffer, CANNOT_CONVERT_I_TO_O, + HTAtom_name(rep_in), HTAtom_name(format_out)); + CTRACE((tfp, "HTFormat(in HTParseBzFile): %s\n", buffer)); + rv = HTLoadError(sink, 501, buffer); + FREE(buffer); + result = rv; + } + } else { + + /* + * Push the data down the stream + * + * @@ Bug: This decision ought to be made based on "encoding" rather than + * on content-type. @@@ When we handle encoding. The current method + * smells anyway. + */ + targetClass = *(stream->isa); /* Copy pointers to procedures */ + rv = HTBzFileCopy(bzfp, stream); + if (rv == -1 || rv == HT_INTERRUPTED) { + (*targetClass._abort) (stream, NULL); + } else { + (*targetClass._free) (stream); + } + + HTCloseBzFile(bzfp); + if (rv == -1) { + result = HT_NO_DATA; + } else if (rv == HT_INTERRUPTED || (rv > 0 && rv != HT_LOADED)) { + result = HT_PARTIAL_CONTENT; + } else { + result = HT_LOADED; + } + } + return result; +} +#endif /* USE_BZLIB */ + +#ifdef USE_BROTLI +/* HTParseBrFile + * + * State of file and target stream on entry: + * FILE* (brfp) assumed open, + * target (sink) usually NULL (will call stream stack). + * + * Return values: + * -501 Stream stack failed (cannot present or convert). + * -1 Download cancelled. + * HT_NO_DATA Error before any data read. + * HT_PARTIAL_CONTENT Interruption or error after some data read. + * HT_LOADED Normal end of file indication on reading. + * + * State of file and target stream on return: + * always brfp closed; target freed, aborted, or NULL. + */ +int HTParseBrFile(HTFormat rep_in, + HTFormat format_out, + HTParentAnchor *anchor, + FILE *brfp, + HTStream *sink) +{ +#undef THIS_FUNC +#define THIS_FUNC "HTParseBrFile" + HTStream *stream; + HTStreamClass targetClass; + int rv; + int result; + + stream = HTStreamStack(rep_in, format_out, sink, anchor); + + if (!stream || !stream->isa) { + char *buffer = 0; + + fclose(brfp); + if (LYCancelDownload) { + LYCancelDownload = FALSE; + result = -1; + } else { + HTSprintf0(&buffer, CANNOT_CONVERT_I_TO_O, + HTAtom_name(rep_in), HTAtom_name(format_out)); + CTRACE((tfp, "HTFormat(in " THIS_FUNC "): %s\n", buffer)); + rv = HTLoadError(sink, 501, buffer); + FREE(buffer); + result = rv; + } + } else { + + /* + * Push the data down the stream + * + * @@ Bug: This decision ought to be made based on "encoding" rather than + * on content-type. @@@ When we handle encoding. The current method + * smells anyway. + */ + targetClass = *(stream->isa); /* Copy pointers to procedures */ + rv = HTBrFileCopy(brfp, stream); + if (rv == -1 || rv == HT_INTERRUPTED) { + (*targetClass._abort) (stream, NULL); + } else { + (*targetClass._free) (stream); + } + + fclose(brfp); + if (rv == -1) { + result = HT_NO_DATA; + } else if (rv == HT_INTERRUPTED || (rv > 0 && rv != HT_LOADED)) { + result = HT_PARTIAL_CONTENT; + } else { + result = HT_LOADED; + } + } + return result; +#undef THIS_FUNC +} +#endif /* USE_BROTLI */ + +/* Converter stream: Network Telnet to internal character text + * ----------------------------------------------------------- + * + * The input is assumed to be in ASCII, with lines delimited + * by (13,10) pairs, These pairs are converted into (CR,LF) + * pairs in the local representation. The (CR,LF) sequence + * when found is changed to a '\n' character, the internal + * C representation of a new line. + */ + +static void NetToText_put_character(HTStream *me, int net_char) +{ + char c = (char) FROMASCII(net_char); + + if (me->had_cr) { + if (c == LF) { + me->sink->isa->put_character(me->sink, '\n'); /* Newline */ + me->had_cr = NO; + return; + } else { + me->sink->isa->put_character(me->sink, CR); /* leftover */ + } + } + me->had_cr = (BOOL) (c == CR); + if (!me->had_cr) + me->sink->isa->put_character(me->sink, c); /* normal */ +} + +static void NetToText_put_string(HTStream *me, const char *s) +{ + const char *p; + + for (p = s; *p; p++) + NetToText_put_character(me, *p); +} + +static void NetToText_put_block(HTStream *me, const char *s, int l) +{ + const char *p; + + for (p = s; p < (s + l); p++) + NetToText_put_character(me, *p); +} + +static void NetToText_free(HTStream *me) +{ + (me->sink->isa->_free) (me->sink); /* Close rest of pipe */ + FREE(me); +} + +static void NetToText_abort(HTStream *me, HTError e) +{ + me->sink->isa->_abort(me->sink, e); /* Abort rest of pipe */ + FREE(me); +} + +/* The class structure +*/ +static HTStreamClass NetToTextClass = +{ + "NetToText", + NetToText_free, + NetToText_abort, + NetToText_put_character, + NetToText_put_string, + NetToText_put_block +}; + +/* The creation method +*/ +HTStream *HTNetToText(HTStream *sink) +{ + HTStream *me = typecalloc(HTStream); + + if (me == NULL) + outofmem(__FILE__, "NetToText"); + + me->isa = &NetToTextClass; + + me->had_cr = NO; + me->sink = sink; + return me; +} + +static HTStream HTBaseStreamInstance; /* Made static */ + +/* + * ERROR STREAM + * ------------ + * There is only one error stream shared by anyone who wants a + * generic error returned from all stream methods. + */ +static void HTErrorStream_put_character(HTStream *me GCC_UNUSED, int c GCC_UNUSED) +{ + LYCancelDownload = TRUE; +} + +static void HTErrorStream_put_string(HTStream *me GCC_UNUSED, const char *s) +{ + if (s && *s) + LYCancelDownload = TRUE; +} + +static void HTErrorStream_write(HTStream *me GCC_UNUSED, const char *s, int l) +{ + if (l && s) + LYCancelDownload = TRUE; +} + +static void HTErrorStream_free(HTStream *me GCC_UNUSED) +{ + return; +} + +static void HTErrorStream_abort(HTStream *me GCC_UNUSED, HTError e GCC_UNUSED) +{ + return; +} + +static const HTStreamClass HTErrorStreamClass = +{ + "ErrorStream", + HTErrorStream_free, + HTErrorStream_abort, + HTErrorStream_put_character, + HTErrorStream_put_string, + HTErrorStream_write +}; + +HTStream *HTErrorStream(void) +{ + CTRACE((tfp, "ErrorStream. Created\n")); + HTBaseStreamInstance.isa = &HTErrorStreamClass; /* The rest is random */ + return &HTBaseStreamInstance; +} diff --git a/WWW/Library/Implementation/HTFormat.h b/WWW/Library/Implementation/HTFormat.h new file mode 100644 index 0000000..835daef --- /dev/null +++ b/WWW/Library/Implementation/HTFormat.h @@ -0,0 +1,588 @@ +/* + * $LynxId: HTFormat.h,v 1.42 2022/04/01 07:54:14 tom Exp $ + * + * HTFormat: The format manager in the WWW Library + * MANAGE DIFFERENT DOCUMENT FORMATS + * + * Here we describe the functions of the HTFormat module which handles conversion between + * different data representations. (In MIME parlance, a representation is known as a + * content-type. In WWW the term "format" is often used as it is shorter). + * + * This module is implemented by HTFormat.c. This hypertext document is used to generate + * the HTFormat.h include file. Part of the WWW library. + */ +#ifndef HTFORMAT_H +#define HTFORMAT_H + +#include +#include +#include +#include + +#ifdef USE_SOURCE_CACHE +#include +#endif + +#ifdef USE_BZLIB +#include +#endif + +#ifdef USE_ZLIB +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif +/* + + These macros (which used to be constants) define some basic internally + referenced representations. The www/xxx ones are of course not MIME + standard. + + www/source is an output format which leaves the input untouched. It is + useful for diagnostics, and for users who want to see the original, whatever + it is. + + */ +/* Internal ones */ +#define STR_SOURCE "www/source" + extern HTAtom *WWW_SOURCE; /* calculated once, heavy used */ + +/* + + www/present represents the user's perception of the document. If you + convert to www/present, you present the material to the user. + + */ +#define STR_PRESENT "www/present" +#define WWW_PRESENT HTAtom_for(STR_PRESENT) /* The user's perception */ + +#define WWW_DEBUG HTAtom_for("www/debug") +/* + + WWW_DEBUG represents the user's perception of debug information, for example + sent as a HTML document in a HTTP redirection message. + + */ + +/* + + The message/rfc822 format means a MIME message or a plain text message with + no MIME header. This is what is returned by an HTTP server. + + */ +#define WWW_MIME HTAtom_for("www/mime") /* A MIME message */ + +/* + For parsing only the header. - kw + */ +#define WWW_MIME_HEAD HTAtom_for("message/x-rfc822-head") + +/* + + www/print is like www/present except it represents a printed copy. + + */ +#define WWW_PRINT HTAtom_for("www/print") /* A printed copy */ + +/* + + www/unknown is a really unknown type. Some default action is appropriate. + + */ +#define WWW_UNKNOWN HTAtom_for("www/unknown") + +#ifdef DIRED_SUPPORT +/* + www/dired signals directory edit mode. +*/ +#define WWW_DIRED HTAtom_for("www/dired") +#endif + +/* + * Miscellaneous internal MIME types. + */ +#define STR_DOWNLOAD "www/download" +#define WWW_DOWNLOAD HTAtom_for(STR_DOWNLOAD) + +#define STR_DUMP "www/dump" +#define WWW_DUMP HTAtom_for(STR_DUMP) + +/* + + These are regular MIME types. HTML is assumed to be added by the W3 code. + application/octet-stream was mistakenly application/binary in earlier libwww + versions (pre 2.11). + + */ +#define STR_BINARY "application/octet-stream" +#define STR_PLAINTEXT "text/plain" +#define STR_HTML "text/html" + +#define WWW_BINARY HTAtom_for(STR_BINARY) +#define WWW_PLAINTEXT HTAtom_for(STR_PLAINTEXT) +#define WWW_HTML HTAtom_for(STR_HTML) + +#define WWW_POSTSCRIPT HTAtom_for("application/postscript") +#define WWW_RICHTEXT HTAtom_for("application/rtf") +#define WWW_AUDIO HTAtom_for("audio/basic") + + typedef HTAtom *HTEncoding; + +/* + * The following are values for the MIME types: + */ +#define WWW_ENC_7BIT HTAtom_for("7bit") +#define WWW_ENC_8BIT HTAtom_for("8bit") +#define WWW_ENC_BINARY HTAtom_for("binary") + +/* + * We also add + */ +#define WWW_ENC_COMPRESS HTAtom_for("compress") + +/* + * Does a string designate a real encoding, or is it just + * a "dummy" as for example 7bit, 8bit, and binary? + */ +#define IsUnityEncStr(senc) \ + ((senc)==NULL || *(senc)=='\0' || !strcmp(senc,"identity") ||\ + !strcmp(senc,"8bit") || !strcmp(senc,"binary") || !strcmp(senc,"7bit")) + +#define IsUnityEnc(enc) \ + ((enc)==NULL || (enc)==HTAtom_for("identity") ||\ + (enc)==WWW_ENC_8BIT || (enc)==WWW_ENC_BINARY || (enc)==WWW_ENC_7BIT) + +/* + +The HTPresentation and HTConverter types + + This HTPresentation structure represents a possible conversion algorithm + from one format to another. It includes a pointer to a conversion routine. + The conversion routine returns a stream to which data should be fed. See + also HTStreamStack which scans the list of registered converters and calls + one. See the initialisation module for a list of conversion routines. + + */ + typedef struct _HTPresentation HTPresentation; + + typedef HTStream *HTConverter (HTPresentation *pres, + HTParentAnchor *anchor, + HTStream *sink); + + struct _HTPresentation { + HTAtom *rep; /* representation name atomized */ + HTAtom *rep_out; /* resulting representation */ + HTConverter *converter; /* routine to gen the stream stack */ + char *command; /* MIME-format command string */ + char *testcommand; /* MIME-format test string */ + float quality; /* Between 0 (bad) and 1 (good) */ + float secs; + float secs_per_byte; + off_t maxbytes; + BOOL get_accept; /* list in "Accept:" for GET */ + int accept_opt; /* matches against LYAcceptMedia */ + }; + +/* + + The list of presentations is kept by this module. It is also scanned by + modules which want to know the set of formats supported. for example. + + */ + extern HTList *HTPresentations; + +/* + + The default presentation is used when no other is appropriate + + */ + extern HTPresentation *default_presentation; + +/* + * Options used for "Content-Type" string + */ + typedef enum { + contentBINARY = 0 + ,contentTEXT + ,contentHTML + } ContentType; + +/* + * Options used for "Accept:" string + */ + typedef enum { + /* make the components powers of two so we can add them */ + mediaINT = 1 /* internal types predefined in HTInit.c */ + ,mediaEXT = 2 /* external types predefined in HTInit.c */ + ,mediaCFG = 4 /* types, e.g., viewers, from lynx.cfg */ + ,mediaUSR = 8 /* user's mime-types, etc. */ + ,mediaSYS = 16 /* system's mime-types, etc. */ + /* these are useful flavors for the options menu */ + ,mediaOpt1 = mediaINT + ,mediaOpt2 = mediaINT + mediaCFG + ,mediaOpt3 = mediaINT + mediaCFG + mediaUSR + ,mediaOpt4 = mediaINT + mediaCFG + mediaUSR + mediaSYS + /* this is the flavor from pre-2.8.6 */ + ,mediaALL = mediaINT + mediaEXT + mediaCFG + mediaUSR + mediaSYS + } AcceptMedia; + +/* + * Options used for "Accept-Encoding:" string + */ + typedef enum { + encodingNONE = 0 + ,encodingGZIP = 1 + ,encodingDEFLATE = 2 + ,encodingCOMPRESS = 4 + ,encodingBZIP2 = 8 + ,encodingBROTLI = 16 + ,encodingALL = (encodingGZIP + + encodingDEFLATE + + encodingCOMPRESS + + encodingBZIP2 + + encodingBROTLI) + } AcceptEncoding; + +/* + +HTSetPresentation: Register a system command to present a format + + ON ENTRY, + + rep is the MIME - style format name + + command is the MAILCAP - style command template + + testcommand is the MAILCAP - style testcommand template + + quality A degradation faction 0..1.0 + + secs A limit on the time user will wait (0.0 for infinity) + secs_per_byte + + maxbytes A limit on the length acceptable as input (0 infinite) + + media Used in filtering presentation types for "Accept:" + + */ + extern void HTSetPresentation(const char *representation, + const char *command, + const char *testcommand, + double quality, + double secs, + double secs_per_byte, + long int maxbytes, + AcceptMedia media + ); + +/* + +HTSetConversion: Register a conversion routine + + ON ENTRY, + + rep_in is the content-type input + + rep_out is the resulting content-type + + converter is the routine to make the stream to do it + + */ + + extern void HTSetConversion(const char *rep_in, + const char *rep_out, + HTConverter *converter, + double quality, + double secs, + double secs_per_byte, + long int maxbytes, + AcceptMedia media + ); + +/* + +HTStreamStack: Create a stack of streams + + This is the routine which actually sets up the conversion. It currently + checks only for direct conversions, but multi-stage conversions are forseen. + It takes a stream into which the output should be sent in the final format, + builds the conversion stack, and returns a stream into which the data in the + input format should be fed. The anchor is passed because hypertxet objects + load information into the anchor object which represents them. + + */ + extern HTStream *HTStreamStack(HTFormat format_in, + HTFormat format_out, + HTStream *stream_out, + HTParentAnchor *anchor); + +/* +HTReorderPresentation: put presentation near head of list + + Look up a presentation (exact match only) and, if found, reorder it to the + start of the HTPresentations list. - kw + */ + + extern void HTReorderPresentation(HTFormat format_in, + HTFormat format_out); + +/* + * Setup 'get_accept' flag to denote presentations that are not redundant, + * and will be listed in "Accept:" header. + */ + extern void HTFilterPresentations(void); + +/* + +HTStackValue: Find the cost of a filter stack + + Must return the cost of the same stack which HTStreamStack would set up. + + ON ENTRY, + + format_in The format of the data to be converted + + format_out The format required + + initial_value The intrinsic "value" of the data before conversion on a scale + from 0 to 1 + + length The number of bytes expected in the input format + + */ + extern float HTStackValue(HTFormat format_in, + HTFormat rep_out, + double initial_value, + long int length); + +#define NO_VALUE_FOUND -1e20 /* returned if none found */ + +/* Display the page while transfer in progress + * ------------------------------------------- + * + * Repaint the page only when necessary. + * This is a traverse call for HText_pageDispaly() - it works!. + * + */ + extern void HTDisplayPartial(void); + + extern void HTFinishDisplayPartial(void); + +/* + +HTCopy: Copy a socket to a stream + + This is used by the protocol engines to send data down a stream, typically + one which has been generated by HTStreamStack. + + */ + extern int HTCopy(HTParentAnchor *anchor, + int file_number, + void *handle, + HTStream *sink); + +/* + +HTFileCopy: Copy a file to a stream + + This is used by the protocol engines to send data down a stream, typically + one which has been generated by HTStreamStack. It is currently called by + HTParseFile + + */ + extern int HTFileCopy(FILE *fp, + HTStream *sink); + +#ifdef USE_SOURCE_CACHE +/* + +HTMemCopy: Copy a memory chunk to a stream + + This is used by the protocol engines to send data down a stream, typically + one which has been generated by HTStreamStack. It is currently called by + HTParseMem + + */ + extern int HTMemCopy(HTChunk *chunk, + HTStream *sink); +#endif + +/* + +HTCopyNoCR: Copy a socket to a stream, stripping CR characters. + + It is slower than HTCopy . + + */ + + extern void HTCopyNoCR(HTParentAnchor *anchor, + int file_number, + HTStream *sink); + +/* + +Clear input buffer and set file number + + This routine and the one below provide simple character input from sockets. + (They are left over from the older architecture and may not be used very + much.) The existence of a common routine and buffer saves memory space in + small implementations. + + */ + extern void HTInitInput(int file_number); + +/* + +Get next character from buffer + + */ + extern int interrupted_in_htgetcharacter; + extern int HTGetCharacter(void); + +/* + +HTParseSocket: Parse a socket given its format + + This routine is called by protocol modules to load an object. uses + HTStreamStack and the copy routines above. Returns HT_LOADED if successful, + <0 if not. + + */ + extern int HTParseSocket(HTFormat format_in, + HTFormat format_out, + HTParentAnchor *anchor, + int file_number, + HTStream *sink); + +/* + +HTParseFile: Parse a File through a file pointer + + This routine is called by protocols modules to load an object. uses + HTStreamStack and HTFileCopy. Returns HT_LOADED if successful, can also + return HT_PARTIAL_CONTENT, HT_NO_DATA, or other <0 for failure. + + */ + extern int HTParseFile(HTFormat format_in, + HTFormat format_out, + HTParentAnchor *anchor, + FILE *fp, + HTStream *sink); + +#ifdef USE_SOURCE_CACHE +/* + +HTParseMem: Parse a document in memory + + This routine is called by protocols modules to load an object. uses + HTStreamStack and HTMemCopy. Returns HT_LOADED if successful, can also + return <0 for failure. + + */ + extern int HTParseMem(HTFormat format_in, + HTFormat format_out, + HTParentAnchor *anchor, + HTChunk *chunk, + HTStream *sink); +#endif + +#ifdef USE_ZLIB +/* +HTParseGzFile: Parse a gzip'ed File through a file pointer + + This routine is called by protocols modules to load an object. uses + HTStreamStack and HTGzFileCopy. Returns HT_LOADED if successful, can also + return HT_PARTIAL_CONTENT, HT_NO_DATA, or other <0 for failure. + */ + extern int HTParseGzFile(HTFormat format_in, + HTFormat format_out, + HTParentAnchor *anchor, + gzFile gzfp, + HTStream *sink); + +/* +HTParseZzFile: Parse a deflate'd File through a file pointer + + This routine is called by protocols modules to load an object. uses + HTStreamStack and HTZzFileCopy. Returns HT_LOADED if successful, can also + return HT_PARTIAL_CONTENT, HT_NO_DATA, or other <0 for failure. + */ + extern int HTParseZzFile(HTFormat format_in, + HTFormat format_out, + HTParentAnchor *anchor, + FILE *zzfp, + HTStream *sink); + +#endif /* USE_ZLIB */ + +#ifdef USE_BZLIB +/* +HTParseBzFile: Parse a bzip2'ed File through a file pointer + + This routine is called by protocols modules to load an object. uses + HTStreamStack and HTBzFileCopy. Returns HT_LOADED if successful, can also + return HT_PARTIAL_CONTENT, HT_NO_DATA, or other <0 for failure. + */ + extern int HTParseBzFile(HTFormat format_in, + HTFormat format_out, + HTParentAnchor *anchor, + BZFILE * bzfp, + HTStream *sink); + +#endif /* USE_BZLIB */ + +#ifdef USE_BROTLI +/* +HTParseBrFile: Parse a brotli'ed File through a file pointer + + This routine is called by protocols modules to load an object. uses + HTStreamStack and HTBrFileCopy. Returns HT_LOADED if successful, can also + return HT_PARTIAL_CONTENT, HT_NO_DATA, or other <0 for failure. + */ + extern int HTParseBrFile(HTFormat format_in, + HTFormat format_out, + HTParentAnchor *anchor, + FILE *brfp, + HTStream *sink); + +#endif /* USE_BROTLI */ + +/* + +HTNetToText: Convert Net ASCII to local representation + + This is a filter stream suitable for taking text from a socket and passing + it into a stream which expects text in the local C representation. It does + ASCII and newline conversion. As usual, pass its output stream to it when + creating it. + + */ + extern HTStream *HTNetToText(HTStream *sink); + +/* + +HTFormatInit: Set up default presentations and conversions + + These are defined in HTInit.c or HTSInit.c if these have been replaced. If + you don't call this routine, and you don't define any presentations, then + this routine will automatically be called the first time a conversion is + needed. However, if you explicitly add some conversions (eg using + HTLoadRules) then you may want also to explicitly call this to get the + defaults as well. + + */ + extern void HTFormatInit(void); + +/* + +Epilogue + + */ + extern BOOL HTOutputSource; /* Flag: shortcut parser */ + +#ifdef __cplusplus +} +#endif +#endif /* HTFORMAT_H */ diff --git a/WWW/Library/Implementation/HTGopher.c b/WWW/Library/Implementation/HTGopher.c new file mode 100644 index 0000000..0314179 --- /dev/null +++ b/WWW/Library/Implementation/HTGopher.c @@ -0,0 +1,2071 @@ +/* + * $LynxId: HTGopher.c,v 1.77 2022/04/01 00:18:09 tom Exp $ + * + * GOPHER ACCESS HTGopher.c + * ============= + * + * History: + * 26 Sep 90 Adapted from other accesses (News, HTTP) TBL + * 29 Nov 91 Downgraded to C, for portable implementation. + * 10 Mar 96 Foteos Macrides (macrides@sci.wfbr.edu). Added a + * form-based CSO/PH gateway. Can be invoked via a + * "cso://host[:port]/" or "gopher://host:105/2" + * URL. If a gopher URL is used with a query token + * ('?'), the old ISINDEX procedure will be used + * instead of the form-based gateway. + * 15 Mar 96 Foteos Macrides (macrides@sci.wfbr.edu). Pass + * port 79, gtype 0 gopher URLs to the finger + * gateway. + */ + +#define HTSTREAM_INTERNAL 1 + +#include /* Coding convention macros */ +#include /* For HTFileFormat() */ + +#ifndef DISABLE_GOPHER +#include +#include +#include +#include +#include + +/* + * Implements. + */ +#include + +#define GOPHER_PORT 70 /* See protocol spec */ +#define CSO_PORT 105 /* See protocol spec */ +#define BIG 1024 /* Bug */ +#define LINE_LENGTH 256 /* Bug */ + +/* + * Gopher entity types. + */ +#define GOPHER_TEXT '0' +#define GOPHER_MENU '1' +#define GOPHER_CSO '2' +#define GOPHER_ERROR '3' +#define GOPHER_MACBINHEX '4' +#define GOPHER_PCBINARY '5' +#define GOPHER_UUENCODED '6' +#define GOPHER_INDEX '7' +#define GOPHER_TELNET '8' +#define GOPHER_BINARY '9' +#define GOPHER_GIF 'g' +#define GOPHER_HTML 'h' /* HTML */ +#define GOPHER_CHTML 'H' /* HTML */ +#define GOPHER_SOUND 's' +#define GOPHER_WWW 'w' /* W3 address */ +#define GOPHER_IMAGE 'I' +#define GOPHER_TN3270 'T' +#define GOPHER_INFO 'i' +#define GOPHER_DUPLICATE '+' +#define GOPHER_PLUS_IMAGE ':' /* Addition from Gopher Plus */ +#define GOPHER_PLUS_MOVIE ';' +#define GOPHER_PLUS_SOUND '<' +#define GOPHER_PLUS_PDF 'P' + +#include + +/* + * Hypertext object building machinery. + */ +#include + +#include +#include +#include + +#define PUTC(c) (*targetClass.put_character)(target, c) +#define PUTS(s) (*targetClass.put_string)(target, s) +#define START(e) (*targetClass.start_element)(target, e, 0, 0, -1, 0) +#define END(e) (*targetClass.end_element)(target, e, 0) +#define FREE_TARGET (*targetClass._free)(target) + +#define NEXT_CHAR HTGetCharacter() + +/* + * Module-wide variables. + */ +static int s; /* Socket for gopher or CSO host */ + +struct _HTStructured { + const HTStructuredClass *isa; /* For gopher streams */ + /* ... */ +}; + +static HTStructured *target; /* the new gopher hypertext */ +static HTStructuredClass targetClass; /* Its action routines */ + +struct _HTStream { + HTStreamClass *isa; /* For form-based CSO gateway - FM */ +}; + +typedef struct _CSOfield_info { /* For form-based CSO gateway - FM */ + struct _CSOfield_info *next; + char *name; + char *attributes; + char *description; + int id; + int lookup; + int indexed; + int url; + int max_size; + int defreturn; + int explicit_return; + int reserved; + int gpublic; + char name_buf[16]; /* Avoid malloc if we can */ + char desc_buf[32]; /* Avoid malloc if we can */ + char attr_buf[80]; /* Avoid malloc if we can */ +} CSOfield_info; + +static CSOfield_info *CSOfields = NULL; /* For form-based CSO gateway - FM */ + +typedef struct _CSOformgen_context { /* For form-based CSO gateway - FM */ + const char *host; + const char *seek; + CSOfield_info *fld; + int port; + int cur_line; + int cur_off; + int rep_line; + int rep_off; + int public_override; + int field_select; +} CSOformgen_context; + +/* Matrix of allowed characters in filenames + * ========================================= + */ +static BOOL acceptable_html[256]; +static BOOL acceptable_file[256]; +static BOOL acceptable_inited = NO; + +static void init_acceptable(void) +{ + unsigned int i; + const char *good = + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./-_$"; + + for (i = 0; i < 256; i++) { + acceptable_html[i] = NO; + acceptable_file[i] = NO; + } + for (; *good; good++) { + acceptable_html[(unsigned int) *good] = YES; + acceptable_file[(unsigned int) *good] = YES; + } + for (good = ";?=#"; *good; ++good) { + acceptable_html[(unsigned int) *good] = YES; + } + acceptable_inited = YES; +} + +/* Decode one hex character + * ======================== + */ +static const char hex[17] = "0123456789abcdef"; + +static char from_hex(int c) +{ + return (char) ((c >= '0') && (c <= '9') ? c - '0' + : (c >= 'A') && (c <= 'F') ? c - 'A' + 10 + : (c >= 'a') && (c <= 'f') ? c - 'a' + 10 + : 0); +} + +/* Paste in an Anchor + * ================== + * + * The title of the destination is set, as there is no way + * of knowing what the title is when we arrive. + * + * On entry, + * HT is in append mode. + * text points to the text to be put into the file, 0 terminated. + * addr points to the hypertext reference address 0 terminated. + */ +BOOLEAN HT_Is_Gopher_URL = FALSE; + +static void write_anchor(const char *text, const char *addr) +{ + BOOL present[HTML_A_ATTRIBUTES]; + const char *value[HTML_A_ATTRIBUTES]; + + int i; + + for (i = 0; i < HTML_A_ATTRIBUTES; i++) + present[i] = 0; + present[HTML_A_HREF] = YES; + ((const char **) value)[HTML_A_HREF] = addr; + present[HTML_A_TITLE] = YES; + ((const char **) value)[HTML_A_TITLE] = text; + + CTRACE((tfp, "HTGopher: adding URL: %s\n", addr)); + + HT_Is_Gopher_URL = TRUE; /* tell HTML.c that this is a Gopher URL */ + (*targetClass.start_element) (target, HTML_A, present, + (const char **) value, -1, 0); + + PUTS(text); + END(HTML_A); +} + +/* Parse a Gopher Menu document + * ============================ + */ +static void parse_menu(const char *arg GCC_UNUSED, + HTParentAnchor *anAnchor) +{ + char gtype; + char this_type; + int ich; + char line[BIG]; + char *name = NULL, *selector = NULL; /* Gopher menu fields */ + char *host = NULL; + char *port; + char *p = line; + const char *title; + int bytes = 0; + int BytesReported = 0; + char buffer[128]; + BOOL *valid_chars; + +#define TAB '\t' +#define HEX_ESCAPE '%' + + START(HTML_HTML); + PUTC('\n'); + START(HTML_HEAD); + PUTC('\n'); + START(HTML_TITLE); + if ((title = HTAnchor_title(anAnchor))) + PUTS(title); + else + PUTS(GOPHER_MENU_TITLE); + END(HTML_TITLE); + PUTC('\n'); + END(HTML_HEAD); + PUTC('\n'); + + START(HTML_BODY); + PUTC('\n'); + START(HTML_H1); + if ((title = HTAnchor_title(anAnchor))) + PUTS(title); + else + PUTS(GOPHER_MENU_TITLE); + END(HTML_H1); + PUTC('\n'); + START(HTML_PRE); + PUTC('\n'); /* newline after HTML_PRE forces split-line */ + this_type = GOPHER_ERROR; + while ((ich = NEXT_CHAR) != EOF) { + + if (interrupted_in_htgetcharacter) { + CTRACE((tfp, + "HTGopher: Interrupted in HTGetCharacter, apparently.\n")); + goto end_html; + } + + if ((char) ich != LF) { + const char *ss = NULL; + + /* + * Help the -source output to look like the HTML equivalent of the + * Gopher menu. + */ + if (dump_output_immediately + && HTOutputFormat == WWW_DUMP) { + if (ich == '<') { + ss = "<"; + } else if (ich == '>') { + ss = ">"; + } else if (ich == '&') { + ss = "&"; + } + } + if (ss != NULL) { + if ((p + 5) < &line[BIG - 1]) { + while (*ss != '\0') { + *p++ = *ss++; + } + } + } else { + *p = (char) ich; /* Put character in line */ + if (p < &line[BIG - 1]) + p++; + } + + } else { + *p++ = '\0'; /* Terminate line */ + bytes += (int) (p - line); /* add size */ + p = line; /* Scan it to parse it */ + port = 0; /* Flag "not parsed" */ + CTRACE((tfp, "HTGopher: Menu item: %s\n", line)); + gtype = *p++; + + if (bytes > BytesReported + 1024) { + sprintf(buffer, TRANSFERRED_X_BYTES, bytes); + HTProgress(buffer); + BytesReported = bytes; + } + + /* Break on line with a dot by itself */ + if ((gtype == '.') && ((*p == '\r') || (*p == 0))) + break; + + if (gtype && *p) { + name = p; + selector = StrChr(name, TAB); + if (selector) { + *selector++ = '\0'; /* Terminate name */ + /* + * Gopher+ Type=0+ objects can be binary, and will + * have 9 or 5 beginning their selector. Make sure + * we don't trash the terminal by treating them as + * text. - FM + */ + if (gtype == GOPHER_TEXT && (*selector == GOPHER_BINARY || + *selector == GOPHER_PCBINARY)) + gtype = *selector; + host = StrChr(selector, TAB); + if (host) { + *host++ = '\0'; /* Terminate selector */ + port = StrChr(host, TAB); + if (port) { + char *junk; + + port[0] = ':'; /* delimit host a la W3 */ + junk = StrChr(port, TAB); + if (junk) + *junk = '\0'; /* Chop port */ + if ((port[1] == '0') && (!port[2])) + port[0] = '\0'; /* 0 means none */ + } /* no port */ + } /* host ok */ + } /* selector ok */ + } + /* gtype and name ok */ + /* Nameless files are a separator line */ + if (name != NULL && gtype == GOPHER_TEXT) { + int i = (int) strlen(name) - 1; + + while (name[i] == ' ' && i >= 0) + name[i--] = '\0'; + if (i < 0) + gtype = GOPHER_INFO; + } + + if (gtype == GOPHER_WWW) { /* Gopher pointer to W3 */ + PUTS("(HTML) "); + write_anchor(name, selector); + + } else if (gtype == GOPHER_INFO) { + /* Information or separator line */ + PUTS(" "); + PUTS(name); + + } else if (port && /* Other types need port */ + (gtype != GOPHER_DUPLICATE || + this_type != GOPHER_ERROR)) { + char *address = 0; + const char *format = *selector ? "%s//%s@%s/" : "%s//%s/"; + + if (gtype == GOPHER_TELNET) { + PUTS(" (TEL) "); + if (*selector == '/') + ++selector; + HTSprintf0(&address, format, STR_TELNET_URL, selector, host); + } else if (gtype == GOPHER_TN3270) { + PUTS("(3270) "); + if (*selector == '/') + ++selector; + HTSprintf0(&address, format, STR_TN3270_URL, selector, host); + } else { /* If parsed ok */ + char *r; + + switch (gtype) { + case GOPHER_TEXT: + PUTS("(FILE) "); + break; + case GOPHER_MENU: + PUTS(" (DIR) "); + break; + case GOPHER_DUPLICATE: + PUTS(" (+++) "); + break; + case GOPHER_CSO: + PUTS(" (CSO) "); + break; + case GOPHER_PCBINARY: + PUTS(" (BIN) "); + break; + case GOPHER_UUENCODED: + PUTS(" (UUE) "); + break; + case GOPHER_INDEX: + PUTS(" (?) "); + break; + case GOPHER_BINARY: + PUTS(" (BIN) "); + break; + case GOPHER_GIF: + case GOPHER_IMAGE: + case GOPHER_PLUS_IMAGE: + PUTS(" (IMG) "); + break; + case GOPHER_SOUND: + case GOPHER_PLUS_SOUND: + PUTS(" (SND) "); + break; + case GOPHER_MACBINHEX: + PUTS(" (HQX) "); + break; + case GOPHER_HTML: + case GOPHER_CHTML: + PUTS("(HTML) "); + break; + case 'm': + PUTS("(MIME) "); + break; + case GOPHER_PLUS_MOVIE: + PUTS(" (MOV) "); + break; + case GOPHER_PLUS_PDF: + PUTS(" (PDF) "); + break; + default: + PUTS("(UNKN) "); + break; + } + + if (gtype != GOPHER_DUPLICATE) + this_type = gtype; + + HTSprintf0(&address, "//%s/%c", host, this_type); + if (gtype == GOPHER_HTML) { + valid_chars = acceptable_html; + if (*selector == '/') + ++selector; + } else { + valid_chars = acceptable_file; + } + + for (r = selector; *r; r++) { /* Encode selector string */ + if (valid_chars[UCH(*r)]) { + HTSprintf(&address, "%c", *r); + } else { + HTSprintf(&address, "%c%c%c", + HEX_ESCAPE, /* Means hex coming */ + hex[(TOASCII(*r)) >> 4], + hex[(TOASCII(*r)) & 15]); + } + } + } + /* Error response from Gopher doesn't deserve to + be a hyperlink. */ + if (strcmp(address, "gopher://error.host:1/0")) + write_anchor(name, address); + else + PUTS(name); + FREE(address); + } else { /* parse error */ + CTRACE((tfp, "HTGopher: Bad menu item (type %d, port %s).\n", + gtype, NonNull(port))); + PUTS(line); + + } /* parse error */ + + PUTC('\n'); + p = line; /* Start again at beginning of line */ + + } /* if end of line */ + + } /* Loop over characters */ + + end_html: + END(HTML_PRE); + PUTC('\n'); + END(HTML_BODY); + PUTC('\n'); + END(HTML_HTML); + PUTC('\n'); + FREE_TARGET; + + return; +} + +/* Parse a Gopher CSO document from an ISINDEX query. + * ================================================== + * + * Accepts an open socket to a CSO server waiting to send us + * data and puts it on the screen in a reasonable manner. + * + * Perhaps this data can be automatically linked to some + * other source as well??? + * + * Taken from hacking by Lou Montulli@ukanaix.cc.ukans.edu + * on XMosaic-1.1, and put on libwww 2.11 by Arthur Secret, + * secret@dxcern.cern.ch . + */ +static void parse_cso(const char *arg, + HTParentAnchor *anAnchor) +{ + int ich; + char line[BIG]; + char *p = line; + char *first_colon, *second_colon, last_char = '\0'; + const char *title; + + START(HTML_HEAD); + PUTC('\n'); + START(HTML_TITLE); + if ((title = HTAnchor_title(anAnchor))) + PUTS(title); + else + PUTS(GOPHER_CSO_SEARCH_RESULTS); + END(HTML_TITLE); + PUTC('\n'); + END(HTML_HEAD); + PUTC('\n'); + START(HTML_H1); + if ((title = HTAnchor_title(anAnchor))) + PUTS(title); + else { + PUTS(arg); + PUTS(GOPHER_SEARCH_RESULTS); + } + END(HTML_H1); + PUTC('\n'); + START(HTML_PRE); + + /* + * Start grabbing chars from the network. + */ + while ((ich = NEXT_CHAR) != EOF) { + if ((char) ich != LF) { + *p = (char) ich; /* Put character in line */ + if (p < &line[BIG - 1]) + p++; + } else { + *p = '\0'; /* Terminate line */ + p = line; /* Scan it to parse it */ + /* + * OK we now have a line in 'p'. Lets parse it and print it. + */ + + /* + * Break on line that begins with a 2. It's the end of data. + */ + if (*p == '2') + break; + + /* + * Lines beginning with 5 are errors. Print them and quit. + */ + if (*p == '5') { + START(HTML_H2); + PUTS(p + 4); + END(HTML_H2); + break; + } + + if (*p == '-') { + /* + * Data lines look like -200:#: + * where # is the search result number and can be multiple + * digits (infinite?). + * Find the second colon and check the digit to the left of it + * to see if they are different. If they are then a different + * person is starting. Make this line an

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

%s

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

Description

\n

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

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

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

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

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

%s table

\n", &escaped($charset); + printf FP "\n"; + printf FP "
\n";
+	printf FP "Code  Char  Entity   Render          Description\n";
+}
+
+sub make_mark() {
+	printf FP "----  ----  ------   ------          -----------------------------------\n";
+}
+
+sub escaped($) {
+	my $result = $_[0];
+	$result =~ s/&/&/g;
+	$result =~ s//>/g;
+	return $result;
+}
+
+sub make_row($$$) {
+	my $old_code = $_[0];
+	my $new_code = $_[1];
+	my $comments = $_[2];
+
+	# printf "# make_row %d %d %s\n", $old_code, $new_code, $comments;
+	my $visible = sprintf("&#%d;      ", $new_code);
+	if ($old_code < 256) {
+		printf FP "%4x    %c   %.13s  &#%d;             %s\n",
+			$old_code, $old_code,
+			$visible, $new_code,
+			&escaped($comments);
+	} else {
+		printf FP "%4x    .   %.13s  &#%d;             %s\n",
+			$old_code,
+			$visible, $new_code,
+			&escaped($comments);
+	}
+}
+
+sub null_row($$) {
+	my $old_code = $_[0];
+	my $comments = $_[1];
+
+	if ($old_code < 256) {
+		printf FP "%4x    %c                     %s\n",
+			$old_code, $old_code,
+			&escaped($comments);
+	} else {
+		printf FP "%4x    .                     %s\n",
+			$old_code,
+			&escaped($comments);
+	}
+}
+
+sub make_footer() {
+	printf FP "
\n"; + printf FP "\n"; + printf FP "\n"; +} + +# return true if the string describes a range +sub is_range($) { + return ($_[0] =~ /.*-.*/); +} + +# convert the U+'s to 0x's so strtod() can convert them. +sub zeroxes($) { + my $result = $_[0]; + $result =~ s/^U\+/0x/; + $result =~ s/-U\+/-0x/; + return $result; +} + +# convert a string to a number (-1's are outside the range of Unicode). +sub value_of($) { + my ($result, $oops) = strtod($_[0]); + $result = -1 if ($oops ne 0); + return $result; +} + +# return the first number in a range +sub first_of($) { + my $range = &zeroxes($_[0]); + $range =~ s/-.*//; + return &value_of($range); +} + +# return the last number in a range +sub last_of($) { + my $range = &zeroxes($_[0]); + $range =~ s/^.*-//; + return &value_of($range); +} + +sub one_many($$$) { + my $oldcode = $_[0]; + my $newcode = &zeroxes($_[1]); + my $comment = $_[2]; + + my $old_code = &value_of($oldcode); + if ( $old_code lt 0 ) { + printf "? Problem with number \"%s\"\n", $oldcode; + } else { + &make_mark if (( $old_code % 8 ) == 0 ); + + if ( $newcode =~ /^#.*/ ) { + &null_row($old_code, $comment); + } elsif ( &is_range($newcode) ) { + my $first_item = &first_of($newcode); + my $last_item = &last_of($newcode); + my $item; + + if ( $first_item lt 0 or $last_item lt 0 ) { + printf "? Problem with one:many numbers \"%s\"\n", $newcode; + } else { + if ( $comment =~ /^$/ ) { + $comment = sprintf("mapped: %#x to %#x..%#x", $old_code, $first_item, $last_item); + } else { + $comment = $comment . " (range)"; + } + for $item ( $first_item..$last_item) { + &make_row($old_code, $item, $comment); + } + } + } else { + my $new_code = &value_of($newcode); + if ( $new_code lt 0 ) { + printf "? Problem with number \"%s\"\n", $newcode; + } else { + if ( $comment =~ /^$/ ) { + $comment = sprintf("mapped: %#x to %#x", $old_code, $new_code); + } + &make_row($old_code, $new_code, $comment); + } + } + } +} + +sub many_many($$$) { + my $oldcode = $_[0]; + my $newcode = $_[1]; + my $comment = $_[2]; + + my $first_old = &first_of($oldcode); + my $last_old = &last_of($oldcode); + my $item; + + if (&is_range($newcode)) { + my $first_new = &first_of($newcode); + my $last_new = &last_of($newcode); + for $item ( $first_old..$last_old) { + &one_many($item, $first_new, $comment); + $first_new += 1; + } + } else { + for $item ( $first_old..$last_old) { + &one_many($item, $newcode, $comment); + } + } +} + +sub approximate($$$) { + my $values = $_[0]; + my $expect = sprintf("%-8s", $_[1]); + my $comment = $_[2]; + my $escaped = &escaped($expect); + my $left; + my $this; + my $next; + + $escaped =~ s/\\134/\\/g; + $escaped =~ s/\\015/\ \;/g; + $escaped =~ s/\\012/\ \;/g; + + while ( $escaped =~ /^.*\\[0-7]{3}.*$/ ) { + $left = $escaped; + $left =~ s/\\[0-7]{3}.*//; + $this = substr $escaped,length($left)+1,3; + $next = substr $escaped,length($left)+4; + $escaped = sprintf("%s&#%d;%s", $left, oct $this, $next); + } + + my $visible = sprintf("&#%d; ", $values); + if ($values < 256) { + printf FP "%4x %c %.13s &#%d; approx: %s\n", + $values, $values, + $visible, + $values, + $escaped; + } else { + printf FP "%4x . %.13s &#%d; approx: %s\n", + $values, + $visible, + $values, + $escaped; + } +} + +sub doit($) { + my $source = $_[0]; + + printf "** %s\n", $source; + + my $target = basename($source, ".tbl"); + + # Read the file into an array in memory. + open(FP,$source) || do { + print STDERR "Can't open input $source: $!\n"; + return; + }; + my (@input) = ; + chomp @input; + close(FP); + + my $n; + my $charset = ""; + my $official = ""; + my $empty = 1; + + for $n (0..$#input) { + $input[$n] =~ s/\s*$//; # trim trailing blanks + $input[$n] =~ s/^\s*//; # trim leading blanks + $input[$n] =~ s/^#0x/0x/; # uncomment redundant stuff + + next if $input[$n] =~ /^$/; + next if $input[$n] =~ /^#.*$/; + + if ( $empty + and ( $input[$n] =~ /^\d/ + or $input[$n] =~ /^U\+/ ) ) { + $target = $charset . ".html"; + printf "=> %s\n", $target; + open(FP,">$target") || do { + print STDERR "Can't open output $target: $!\n"; + return; + }; + &make_header($source, $charset, $official); + $empty = 0; + } + + if ( $input[$n] =~ /^M.*/ ) { + $charset = $input[$n]; + $charset =~ s/^.//; + } elsif ( $input[$n] =~ /^O.*/ ) { + $official = $input[$n]; + $official =~ s/^.//; + } elsif ( $input[$n] =~ /^\d/ ) { + + my $newcode = &field($input[$n], 1); + + next if ( $newcode eq "idem" ); + next if ( $newcode eq "" ); + + my $oldcode = &field($input[$n], 0); + if ( &is_range($oldcode) ) { + &many_many($oldcode, $newcode, ¬es($input[$n])); + } else { + &one_many($oldcode, $newcode, ¬es($input[$n])); + } + } elsif ( $input[$n] =~ /^U\+/ ) { + if ( $input[$n] =~ /^U\+\w+:/ ) { + my $values = $input[$n]; + my $expect = $input[$n]; + + $values =~ s/:.*//; + $values = &zeroxes($values); + $expect =~ s/^[^:]+://; + + if ( &is_range($values) ) { + printf "fixme:%s(%s)(%s)\n", $input[$n], $values, $expect; + } else { + &approximate(&value_of($values), $expect, ¬es($input[$n])); + } + } else { + my $value = $input[$n]; + $value =~ s/\s*".*//; + $value = &value_of(&zeroxes($value)); + if ($value gt 0) { + my $quote = $input[$n]; + my $comment = ¬es($input[$n]); + $quote =~ s/^[^"]*"//; + $quote =~ s/".*//; + &approximate($value, $quote, $comment); + } else { + printf "fixme:%d(%s)\n", $n, $input[$n]; + } + } + } else { + # printf "skipping line %d:%s\n", $n + 1, $input[$n]; + } + } + if ( ! $empty ) { + &make_footer(); + } + close FP; +} + +sub usage() { + print <= 0 ) { + &doit ( shift @ARGV ); + } +} +exit (0); diff --git a/src/AttrList.h b/src/AttrList.h new file mode 100644 index 0000000..cf38261 --- /dev/null +++ b/src/AttrList.h @@ -0,0 +1,62 @@ +/* + * $LynxId: AttrList.h,v 1.17 2013/05/03 20:54:09 tom Exp $ + */ +#if !defined(__ATTRLIST_H) +#define __ATTRLIST_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + enum { + ABS_OFF = 0, + STACK_OFF = 0, + STACK_ON, + ABS_ON + }; + +#define STARTAT 8 + + enum { + DSTYLE_LINK = HTML_A + STARTAT, + DSTYLE_STATUS = HTML_ELEMENTS + STARTAT, + DSTYLE_ALINK, /* active link */ + DSTYLE_NORMAL, /* default attributes */ + DSTYLE_OPTION, /* option on the option screen */ + DSTYLE_VALUE, /* value on the option screen */ + DSTYLE_CANDY, /* possibly going to vanish */ + DSTYLE_WHEREIS, /* whereis search target */ + DSTYLE_ELEMENTS + }; + + typedef struct { + int color; /* color highlighting to be done */ + int mono; /* mono highlighting to be done */ + int cattr; /* attributes to go with the color */ + } HTCharStyle; + +#if 0 +#define HText_characterStyle CTRACE((tfp,"HTC called from %s/%d\n",__FILE__,__LINE__));_internal_HTC +#else +#define HText_characterStyle _internal_HTC +#endif + +#if defined(USE_COLOR_STYLE) + extern void _internal_HTC(HText *text, int style, int dir); + +#define TEMPSTRINGSIZE 256 + extern char class_string[TEMPSTRINGSIZE + 1]; + +/* stack of attributes during page rendering */ +#define MAX_LAST_STYLES 128 + extern int last_styles[MAX_LAST_STYLES + 1]; + extern int last_colorattr_ptr; + +#endif + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/DefaultStyle.c b/src/DefaultStyle.c new file mode 100644 index 0000000..3dc17f7 --- /dev/null +++ b/src/DefaultStyle.c @@ -0,0 +1,504 @@ +/* + * $LynxId: DefaultStyle.c,v 1.20 2009/11/27 13:04:27 tom Exp $ + * + * A real style sheet for the Character Grid browser + * + * The dimensions are all in characters! + */ + +#include +#include +#include + +#include +#include + +/* Tab arrays: +*/ +static const HTTabStop tabs_8[] = +{ + {0, 8}, + {0, 16}, + {0, 24}, + {0, 32}, + {0, 40}, + {0, 48}, + {0, 56}, + {0, 64}, + {0, 72}, + {0, 80}, + {0, 88}, + {0, 96}, + {0, 104}, + {0, 112}, + {0, 120}, + {0, 128}, + {0, 136}, + {0, 144}, + {0, 152}, + {0, 160}, + {0, 168}, + {0, 176}, + {0, 0} /* Terminate */ +}; + +/* Template: + * link to next, name, name id (enum), tag, + * font, size, colour, superscript, anchor id, + * indents: 1st, left, right, alignment lineheight, descent, tabs, + * word wrap, free format, space: before, after, flags. + */ + +static HTStyle HTStyleNormal = +HTStyleInit( + 0, Normal, "P", + HT_FONT, 1, HT_BLACK, 0, 0, + 3, 3, 6, HT_LEFT, 1, 0, tabs_8, + YES, YES, 1, 0, 0); + +static HTStyle HTStyleDivCenter = +HTStyleInit( + &HTStyleNormal, DivCenter, "DCENTER", + HT_FONT, 1, HT_BLACK, 0, 0, + 3, 3, 6, HT_CENTER, 1, 0, tabs_8, + YES, YES, 1, 0, 0); + +static HTStyle HTStyleDivLeft = +HTStyleInit( + &HTStyleDivCenter, DivLeft, "DLEFT", + HT_FONT, 1, HT_BLACK, 0, 0, + 3, 3, 6, HT_LEFT, 1, 0, tabs_8, + YES, YES, 1, 0, 0); + +static HTStyle HTStyleDivRight = +HTStyleInit( + &HTStyleDivLeft, DivRight, "DRIGHT", + HT_FONT, 1, HT_BLACK, 0, 0, + 3, 3, 6, HT_RIGHT, 1, 0, tabs_8, + YES, YES, 1, 0, 0); + +static HTStyle HTStyleBanner = +HTStyleInit( + &HTStyleDivRight, Banner, "BANNER", + HT_FONT, 1, HT_BLACK, 0, 0, + 3, 3, 6, HT_LEFT, 1, 0, tabs_8, + YES, YES, 1, 0, 0); + +static HTStyle HTStyleBlockquote = +HTStyleInit( + &HTStyleBanner, Blockquote, "BLOCKQUOTE", + HT_FONT, 1, HT_BLACK, 0, 0, + 5, 5, 7, HT_LEFT, 1, 0, tabs_8, + YES, YES, 1, 0, 0); + +static HTStyle HTStyleBq = +HTStyleInit( /* HTML 3.0 BLOCKQUOTE - FM */ + &HTStyleBlockquote, Bq, "BQ", + HT_FONT, 1, HT_BLACK, 0, 0, + 5, 5, 7, HT_LEFT, 1, 0, tabs_8, + YES, YES, 1, 0, 0); + +static HTStyle HTStyleFootnote = +HTStyleInit( /* HTML 3.0 FN - FM */ + &HTStyleBq, Footnote, "FN", + HT_FONT, 1, HT_BLACK, 0, 0, + 5, 5, 7, HT_LEFT, 1, 0, tabs_8, + YES, YES, 1, 0, 0); + +static HTStyle HTStyleList = +HTStyleInit( + &HTStyleFootnote, List, "UL", + HT_FONT, 1, HT_BLACK, 0, 0, + 3, 7, 6, HT_LEFT, 1, 0, 0, + YES, YES, 0, 0, 0); + +static HTStyle HTStyleList1 = +HTStyleInit( + &HTStyleList, List1, "UL", + HT_FONT, 1, HT_BLACK, 0, 0, + 8, 12, 6, HT_LEFT, 1, 0, 0, + YES, YES, 0, 0, 0); + +static HTStyle HTStyleList2 = +HTStyleInit( + &HTStyleList1, List2, "UL", + HT_FONT, 1, HT_BLACK, 0, 0, + 13, 17, 6, HT_LEFT, 1, 0, 0, + YES, YES, 0, 0, 0); + +static HTStyle HTStyleList3 = +HTStyleInit( + &HTStyleList2, List3, "UL", + HT_FONT, 1, HT_BLACK, 0, 0, + 18, 22, 6, HT_LEFT, 1, 0, 0, + YES, YES, 0, 0, 0); + +static HTStyle HTStyleList4 = +HTStyleInit( + &HTStyleList3, List4, "UL", + HT_FONT, 1, HT_BLACK, 0, 0, + 23, 27, 6, HT_LEFT, 1, 0, 0, + YES, YES, 0, 0, 0); + +static HTStyle HTStyleList5 = +HTStyleInit( + &HTStyleList4, List5, "UL", + HT_FONT, 1, HT_BLACK, 0, 0, + 28, 32, 6, HT_LEFT, 1, 0, 0, + YES, YES, 0, 0, 0); + +static HTStyle HTStyleList6 = +HTStyleInit( + &HTStyleList5, List6, "UL", + HT_FONT, 1, HT_BLACK, 0, 0, + 33, 37, 6, HT_LEFT, 1, 0, 0, + YES, YES, 0, 0, 0); + +static HTStyle HTStyleMenu = +HTStyleInit( + &HTStyleList6, Menu, "MENU", + HT_FONT, 1, HT_BLACK, 0, 0, + 3, 7, 6, HT_LEFT, 1, 0, 0, + YES, YES, 0, 0, 0 +); + +static HTStyle HTStyleMenu1 = +HTStyleInit( + &HTStyleMenu, Menu1, "MENU", + HT_FONT, 1, HT_BLACK, 0, 0, + 8, 12, 6, HT_LEFT, 1, 0, 0, + YES, YES, 0, 0, 0 +); + +static HTStyle HTStyleMenu2 = +HTStyleInit( + &HTStyleMenu1, Menu2, "MENU", + HT_FONT, 1, HT_BLACK, 0, 0, + 13, 17, 6, HT_LEFT, 1, 0, 0, + YES, YES, 0, 0, 0 +); + +static HTStyle HTStyleMenu3 = +HTStyleInit( + &HTStyleMenu2, Menu3, "MENU", + HT_FONT, 1, HT_BLACK, 0, 0, + 18, 22, 6, HT_LEFT, 1, 0, 0, + YES, YES, 0, 0, 0 +); + +static HTStyle HTStyleMenu4 = +HTStyleInit( + &HTStyleMenu3, Menu4, "MENU", + HT_FONT, 1, HT_BLACK, 0, 0, + 23, 27, 6, HT_LEFT, 1, 0, 0, + YES, YES, 0, 0, 0 +); + +static HTStyle HTStyleMenu5 = +HTStyleInit( + &HTStyleMenu4, Menu5, "MENU", + HT_FONT, 1, HT_BLACK, 0, 0, + 28, 33, 6, HT_LEFT, 1, 0, 0, + YES, YES, 0, 0, 0 +); + +static HTStyle HTStyleMenu6 = +HTStyleInit( + &HTStyleMenu5, Menu6, "MENU", + HT_FONT, 1, HT_BLACK, 0, 0, + 33, 38, 6, HT_LEFT, 1, 0, 0, + YES, YES, 0, 0, 0 +); + +static HTStyle HTStyleGlossary = +HTStyleInit( + &HTStyleMenu6, Glossary, "DL", + HT_FONT, 1, HT_BLACK, 0, 0, + 3, 10, 6, HT_LEFT, 1, 0, 0, + YES, YES, 1, 1, 0 +); + +static HTStyle HTStyleGlossary1 = +HTStyleInit( + &HTStyleGlossary, Glossary1, "DL", + HT_FONT, 1, HT_BLACK, 0, 0, + 8, 16, 6, HT_LEFT, 1, 0, 0, + YES, YES, 1, 1, 0 +); + +static HTStyle HTStyleGlossary2 = +HTStyleInit( + &HTStyleGlossary1, Glossary2, "DL", + HT_FONT, 1, HT_BLACK, 0, 0, + 14, 22, 6, HT_LEFT, 1, 0, 0, + YES, YES, 1, 1, 0 +); + +static HTStyle HTStyleGlossary3 = +HTStyleInit( + &HTStyleGlossary2, Glossary3, "DL", + HT_FONT, 1, HT_BLACK, 0, 0, + 20, 28, 6, HT_LEFT, 1, 0, 0, + YES, YES, 1, 1, 0 +); + +static HTStyle HTStyleGlossary4 = +HTStyleInit( + &HTStyleGlossary3, Glossary4, "DL", + HT_FONT, 1, HT_BLACK, 0, 0, + 26, 34, 6, HT_LEFT, 1, 0, 0, + YES, YES, 1, 1, 0 +); + +static HTStyle HTStyleGlossary5 = +HTStyleInit( + &HTStyleGlossary4, Glossary5, "DL", + HT_FONT, 1, HT_BLACK, 0, 0, + 32, 40, 6, HT_LEFT, 1, 0, 0, + YES, YES, 1, 1, 0 +); + +static HTStyle HTStyleGlossary6 = +HTStyleInit( + &HTStyleGlossary5, Glossary6, "DL", + HT_FONT, 1, HT_BLACK, 0, 0, + 38, 46, 6, HT_LEFT, 1, 0, 0, + YES, YES, 1, 1, 0 +); + +static HTStyle HTStyleGlossaryCompact = +HTStyleInit( + &HTStyleGlossary6, GlossaryCompact, "DLC", + HT_FONT, 1, HT_BLACK, 0, 0, + 3, 10, 6, HT_LEFT, 1, 0, 0, + YES, YES, 0, 0, 0 +); + +static HTStyle HTStyleGlossaryCompact1 = +HTStyleInit( + &HTStyleGlossaryCompact, + GlossaryCompact1, "DLC", + HT_FONT, 1, HT_BLACK, 0, 0, + 8, 15, 6, HT_LEFT, 1, 0, 0, + YES, YES, 0, 0, 0 +); + +static HTStyle HTStyleGlossaryCompact2 = +HTStyleInit( + &HTStyleGlossaryCompact1, + GlossaryCompact2, "DLC", + HT_FONT, 1, HT_BLACK, 0, 0, + 13, 20, 6, HT_LEFT, 1, 0, 0, + YES, YES, 0, 0, 0 +); + +static HTStyle HTStyleGlossaryCompact3 = +HTStyleInit( + &HTStyleGlossaryCompact2, + GlossaryCompact3, "DLC", + HT_FONT, 1, HT_BLACK, 0, 0, + 18, 25, 6, HT_LEFT, 1, 0, 0, + YES, YES, 0, 0, 0 +); + +static HTStyle HTStyleGlossaryCompact4 = +HTStyleInit( + &HTStyleGlossaryCompact3, + GlossaryCompact4, "DLC", + HT_FONT, 1, HT_BLACK, 0, 0, + 23, 30, 6, HT_LEFT, 1, 0, 0, + YES, YES, 0, 0, 0 +); + +static HTStyle HTStyleGlossaryCompact5 = +HTStyleInit( + &HTStyleGlossaryCompact4, + GlossaryCompact5, "DLC", + HT_FONT, 1, HT_BLACK, 0, 0, + 28, 35, 6, HT_LEFT, 1, 0, 0, + YES, YES, 0, 0, 0 +); + +static HTStyle HTStyleGlossaryCompact6 = +HTStyleInit( + &HTStyleGlossaryCompact5, + GlossaryCompact6, "DLC", + HT_FONT, 1, HT_BLACK, 0, 0, + 33, 40, 6, HT_LEFT, 1, 0, 0, + YES, YES, 0, 0, 0 +); + +static HTStyle HTStyleExample = +HTStyleInit( + &HTStyleGlossaryCompact6, + Example, "XMP", + HT_FONT, 1, HT_BLACK, 0, 0, + 0, 0, 0, HT_LEFT, 1, 0, tabs_8, + NO, NO, 0, 0, 0 +); + +static HTStyle HTStylePreformatted = +HTStyleInit( + &HTStyleExample, + Preformatted, "PRE", + HT_FONT, 1, HT_BLACK, 0, 0, + 0, 0, 0, HT_LEFT, 1, 0, tabs_8, + NO, NO, 0, 0, 0 +); + +static HTStyle HTStyleListing = +HTStyleInit( + &HTStylePreformatted, Listing, "LISTING", + HT_FONT, 1, HT_BLACK, 0, 0, + 0, 0, 0, HT_LEFT, 1, 0, tabs_8, + NO, NO, 0, 0, 0); + +static HTStyle HTStyleAddress = +HTStyleInit( + &HTStyleListing, Address, "ADDRESS", + HT_FONT, 1, HT_BLACK, 0, 0, + 4, 4, 7, HT_LEFT, 1, 0, tabs_8, + YES, YES, 2, 0, 0); + +static HTStyle HTStyleNote = +HTStyleInit( /* HTML 3.0 NOTE - FM */ + &HTStyleAddress, Note, "NOTE", + HT_FONT, 1, HT_BLACK, 0, 0, + 5, 5, 7, HT_LEFT, 1, 0, tabs_8, + YES, YES, 1, 0, 0); + +static HTStyle HTStyleHeading1 = +HTStyleInit( + &HTStyleNote, Heading1, "H1", + HT_FONT + HT_BOLD, 1, HT_BLACK, 0, 0, + 0, 0, 0, HT_CENTER, 1, 0, 0, + YES, YES, 1, 1, 0); + +static HTStyle HTStyleHeading2 = +HTStyleInit( + &HTStyleHeading1, Heading2, "H2", + HT_FONT + HT_BOLD, 1, HT_BLACK, 0, 0, + 0, 0, 0, HT_LEFT, 1, 0, 0, + YES, YES, 1, 1, 0); + +static HTStyle HTStyleHeading3 = +HTStyleInit( + &HTStyleHeading2, Heading3, "H3", + HT_FONT + HT_BOLD, 1, HT_BLACK, 0, 0, + 2, 2, 0, HT_LEFT, 1, 0, 0, + YES, YES, 1, 0, 0); + +static HTStyle HTStyleHeading4 = +HTStyleInit( + &HTStyleHeading3, Heading4, "H4", + HT_FONT + HT_BOLD, 1, HT_BLACK, 0, 0, + 4, 4, 0, HT_LEFT, 1, 0, 0, + YES, YES, 1, 0, 0); + +static HTStyle HTStyleHeading5 = +HTStyleInit( + &HTStyleHeading4, Heading5, "H5", + HT_FONT + HT_BOLD, 1, HT_BLACK, 0, 0, + 6, 6, 0, HT_LEFT, 1, 0, 0, + YES, YES, 1, 0, 0); + +static HTStyle HTStyleHeading6 = +HTStyleInit( + &HTStyleHeading5, Heading6, "H6", + HT_FONT + HT_BOLD, 1, HT_BLACK, 0, 0, + 8, 8, 0, HT_LEFT, 1, 0, 0, + YES, YES, 1, 0, 0); + +static HTStyle HTStyleHeadingCenter = +HTStyleInit( + &HTStyleHeading6, HeadingCenter, "HCENTER", + HT_FONT + HT_BOLD, 1, HT_BLACK, 0, 0, + 0, 0, 3, HT_CENTER, 1, 0, tabs_8, + YES, YES, 1, 0, 0); + +static HTStyle HTStyleHeadingLeft = +HTStyleInit( + &HTStyleHeadingCenter, HeadingLeft, "HLEFT", + HT_FONT + HT_BOLD, 1, HT_BLACK, 0, 0, + 0, 0, 3, HT_LEFT, 1, 0, tabs_8, + YES, YES, 1, 0, 0); + +static HTStyle HTStyleHeadingRight = +HTStyleInit( + &HTStyleHeadingLeft, HeadingRight, "HRIGHT", + HT_FONT + HT_BOLD, 1, HT_BLACK, 0, 0, + 0, 0, 3, HT_RIGHT, 1, 0, tabs_8, + YES, YES, 1, 0, 0); + +/* Style sheet points to the last in the list: +*/ +static HTStyleSheet sheet = +{"default.style", + &HTStyleHeadingRight}; /* sheet */ + +static HTStyle *st_array[ST_HeadingRight + 1] = +{NULL}; + +static HTStyleSheet *result = NULL; + +#ifdef LY_FIND_LEAKS +static void FreeDefaultStyle(void) +{ + HTStyle *style; + + while ((style = result->styles) != 0) { + result->styles = style->next; + FREE(style); + } + FREE(result); +} +#endif /* LY_FIND_LEAKS */ + +HTStyleSheet *DefaultStyle(HTStyle ***result_array) +{ + HTStyle *p, *q; + + /* + * The first time we're called, allocate a copy of the 'sheet' linked + * list. Thereafter, simply copy the data from 'sheet' into our copy + * (preserving the copy's linked-list pointers). We do this to reset the + * parameters of a style that might be altered while processing a page. + */ + if (result == 0) { /* allocate & copy */ + result = HTStyleSheetNew(); + *result = sheet; + result->styles = 0; +#ifdef LY_FIND_LEAKS + atexit(FreeDefaultStyle); +#endif + for (p = sheet.styles; p != 0; p = p->next) { + q = HTStyleNew(); + *q = *p; + if (no_margins) { + q->indent1st = 0; + q->leftIndent = 0; + q->rightIndent = 0; + } + st_array[q->id] = q; + q->next = result->styles; + result->styles = q; + } + } else { /* recopy the data */ + for (q = result->styles, p = sheet.styles; + p != 0 && q != 0; + p = p->next, q = q->next) { + HTStyle *r = q->next; + + *q = *p; + if (no_margins) { + q->indent1st = 0; + q->leftIndent = 0; + q->rightIndent = 0; + } + st_array[q->id] = q; + q->next = r; + } + } + *result_array = st_array; + return result; +} diff --git a/src/GridText.c b/src/GridText.c new file mode 100644 index 0000000..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,"